Lists
One name for many values, kept in order. Learn to build a list, reach into it by position, walk through it with a loop, and understand why counting starts at zero — plus the crash that catches everyone the first time.
Three names is fine. Three hundred is not.
Say you are storing the players in a game:
1val player1 = "Ada"
2val player2 = "Alan"
3val player3 = "Grace"That is survivable. Now write the code that prints all of them. Then the code that counts them. Then add a fourth player while the game is running.
You cannot. Every one of those jobs needs to talk about "all the players" as a single thing, and three separate names is three separate things that merely look related.
A is one name holding many values, kept in order:
val players = listOf("Ada", "Alan", "Grace")One name. Countable, walkable, printable, passable to a function. This is the shape almost all real app data has — a list of notes, a list of messages, a list of scores.
Think of an egg box.
It is one object you can pick up, carry and hand to someone: the box. But it also has six cups, in a fixed order, and you can talk about "the third one" without disturbing the others. Count them and you get six, whether or not every cup is full.
A list is that box. players is the box. players[0] is a particular cup. players.size is how many cups there are.
And here is the odd bit that programmers have lived with for fifty years: the first cup is numbered zero.
Making one and looking inside
1val names = listOf("Ada", "Alan", "Grace")
2println(names.size) // 3
3println(names[0]) // Ada
4println(names[2]) // GracelistOf(...) builds a list from the values you give it. Each value is an .
The square brackets reach in by position. That position is called the , and — this is the part to memorise —
The first element is at index 0.
So a list of 3 has indexes 0, 1 and 2. There is no index 3. The last index is always size - 1, which is why this line turns up everywhere:
println(names[names.size - 1]) // GraceKotlin has kinder ways to say the same thing:
1println(names.first()) // Ada
2println(names.last()) // GraceAsk for an index that does not exist and the program crashes while running — this one is not caught at build time:
1val names = listOf("Ada", "Alan", "Grace")
2println(names[3])
java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
This is the from Lesson 1.7 with real consequences. Three items, and 3 is still too far — because we started at 0.
Walking through it
A for loop over a list needs no numbers at all:
1for (name in names) {
2 println(name)
3}Read it as "for each name in names". Each pass, name holds the next . It stops by itself at the end, so it can never run off the edge.
When you genuinely need the position too, indices gives you the valid range:
1for (i in names.indices) {
2 println("$i: ${names[i]}")
3}names.indices for a list of 3 is 0..2 — exactly the legal positions, worked out for you. Prefer this over 0 until names.size; it says the same thing and cannot be got wrong.
Lists that can change
listOf gives you a read-only list. There is no add, no remove, and names[0] = "X" is refused with No set method providing array access. That is deliberate: most lists never need to change, and one that cannot change cannot be changed by accident.
When you do need to add and remove, ask for a :
1val todo = mutableListOf("Wash up")
2todo.add("Feed cat")
3todo.add("Do homework")
4todo.removeAt(0)
5todo.remove("Feed cat")
6println(todo) // [Do homework]The useful ones:
add(item)puts an item on the end.removeAt(index)removes by position.remove(item)removes the first match by value.clear()empties it.
Here is a subtlety worth its own paragraph, because it surprises people.
val todo = mutableListOf(...) still lets you add items. The val protects the name, not the contents: todo will always point at that same list, but the list itself can change.
To point the name at a different list entirely you would need var. Both ideas are useful, and they are genuinely separate:
1val a = mutableListOf(1)
2a.add(2) // fine — changing the list
3// a = mutableListOf(3) // refused — changing the nameQuestions you can ask a list
These work on any list, changeable or not:
1println(names.isEmpty()) // false
2println("Ada" in names) // true
3println(names.indexOf("Alan")) // 1
4println(names.joinToString(", "))in is the readable way to ask "is this in there?". indexOf gives the position, or -1 if it is not present — check for -1 before using the answer as an index. And joinToString turns a list into one tidy String, which beats printing the raw list when a person has to read it.
Every list also knows its . listOf("Ada", "Alan") is a List<String>, and you cannot put a number in it — the same checking you met in Lesson 1.2, applied to collections.
The whole lesson in one program
- Open Pocket Studio, tap Projects, open KotlinLab, then open
Lab.kt. - Clear the inside of
mainand type in the program from the walkthrough above. - Tap Run. The Output panel should show
3,Ada,Grace, thenAda,Alan,Graceon three lines, then[Feed cat]. - Add
println(names[3])and run. The program crashes withIndexOutOfBoundsException: Index 3 out of bounds for length 3. Read it carefully — "length 3" and "index 3" in the same sentence is the whole lesson. Delete the line. - Add
println(names.indices)and run. It prints0..2: the only positions that exist. - Try to change a read-only list — add
names[0] = "Bea"and run. Read No set method providing array access, then delete the line. - Now do it properly: add
todo.add("Water plants")before theprintln(todo)line and run. The output becomes[Feed cat, Water plants]. - Last one: add
println(todo.joinToString(" then ")). Much nicer for a human to read.
size - 1. Safer still, avoid indexes entirely: use for (item in list) to walk it, first() and last() for the ends, and getOrNull(i) when the index might genuinely be out of range.add on a list made with listOf, which is read-only. It has no such function.mutableListOf(...) instead. If the list should not change, the error has found a real bug in the line trying to change it.names[0] = "Bea" on a read-only list. Reading with square brackets is allowed; writing is not.mutableListOf(...), or build a new list instead of editing the old one.length belongs to String. A list counts its items with size.names.size. Worth memorising as a pair: String has length, List has size.List<String> holds text and nothing else..toString(), or make a list that holds the right type. If it truly needs to hold both, that is a design question worth answering rather than working around.- A holds many values under one name, in order.
listOf(...)builds a read-only list; builds one you canaddto andremovefrom.- The of the first is 0, so the last is
size - 1. Going past the end throws while the program runs. - Walk a list with
for (item in list)— no indexes, no chance of running off the end. Uselist.indiceswhen you genuinely need the positions. - A
vallist can still change its contents if it is aMutableList.valprotects the name, not what is inside. - String has
length; List hassize. - Next: lists are ordered by position. Sometimes you want to look things up by a word instead — that is what maps and sets are for.