Start learning free

LESSON 148 OF 348 · LOOPING OVER ARRAY INDICES

Changing Array Elements in a Kotlin For Loop

Lesson Read the full explanation

Suppose you have an array nums and your task is to increment every number in that array by one.

The first idea that might come to your mind is to write a for loop like this:

val nums = intArrayOf(1, 2, 3)

// Increment items:
for (num in nums) {
  num++
}

// Print the array after:
for (num in nums) {
  println(num)
}

but when you run this code, you'll be surprised to see that the output is:

1
2
3

No element in nums changed its value. How is this possible since we clearly do num++ for every num in nums?


Let's talk about copies:

Take a look at this code:

val number = 100

var number_copy = number

number_copy++

println(number_copy) // Output: 101
println(number) // Output: 100

Although we wrote var number_copy = number, this doesn't mean that number and number_copy are one and the same.

number_copy is a completely different entitity (stored at a different memory address) than number.

What we did is initialize this new number_copy entity with the value of number. That's why we say the number_copy is a copy of number.

And so number_copy++ will only increment number_copy and won't have any effect on number.


The loop variable is just a copy:

This is what also happens with a for loop.

Whenever you write a for loop, the loop variable (in our case num) is just a copy of the current item of the array, not the item itself.

This code

val nums = intArrayOf(1, 2, 3)

for (num in nums) {
  num++
}

works exactly like this code:

val nums = intArrayOf(1, 2, 3)

var num = nums[0]
num++

num = nums[1]
num++

num = nums[2]
num++

Notice that only the variable num is being manipulated, which has nothing to do with the actual entries of the array nums.

You'll see this better if we also print the value of num on each iteration.

val nums = intArrayOf(1, 2, 3)

for (num in nums) {
  num++
  println(num)
}

// Print the array after:
for (num in nums) {
  println(num)
}

The output of this code is:

2
3
4
1
2
3

Looping over the indices:

In order to manipulate the entries of some array we have to loop over the items' indices:

val nums = intArrayOf(1, 2, 3)

for (index in 0 until nums.size) {
  nums[index]++
}

This code works exactly like this one:

val nums = intArrayOf(1, 2, 3)

var index = 0
nums[index]++

index++
nums[index]++

index++
nums[index]++

In this way we directly manipulate the actual entries of the list (nums[0], nums[1], etc.)

Assignment:

Fix our code such that it correctly increments (adds one to) the even numbers of nums and decrements (subtracts one from) the odd ones.

STEP 1 OF 1
    Ctrl + Enter to check

    KOTLIN · LOOPING OVER ARRAY INDICES

    Buggy Code Manipulate Array in Kotlin, explained

    The idea: the loop variable is not the element

    A common first attempt at "add one to every number in the array" is a for loop that increments the loop variable:

    val nums = intArrayOf(1, 2, 3)
    
    for (num in nums) {
        num++
    }
    

    In Kotlin this code does not even compile. The compiler reports:

    error: 'val' cannot be reassigned.
    

    Two facts explain it. First, the variable in for (num in nums) is a read-only val, so any assignment to it, including num++ and num--, is rejected. Second, even if Kotlin allowed the change, it would not reach the array. On each iteration num receives a copy of the current element, and a copy lives on its own.

    Copies in Kotlin

    Copying an Int into a new variable gives you two independent values:

    val number = 100
    var numberCopy = number
    numberCopy++
    
    println(numberCopy)
    println(number)
    
    101
    100
    

    Incrementing numberCopy leaves number alone. A for ... in loop does the same thing for each element. This loop:

    for (num in nums) {
        println(num)
    }
    

    behaves like this sequence of assignments, where num is a fresh value each time and nums itself is only read:

    val num0 = nums[0]
    val num1 = nums[1]
    val num2 = nums[2]
    

    Reading elements this way is fine whenever you only need their values: summing, searching, printing. To change the array, you need a different loop.

    Looping over the indices

    The elements of an array are reached through their positions. If the loop variable is an index, you can write to nums[index] directly:

    val nums = intArrayOf(1, 2, 3)
    
    for (index in 0 until nums.size) {
        nums[index]++
    }
    
    println(nums.joinToString())
    
    2, 3, 4
    

    Here the loop variable is the index, which is only read, and the thing that changes is nums[index], an element stored in the array. nums[index]++ is allowed because nums is an IntArray, and the elements of an array are mutable even when the variable holding the array is a val. The val means nums always refers to the same array, not that the array's contents are frozen.

    Some equivalent ways to get the indices:

    • 0 until nums.size counts from 0 up to, but not including, nums.size.
    • nums.indices is the same range, ready-made. Most Kotlin code uses it.
    • nums.withIndex() gives index and value together: for ((i, v) in nums.withIndex()) nums[i] = v + 1.

    The exercise

    The starting code should turn nums = [1, 2, 3, 4, 5] into [0, 3, 2, 5, 4]: add one to every even number and subtract one from every odd number.

    val nums = intArrayOf(1, 2, 3, 4, 5)
    
    for (num in nums) {
        if (num % 2 == 0) {
            num++
        } else {
            num--
        }
    }
    

    It fails to compile with two errors, one for num++ and one for num--. Both come from the loop variable being read-only. The fix has three parts:

    1. Loop over positions. Replace for (num in nums) with a loop over the indices, so that the loop variable, i, is a position.
    2. Read through the index. The even-or-odd test becomes nums[i] % 2 == 0, which asks about the element at position i.
    3. Write through the index. nums[i]++ and nums[i]-- change the element stored in the array.

    The declaration of nums stays as it is.

    val nums = intArrayOf(1, 2, 3, 4, 5)
    
    for (i in 0 until nums.size) {
        if (nums[i] % 2 == 0) {
            nums[i]++
        } else {
            nums[i]--
        }
    }
    

    Printing nums.joinToString() afterwards gives 0, 3, 2, 5, 4. Walking through it: 1 is odd, so it becomes 0; 2 is even, so it becomes 3; 3 becomes 2; 4 becomes 5; 5 becomes 4.

    Common mistakes

    • Modifying the loop variable. It never compiles in Kotlin, and even an allowed change would affect only a copy.
    • Going past the end. Using 0..nums.size includes nums.size itself, and nums[nums.size] throws ArrayIndexOutOfBoundsException. Use until or indices.
    • Trying the same on a read-only list. A List created with listOf has no way to assign by index: list[0] = 5 is rejected because List has no set operator. Use an array, or a MutableList from mutableListOf.
    • Thinking objects behave differently. For an array of objects, the loop variable holds a reference to the same object, so changing the object's properties through it does show up in the array. What you cannot do is replace the element itself. Replacing needs the index: names[i] = names[i].uppercase().

    Key takeaways

    • The variable in for (x in array) is a read-only copy of the current element.
    • To change elements, loop over array.indices (or 0 until array.size) and assign to array[i].
    • A val array can still have its elements changed; only the reference is fixed.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free