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.
- Replace
for...ofwith an index loop. Declarelet i = 0, continue whilei < nums.length, and add one after each iteration withi++. - Read the element through its index. The test becomes
nums[i] % 2 == 0. - Write back through the index.
nums[i]++adds one to the element stored in the array, andnums[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 fornums[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, andnums.forEach((x, i) => { nums[i] = x * 2; })does the same with a callback. In both, the write still goes throughnums[i]. - The test with
%.num % 2 == 0is true for even numbers. For negative numbers, the remainder of an odd number is-1, not1, 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
numis lost at the end of the iteration. - Looping to
i <= nums.length. Indexnums.lengthis past the end. Reading it givesundefined, andnums[i]++there would create a new element holdingNaN. - 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, soi + 1would join text instead of adding. Use a numericlet i = 0loop. - Printing inside the loop and trusting it. Printing
numafternum++shows the incremented copy and makes the loop look right. Printnumsafter the loop to see what really changed.
Key takeaways
- In a
for...ofloop, 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.