Start learning free

LESSON 153 OF 415 · LOOPING OVER ARRAY INDICES

Manipulating Array Elements in a For Loop in Swift

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:

var nums = [1, 2, 3]

// Increment items:
for num in nums {
    var num = num
    num += 1
}

// Print the array after:
for num in nums {
    print(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 += 1 for every num in nums?


Let's talk about copies:

Take a look at this code:

var number = 100

var number_copy = number

number_copy += 1

print(number_copy) // Output: 101
print(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 entity (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 += 1 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

var nums = [1, 2, 3]

for num in nums {
    var num = num
    num += 1
}

works exactly like this code:

var nums = [1, 2, 3]

var num = nums[0]
num += 1

num = nums[1]
num += 1

num = nums[2]
num += 1

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.

var nums = [1, 2, 3]

for num in nums {
    var num = num
    num += 1
    print(num)
}

// Print the array after:
for num in nums {
    print(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:

var nums = [1, 2, 3]

for index in 0..<nums.count {
    nums[index] += 1
}

This code works exactly like this one:

var nums = [1, 2, 3]

var index = 0
nums[index] += 1

index += 1
nums[index] += 1

index += 1
nums[index] += 1

In this way we directly manipulate the actual entries of the array (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

    SWIFT · LOOPING OVER ARRAY INDICES

    Buggy Code Manipulate Array in Swift, explained

    What the code should do

    The array nums holds [1, 2, 3, 4, 5]. Every even number must go up by one and every odd number must go down by one, so the array ends up as [0, 3, 2, 5, 4]. The code you start with looks reasonable, but it doesn't compile:

    var nums = [1, 2, 3, 4, 5]
    
    for num in nums {
        if num % 2 == 0 {
            num += 1
        } else {
            num -= 1
        }
    }
    

    The compiler stops with this error, once for num += 1 and once for num -= 1:

    error: left side of mutating operator isn't mutable: 'num' is a 'let' constant
    

    Why the loop variable can't change the array

    In for num in nums, Swift creates the name num anew on every iteration and gives it the value of the current element. That name is a let constant, so it can't be assigned to. This is why the compiler complains.

    Even if you could change it, the array would stay the same. Integers are value types, so num holds a copy of the element, not the element itself. The same thing happens here:

    var number = 100
    var numberCopy = number
    numberCopy += 1
    print(numberCopy) // 101
    print(number)     // 100
    

    Changing a copy never touches the original. You can see it by shadowing the loop constant with a mutable variable of the same name:

    var more = [1, 2, 3]
    for num in more {
        var num = num
        num += 1
        print(num)
    }
    print(more)
    

    This compiles, and it prints:

    2
    3
    4
    [1, 2, 3]
    

    Each num was incremented, but only the copies. The array is unchanged. So there are two problems in the starter code, and renaming or redeclaring the variable fixes only the first one.

    Fixing it: loop over the positions

    To change an element, you have to write to the array itself, through its position: nums[i] += 1. That means the loop should produce positions, not values. In Swift, 0..<nums.count is a half-open range that runs from 0 up to, but not including, nums.count, which is exactly the set of valid indices.

    1. Loop over the indices. Replace for num in nums with for i in 0..<nums.count. The name i is now an Int position.
    2. Read the element by position. The even-or-odd test becomes nums[i] % 2 == 0.
    3. Write to the element by position. Use nums[i] += 1 in the even branch and nums[i] -= 1 in the odd branch.
    4. Leave the declaration alone. nums must stay a var. A let array can't have its elements assigned.

    The solution in Swift

    var nums = [1, 2, 3, 4, 5]
    
    for i in 0..<nums.count {
        if nums[i] % 2 == 0 {
            nums[i] += 1
        } else {
            nums[i] -= 1
        }
    }
    

    Printing nums afterwards gives [0, 3, 2, 5, 4]. A few Swift details:

    • Arrays are value types. var b = nums makes an independent copy, so changing b doesn't change nums. Here the loop writes to nums directly, so there is nothing to copy back.
    • Odd numbers stay odd under %. The remainder takes the sign of the left operand, so -3 % 2 is -1, not 1. The test nums[i] % 2 == 0 is still correct for negative numbers, because it only asks whether the remainder is zero. Testing nums[i] % 2 == 1 would wrongly treat negative odd numbers as even.
    • nums.indices does the same job. for i in nums.indices is an equivalent way to write the range, and it is a good habit because it can't get the upper bound wrong.
    • An empty array is safe. 0..<0 is an empty range, so the body never runs.
    • enumerated() gives both. for (i, v) in nums.enumerated() hands you the position and a copy of the value. You still write through nums[i], never through v.

    Common mistakes

    • Shadowing the loop constant. Adding var num = num removes the compile error, but the array still isn't changed.
    • Writing 0...nums.count. The closed range includes nums.count, which is one past the last valid index, so the program crashes with an index-out-of-range error.
    • Changing the array's declaration. Declaring nums with let makes every nums[i] += 1 a compile error.
    • Swapping the branches. Even numbers go up and odd numbers go down. [2] becomes [3], and [1] becomes [0].

    Key takeaways

    • A for ... in loop variable is a let constant holding a copy of the element.
    • To change array elements, loop over the indices and assign through nums[i].
    • 0..<nums.count and nums.indices both produce exactly the valid positions.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free