Start learning free

LESSON 148 OF 348 · LOOPING OVER ARRAY INDICES

Changing Array Elements in a TypeScript For Loop: Fixing the Bug

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:

let nums = [1, 2, 3];

for (let num of nums) {
  num++;
}
  
console.log(nums);

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:

let number = 100;

let number_copy = number;

number_copy++;

console.log(number_copy); // Output: 101
console.log(number); // Output: 100

Although we wrote let 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

let nums = [1, 2, 3];

for (let num of nums) {
  num++;
}

works exactly like this code:

let nums = [1, 2, 3];

let 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.

let nums = [1, 2, 3];

for (let num of nums) {
  num++;
  console.log(num);
}

console.log(nums);

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:

let nums = [1, 2, 3];

for (let index = 0; index < nums.length; index++) {
  nums[index]++;
}
  
console.log(nums); // Output: [2, 3, 4]

This code works exactly like this one:

let nums = [1, 2, 3];

let index = 0;
nums[index]++;

index++;
nums[index]++;

index++;
nums[index]++;

console.log(nums);

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

    TYPESCRIPT · LOOPING OVER ARRAY INDICES

    Buggy Code Manipulate Array in TypeScript, explained

    What the code is supposed to do

    The starting code has an array nums holding 1, 2, 3, 4, 5. The loop is meant to add one to every even number and subtract one from every odd number, so that nums ends up as 0, 3, 2, 5, 4. Instead, after the loop nums still holds 1, 2, 3, 4, 5:

    let nums: number[] = [1, 2, 3, 4, 5];
    
    for (let num of nums) {
        if (num % 2 == 0) {
            num++;
        } else {
            num--;
        }
    }
    

    The code compiles and runs without any error. It just does not change the array, which makes this bug easy to miss.

    Why nothing changes: the loop variable is a copy

    In for (let num of nums), the variable num is a new variable. On each iteration TypeScript copies the current element's value into it. Changing num changes only that copy, and the array never hears about it.

    The same thing happens with plain variables:

    let number = 100;
    let numberCopy = number;
    numberCopy++;
    
    console.log(numberCopy); // 101
    console.log(number);     // 100
    

    Writing let numberCopy = number creates a second variable that starts with the same value. After that the two are independent. A for...of loop does exactly this once per element, so the loop above is the same as writing let num = nums[0]; num++;, then num = nums[1]; num++;, and so on. Each num++ touches only num.

    The loop variable is a copy only for primitive values such as numbers, strings and booleans. If the elements are objects, the variable holds a reference to the same object, and changing a property through it does change the array's contents:

    const objs = [{ n: 1 }, { n: 2 }];
    for (const o of objs) {
        o.n++;
    }
    console.log(objs); // [ { n: 2 }, { n: 3 } ]
    

    Even then, assigning a new object to the loop variable (o = { n: 0 }) would replace only the copy of the reference, not the array slot. Here the elements are numbers, so the copy rule applies in full.

    The fix: loop over the indices

    To change an element, you have to write to its slot in the array, such as nums[0]. That means the loop needs to know the position, so it loops over the indices instead of the values.

    1. Replace for...of with an index loop. Declare let i = 0, continue while i < nums.length, and add one after each iteration with i++.
    2. Read the element through its index. The test becomes nums[i] % 2 == 0.
    3. Write back through the index. nums[i]++ adds one to the element stored in the array, and nums[i]-- subtracts one.

    The fixed code:

    let nums: number[] = [1, 2, 3, 4, 5];
    
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] % 2 == 0) {
            nums[i]++;
        } else {
            nums[i]--;
        }
    }
    

    After the loop, console.log(nums) prints:

    [ 0, 3, 2, 5, 4 ]
    

    The element at index 0 is 1, which is odd, so it becomes 0. The element at index 1 is 2, which is even, so it becomes 3. The rest follow the same rule.

    Details worth knowing

    • nums[i]++ is a read and a write. It reads the element, adds one and stores the result back in the same slot. It is shorthand for nums[i] = nums[i] + 1.
    • The annotation number[] changes nothing here. TypeScript types are erased when the code is compiled to JavaScript. The bug is a run-time behaviour of the loop and the type checker has no complaint about it.
    • The array is changed in place. Any other variable that points to the same array sees the new values too. If you need the original as well, build a new array with nums.map(x => x + 1) or copy it first with [...nums].
    • Another way to get the index. for (const [i, v] of nums.entries()) gives you the index and the value together, and nums.forEach((x, i) => { nums[i] = x * 2; }) does the same with a callback. In both, the write still goes through nums[i].
    • The test with %. num % 2 == 0 is true for even numbers. For negative numbers, the remainder of an odd number is -1, not 1, so test for even (== 0) rather than for odd (== 1) if negatives are possible.

    Common mistakes

    • Changing the loop variable and expecting the array to follow. This is the bug of the lesson. Any assignment to num is lost at the end of the iteration.
    • Looping to i <= nums.length. Index nums.length is past the end. Reading it gives undefined, and nums[i]++ there would create a new element holding NaN.
    • Starting at the wrong index. The indices of an array run from 0 to nums.length - 1. A loop that starts at 1 skips the first element.
    • Using for...in. It gives the indices, but as strings, so i + 1 would join text instead of adding. Use a numeric let i = 0 loop.
    • Printing inside the loop and trusting it. Printing num after num++ shows the incremented copy and makes the loop look right. Print nums after the loop to see what really changed.

    Key takeaways

    • In a for...of loop, the loop variable holds a copy of each primitive element.
    • To change the elements of an array, loop over its indices and assign through nums[i].
    • When a loop seems to do nothing, print the array itself after the loop.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free