1. Why arrays are needed
When you are just starting to program, it feels like you can create variables endlessly: score1, score2, score3… and everything will be fine. But reality quickly reminds you: a programmer is not an accountant with a calculator, and storing a “list of similar things” in dozens of separate variables is a path to chaos, bugs, and sadness.
Imagine a task: store steps for a week, or grades for 10 quizzes, or daily temperature values. If you do this with separate variables, you immediately run into three problems: it is inconvenient to iterate through values with a loop, inconvenient to pass a “set” into functions, and inconvenient to extend the logic. We need a structure that holds “many conceptually similar pieces of data” under one name. That structure is an array.
In programming, it is very useful to look at things from different angles. When you see 7 values (for example, by days of the week), you can think: “these are seven separate numbers.” Or you can think: “this is one dataset that has a size and elements.” An array is exactly what trains your brain to see a collection of similar things everywhere.
Let’s imagine what a workout tracker would look like without arrays. It would be something like:
fun main() {
val mon = 0
val tue = 30
val wed = 0
val thu = 45
val fri = 0
val sat = 60
val sun = 15
println("Monday minutes = $mon") // Monday minutes = 0
}
Does it work? Yes. Is it convenient? Not really. And what if later you need to “loop over the days” and print all the values? What if you need to find the maximum? What if you need to replace “7 days” with “30 days” (hypothetically)? This is where arrays start winning by a landslide.
What an array is
An array is a data structure that stores a fixed number of values of one type (or its subtypes). In Kotlin, arrays are an official tool for storing many homogeneous values and working with them as a single object. The key word here is fixed: however many elements you set initially—that is how many you will have.
A handy everyday analogy: an array is a box with numbered compartments. The box is made at the factory, and the number of compartments does not change. You can move the contents around, you can look at what is in a specific compartment, you can go through all compartments in order—but you cannot “insert one more compartment between 3 and 4.” The “box” does not support resizing.
It is worth emphasizing the word “fixed” again, because it is one of the most common sources of surprise. You cannot “extend” an array the way you extend a paper to-do list: add an item—and that’s it. If an array has 7 elements, an “eighth” will not fit. This is a fundamental property of arrays: the size is set at creation time and does not change.
In practice, this means: if you suddenly need more space, you create a new array and move the data. That sounds a bit scary, but later we will learn more convenient ways to do it. For today, it is enough to remember the rule: an array is a “fixed box,” not a “stretchy bag.”
2. Types of arrays in Kotlin: Array and IntArray
In Kotlin there are two families of arrays that you should know about right from the start:
- Array<T> — a generic array for objects (for example, String and any classes).
- primitive arrays (IntArray, DoubleArray, CharArray, etc.) — optimized for numbers/characters without extra overhead.
Array<T>: a generic array
The main “generic” Kotlin array is Array<T>. It is suitable for storing objects: strings, any classes (and later, when you have your own types, those too). The element type is written in angle brackets: Array<String>, Array<Int>, Array<Char>.
The simplest way to create such an array is arrayOf(...): you list the elements, and Kotlin builds an array from them.
Example: an array of strings via arrayOf(...)
fun main() {
val names: Array<String> = arrayOf("Ann", "Bob", "Cindy")
println("names size = ${names.size}") // names size = 3
}
Here a new important property appears right away: size. It is the number of elements in the array. You will need it immediately, because all array bounds depend on its size.
Example: Kotlin often infers the type
fun main() {
val colors = arrayOf("red", "green", "blue")
println("colors size = ${colors.size}") // colors size = 3
}
We did not write Array<String>—and it still worked correctly, because Kotlin inferred the type from the values. It is convenient, but early on it can be useful to sometimes write the type explicitly so your brain gets used to reading code “from left to right.”
Primitive arrays: IntArray, DoubleArray, CharArray
At this point, almost every beginner asks a reasonable question: “Why can’t we always use Array<T>? Why do we even need IntArray, DoubleArray?”
The answer is: you can use Array<T>, but Kotlin also provides separate arrays for primitive types: IntArray, DoubleArray, CharArray, etc. The Java language, on which Kotlin was built, allows primitive types to be packed very compactly into arrays.
Elements in primitive arrays are packed more tightly than in Array<T>. Due to this optimization, they take up less memory and run faster. Just remember the rule: for integers in basic tasks, use IntArray, for Double — DoubleArray, for characters — CharArray. This makes code easier to read and reduces surprises. Kotlin explicitly treats such “primitive arrays” as separate types.
Let’s compare “head-on”:
| What we store | Generic array | Primitive array |
|---|---|---|
| Strings | |
(no separate one) |
| Integers | |
|
| Fractions | |
|
| Characters | |
|
Important: IntArray is not Array<Int>. They are similar in usage (there is size, you can iterate with a loop), but they are different types.
Example: an integer array via intArrayOf(...)
fun main() {
val scores: IntArray = intArrayOf(10, 20, 30)
println("scores size = ${scores.size}") // scores size = 3
}
4. Creating an array of the required size and default values
Up to this point, we created arrays when we already knew the exact elements. But in real tasks, it is often the other way around: we know how many elements we need, and the values will appear later (for example, the user will enter 7 numbers, one per day of the week). For this, primitive arrays have a constructor of the form IntArray(size).
And here is an important point: the array will be filled with default values. For Int, this is 0.
Example: create a 5-element array filled with zeros
fun main() {
val zeros = IntArray(5)
println("zeros size = ${zeros.size}") // zeros size = 5
for (x in zeros) {
print("$x ")
}
// 0 0 0 0 0
}
Pay attention to for (x in zeros): we iterate through the array elements “by value.” We are not working with indices yet (that will be the next lecture), but we can already see the contents.
5. Mini-application: a weekly workout tracker
So that arrays are not “in a vacuum,” we will start assembling a small console application that we will keep developing throughout the course. The idea is simple: we want to store workout data for a week. For now, without input, without editing, without statistics— just a structural scaffold. Yes, this is very similar to real development: first we build the skeleton, then we fill it with logic.
In the first version we will create:
- an array of weekday names (strings),
- an array of workout minutes for each day (integers),
- print how many days we have in the data to make sure the structure is assembled.
Example: data skeleton (days + minutes)
fun main() {
val days: Array<String> = arrayOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
val minutes: IntArray = intArrayOf(0, 30, 0, 45, 0, 60, 15)
println("Days count = ${days.size}") // Days count = 7
println("Minutes count = ${minutes.size}") // Minutes count = 7
}
For now this looks like “just two arrays,” but an important idea is already visible: data of the same nature should be stored together. We do not make monMinutes, tueMinutes, wedMinutes— we make one array minutes, where each position corresponds to a weekday.
And one more important point: if you have two arrays that run “in parallel” (day and minutes), their sizes must match. Otherwise, the logic will start to break, and debugging will turn into an archaeological expedition through your code.
6. Typical mistakes when working with arrays
Mistake #1: expecting an array to “resize itself.”
Beginners sometimes think: “well, I can assign a[10] = 5, so the array will get bigger.” No: the size is fixed, and trying to access beyond the bounds will cause a runtime error. Remember today’s mantra: an array is a fixed-size box.
Mistake #2: confusing Array<Int> and IntArray and being surprised that Kotlin does not accept one in place of the other.
These are different types. In basic integer tasks, choose IntArray: it is easier to read and there is less chance to get confused. Kotlin describes primitive arrays as independent classes.
Mistake #3: creating IntArray(n) and expecting “random values” or “emptiness.”
IntArray(n) is filled with zeros, because Kotlin guarantees a predictable initial state. This is convenient, but if you expected something else, make sure to add an explicit initialization step (we will do that in later lectures).
Mistake #4: making an array out of “everything at once,” mixing types.
An array likes order: one array—one type. If you feel like you “really want to mix,” most often it is a sign that the data should be stored differently (but that is not today’s discussion). For now, stick to the simple rule: Array<String> for strings, IntArray for integers.
Mistake #5: forgetting to sanity-check the sizes of parallel arrays.
If you have two arrays that must be in sync (for example, days and minutes), they must have the same size. Otherwise you will later get “a day exists, but minutes do not”— almost like a gym membership, but without the gym.