Pocket Studio Academy
HomePart 11.10

Lists

Full course10 min read·4 questions

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:

kotlin
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:

kotlin
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 it like this

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

kotlin
1val names = listOf("Ada", "Alan", "Grace")
2println(names.size)      // 3
3println(names[0])        // Ada
4println(names[2])        // Grace

listOf(...) 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:

kotlin
println(names[names.size - 1])   // Grace

Kotlin has kinder ways to say the same thing:

kotlin
1println(names.first())    // Ada
2println(names.last())     // Grace
Careful

Ask for an index that does not exist and the program crashes while running — this one is not caught at build time:

kotlin
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:

kotlin
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:

kotlin
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 :

kotlin
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.
Note

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:

kotlin
1val a = mutableListOf(1)
2a.add(2)          // fine — changing the list
3// a = mutableListOf(3)   // refused — changing the name

Questions you can ask a list

These work on any list, changeable or not:

kotlin
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

Try it in Pocket Studio
  1. Open Pocket Studio, tap Projects, open KotlinLab, then open Lab.kt.
  2. Clear the inside of main and type in the program from the walkthrough above.
  3. Tap Run. The Output panel should show 3, Ada, Grace, then Ada, Alan, Grace on three lines, then [Feed cat].
  4. Add println(names[3]) and run. The program crashes with IndexOutOfBoundsException: 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.
  5. Add println(names.indices) and run. It prints 0..2: the only positions that exist.
  6. Try to change a read-only list — add names[0] = "Bea" and run. Read No set method providing array access, then delete the line.
  7. Now do it properly: add todo.add("Water plants") before the println(todo) line and run. The output becomes [Feed cat, Water plants].
  8. Last one: add println(todo.joinToString(" then ")). Much nicer for a human to read.
Error Doctor5 common errors
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
MeansYou asked for a position the list does not have. This one happens while the program runs, not while it builds — Kotlin cannot know the index in advance.
FixRemember the last index is 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.
e: Unresolved reference: add
MeansYou called add on a list made with listOf, which is read-only. It has no such function.
FixBuild it with mutableListOf(...) instead. If the list should not change, the error has found a real bug in the line trying to change it.
e: No set method providing array access
MeansYou wrote names[0] = "Bea" on a read-only list. Reading with square brackets is allowed; writing is not.
FixUse mutableListOf(...), or build a new list instead of editing the old one.
e: Unresolved reference: length
Meanslength belongs to String. A list counts its items with size.
FixUse names.size. Worth memorising as a pair: String has length, List has size.
e: Type mismatch: inferred type is Int but String was expected
MeansYou tried to add a number to a list of text. A List<String> holds text and nothing else.
FixConvert it with .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.
Recap
  • A holds many values under one name, in order.
  • listOf(...) builds a read-only list; builds one you can add to and remove from.
  • 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. Use list.indices when you genuinely need the positions.
  • A val list can still change its contents if it is a MutableList. val protects the name, not what is inside.
  • String has length; List has size.
  • 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.