Start learning free

LESSON 116 OF 677 · ARRAYS I

Slicing a List in Kotlin with subList

Lesson Read the full explanation

TL ; DR:

list.subList(i, j) gives the items from index i up to, not including, j. The original list doesn't change.


val letters = mutableListOf("a", "b", "c", "d", "e")

val middle = letters.subList(1, 4)
println(middle)    // [b, c, d]

println(letters)   // [a, b, c, d, e]


Full lesson:

Sometimes you need only part of a list, like the first few items. The subList method gives you that part.

Give it the start index and the end index. The item at the end index is not included. So letters.subList(1, 4) has the items at 1, 2 and 3.

That makes the length easy to see: 4 - 1 = 3 items.

To go to the very end, use the size as the end index:

val tail = letters.subList(3, letters.size)
println(tail)   // [d, e]

Taking a sub-list leaves the original as it was.

But subList doesn't copy the items. It shows the original's items, so changing one changes the other. Call toMutableList() on it when you need a separate copy.


Your task

runners holds a race's runners, in the order they finished.

Put the first three in a new ArrayList named podium. Put everyone else, from index 3 to the end, in one named rest.

The code below prints both, then the length of runners.

Expected output:

Ana Ben Cleo
Dan Eve
5
STEP 1 OF 2
    Ctrl + Enter to check

    KOTLIN · ARRAYS I

    Array Slicing in Kotlin, explained

    Taking part of a list

    Sometimes you need only part of a list, like the first few items. subList(i, j) gives the items from index i up to, but not including, index j. So letters.subList(1, 4) holds the items at indexes 1, 2 and 3, and its length is 4 - 1 = 3.

    val letters = mutableListOf("a", "b", "c", "d", "e")
    val middle = letters.subList(1, 4)
    println(middle)
    println(letters)
    
    [b, c, d]
    [a, b, c, d, e]
    

    The original list keeps all its items. To go to the very end, use the size as the end index:

    println(letters.subList(3, letters.size))
    
    [d, e]
    

    When both indexes are equal, the result is empty: letters.subList(2, 2) prints [].

    subList is a view, not a copy

    The result doesn't hold its own items. It shows a range of the original list, so a change through one is a change in the other:

    middle[0] = "X"
    println(letters)
    
    [a, X, c, d, e]
    

    When you need a separate list, call toMutableList() on the slice. Changing the copy then leaves the original alone:

    val copy = letters.subList(0, 2).toMutableList()
    copy[0] = "Z"
    println(letters)
    println(copy)
    
    [a, X, c, d, e]
    [Z, X]
    

    Traps

    • The end index is excluded. To include index 3, the end is 4.
    • Bad bounds throw. On the five-letter list, subList(3, 9) throws an IndexOutOfBoundsException (toIndex = 9), and subList(3, 1) throws an IllegalArgumentException (fromIndex(3) > toIndex(1)).
    • Changing the size of the original. If you add to or remove from the original list after taking a view, reading the view afterwards throws a ConcurrentModificationException. Copy the slice first if the original will keep changing.

    Common slices

    With a number n and the list letters (five items again), three slices cover most needs:

    val n = 2
    println(letters.subList(0, n))
    println(letters.subList(letters.size - n, letters.size))
    println(letters.subList(1, letters.size - 1))
    
    [a, b]
    [d, e]
    [b, c, d]
    

    These are the first n items, the last n items, and everything except the two ends. Kotlin also has take(n) and drop(n), which return new lists rather than views, but this lesson practises subList.

    Why the end is left out

    Leaving the end index out looks odd at first, but it makes slices easy to combine. subList(0, 3) and subList(3, size) meet exactly at index 3: nothing is repeated and nothing is missed, which is just what the exercise needs. It also means an empty slice is natural: subList(0, 0), or subList(size, size) at the very end, is simply [].

    Because a slice is a view, it can even change the original. Adding "Z" to the view letters.subList(1, 4) inserts it into letters right after the view's last item, giving [a, b, c, d, Z, e], and removing the view's first item removes b from letters too. Most of the time you only read a slice, and then none of this matters.

    The exercise

    runners holds five names in finishing order. The podium is the first three, so it is subList(0, 3). The rest start at index 3 and run to the end, so the end index is runners.size. Nothing is removed from runners, which is why its size still prints 5.

    val runners = mutableListOf("Ana", "Ben", "Cleo", "Dan", "Eve")
    val podium = runners.subList(0, 3)
    val rest = runners.subList(3, runners.size)
    println(podium[0] + " " + podium[1] + " " + podium[2])
    println(rest[0] + " " + rest[1])
    println(runners.size)
    
    Ana Ben Cleo
    Dan Eve
    5
    

    Both slices here are views of runners, which is fine because the code only reads them. If it had to change podium without touching the race results, you would add .toMutableList() to the first line.

    Key takeaways

    • subList(i, j) covers indexes i to j - 1.
    • Use the size as the end index to reach the last item.
    • The slice is a view of the original; add toMutableList() for a real copy.
    Keep going with Kotlin500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free