Kotlin from the ground up

Lists

Holding several values in order, reading them by position, walking them with a loop, and the difference between listOf and mutableListOf.

Part 7 of 12

Intermediate16 min readBack to Courses

One name for many values

listOf("bread", "milk", "eggs") makes a list: several values, in the order you wrote them, held under one name. The order matters and repeats are allowed, so a list is the right shape for anything you would write down in a column. A list is not a set: two identical values stay two entries, and their positions are different.

The type is worked out from the values, so a list of text is a list of strings and a list of whole numbers is a list of Int. Kotlin will not let you add text to a list of numbers, which is how a list keeps its promise about what it holds. An empty list needs its type from somewhere, which is why listOf<Int>() is the spelling when there are no values to infer from.

Reading one value out of the list

Counting starts at zero, so shopping[0] is the first item and shopping[1] is the second. first() and last() say what they mean without arithmetic, size tells you how many values there are, and contains answers with a Boolean. indexOf answers with a position, and -1 when the value is absent — a number you have to check rather than a value you can use directly.

An index that is past the end throws at run time instead of handing back nothing — the runtime stops the program and names the line. When an index comes from a calculation of your own, checking it against size first is the habit that keeps the crash away.

Fixed lists, changeable lists, and loops

A list from listOf cannot grow or shrink afterwards; that is what makes it safe to pass around. mutableListOf is the version you can change, with add to append and remove to take a value out. Start with the fixed version and move to the mutable one only when the program really does change the list. A fixed list can still be handed to a function without the function being able to add to it.

for (item in list) walks the values in order, which is what you want nine times out of ten. The list also has its own answers to small questions — sum, max, sorted, joinToString — and using them keeps the loop out of your file entirely.

The index is a position, not a value

An index is an address in the list, and it changes when the list changes. Removing the first item moves every later item one place to the left, so an index saved before the removal may now point at a different value or past the end. When you need to remove several items, work from the end backwards or collect the values you want and build a new list.

The same confusion appears in remove and removeAt: one searches by value, the other by position. On a list of numbers the argument looks identical either way, so the method name is the only clue. Read the call as a sentence — "remove the value 2" against "remove the item at position 2" — and the difference becomes hard to miss.

Reading, looping and changing a list

fun main() {
    val shopping = listOf("bread", "milk", "eggs")
    println(shopping.size)              // 3
    println(shopping[0])                // bread
    println(shopping.first())           // bread
    println(shopping.contains("milk"))  // true
    println(shopping.joinToString(", "))

    val numbers = listOf(4, 8, 15)
    println(numbers.sum())              // 27
    println(numbers.max())              // 15
    println(numbers.sorted())           // [4, 8, 15]

    val todo = mutableListOf("write", "run")
    todo.add("print")
    todo.remove("run")
    println(todo)                       // [write, print]
}

A shopping list, then a mutable one

Run it and read the list answers one line at a time. Then add a fourth item to the fixed list where it is written, print the item in the last position, and give the mutable list one more change of your own.

Output
Click "Run" to execute your code

A list is a view over a sequence

A list keeps its items in order and knows how many there are. Reading by index is a direct lookup, not a search: the runtime checks that the index is at least zero and less than size, then returns the item in that slot. That check is why an index equal to size throws instead of returning a default — there is no slot there, and guessing would hide the mistake.

listOf gives a read-only view. It does not mean the underlying data can never change; it means this name has no add or remove. mutableListOf gives a view with those operations. Functions such as sorted, map and filter do not modify either kind: they build a new list and hand it back, leaving the original in the order you gave it.

Three list mistakes show up constantly. The last valid index is size - 1, so list[size] throws. A list from listOf has no add, and the compiler refuses the call rather than letting the program fail later. And remove takes a value while removeAt takes an index — on a list of numbers the two can look identical at a glance, with very different results.

Try these next

  • Print the first, middle and last item of a four-item list without using a loop.
  • Build a mutable list, remove its first item by index, and print what is left.
  • Sort a list of words and compare it with the original to see that the original kept its order.

A playlist that can change

Run it and find the item at the middle index. Then add a fifth track, remove the first one by index, and print the list after each change.

Output
Click "Run" to execute your code

A list printed with println shows its brackets and commas, and text items show no quotes. That is the readable form of a list, so the output pane is often the quickest way to check what a list actually holds.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What is the difference between listOf and mutableListOf?