What the code is supposed to do
The program has an array ages with four numbers and should print its last element, 21. When you run the starting code, it prints undefined instead:
let ages: number[] = [14, 32, 50, 21];
console.log(ages[ages.length]);
No error appears, no red text, and the program finishes normally. That silence is what makes this bug easy to miss.
What goes wrong
Array indices start at 0. An array with four elements stores them at indices 0, 1, 2 and 3:
| Index | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| Value | 14 | 32 | 50 | 21 |
The property ages.length counts the elements, so it is 4. The code therefore reads ages[4], one position past the last element. Nothing is stored there.
In many languages that mistake stops the program with an exception. JavaScript, and so TypeScript, behaves differently: reading a missing index gives the value undefined and the program carries on. The line prints undefined, which looks like a normal result.
Why the compiler doesn't help
The type annotation number[] says every element is a number, and nothing about how many elements there are. By default, the compiler types ages[ages.length] as a plain number, so it reports no error even though the value at run time is undefined. Types are erased when the code is compiled to JavaScript, so they can't check indices while the program runs. The index is your responsibility.
Fixing it step by step
- Find the valid range. For
agesthe indices are0to3, which is0toages.length - 1. - Pick the right index. The last element is the one at the top of that range,
ages.length - 1. - Subtract 1 inside the brackets. The expression
ages.length - 1is evaluated first and gives3, so the access becomesages[3].
The fixed code in TypeScript
let ages: number[] = [14, 32, 50, 21];
console.log(ages[ages.length - 1]);
This prints 21. Writing ages[3] would print the same here, but ages[ages.length - 1] keeps working when the array grows or shrinks.
Other ways to go out of bounds
An index is invalid when it is below 0 or above length - 1. All of these give undefined:
let ages: number[] = [14, 32, 50, 21];
console.log(ages[4]); // undefined
console.log(ages[30]); // undefined
console.log(ages[-1]); // undefined
The case ages[-1] catches people who expect it to mean "the last item", as it does in some other languages. In TypeScript it is just a lookup for something that isn't there. If you want counting from the end, ages.at(-1) returns 21 here, and the lesson's length - 1 form does the same job.
The same thing happens in loops. A loop written as for (let i = 0; i <= ages.length; i++) uses <=, so it runs once more than it should and its last pass reads ages[4]. The correct condition is i < ages.length.
Why undefined is dangerous
The problem often shows up later than the bad read. The value undefined spreads through the program without an error until something uses it:
let ages: number[] = [14, 32, 50, 21];
let last: number = ages[ages.length];
console.log(last + 1); // NaN
Here the arithmetic gives NaN ("not a number") instead of stopping. Reading a property such as last.toFixed(1) would throw a TypeError, but far from the line that caused it. Finding the cause is easier if you remember to check the index where the value is read.
Common mistakes
- Using
lengthas the last index. The last index islength - 1. - Subtracting too much.
ages[ages.length - 2]is the second to last element, which is50. - Empty arrays. For
[],length - 1is-1, so even the fixed line printsundefined. Checkages.length > 0first when the array may be empty. - Trusting the absence of an error. A program that prints
undefinedran fine as far as JavaScript is concerned, so check the output, not only whether the program crashed.
Checking your fix
A quick way to test an index is to print the whole array next to it, so you can count positions yourself: console.log(ages, ages.length) shows the four values and the number 4.
Edit the line in the editor above and run it. A correct fix prints 21. If it still prints undefined, the index is still outside the range 0 to 3.