JavaScript arrays are the first data structure you reach for and the one you misuse most often. This crash course shows what an array actually is at runtime, how to read items out of it without tripping over undefined, and the handful of traps that catch almost every beginner.
What is a JavaScript array, really?
An array is an ordered list of values with a numeric index and a length. You almost always create one with a literal: square brackets, values, commas. Inside the brackets you can put anything at all — strings, numbers, objects, even other arrays — and JavaScript will not complain. That permissiveness is convenient for grouping things and dangerous for the rest of this module, because the array itself promises nothing about the shape of what it holds.
The important detail is what happens under the hood. An array in JavaScript is a special kind of object. Its keys are the string versions of the indices, plus a length property. The array methods you will meet in this module are not built into the array itself — push, map and filter live on Array.prototype, the shared prototype every array inherits from. That is why typeof reports object, and why you can technically hang your own properties on an array without an error.
const posts = [
{ title: 'Closures', minutes: 12 },
{ title: 'Arrays', minutes: 9 },
{ title: 'Promises', minutes: 15 },
];
console.log(posts.length); // 3
console.log(posts[0].title); // Closures
console.log(posts.at(-1).title); // Promises
console.log(posts[3]); // undefined
console.log(posts[-1]); // undefined
Those two lines are the ones people miss when an array bug looks like a data bug. Reading past the end of an array does not throw an error; it quietly hands you undefined. And posts[-1] is also undefined, because a negative index is not a JavaScript concept for bracket access — the property named -1 simply does not exist on the object. Array.prototype.at() was added so you can count from the end without doing the arithmetic yourself, and it is the clearest way to grab the last item.
How do you read every item out of an array?
Reading one item is trivial. Reading all of them is where your style choice shows. for...of walks the values in order and hands each one to a variable you declare with const or let. It is the loop to reach for when you want to look at each value and do something with it, because it has an obvious place to put break and continue — something the callback-based array methods famously do not give you.
const steps = ['plan', 'write', 'edit', 'publish'];
for (const step of steps) {
console.log(`> ${step}`);
}
console.log(steps.join(' -> '));
That prints four lines beginning with > and then the joined list plan -> write -> edit -> publish. join converts an array into a string using a separator you choose, which is the quickest way to render a list into a sentence, a breadcrumb trail or a sentence of comma-separated names.
Why does typeof say object for an array?
Because it genuinely is one. The only reliable way to test for an array is Array.isArray(). It returns true for real arrays and false for array-like objects such as the arguments object or a NodeList — things with a length and indexed properties that are not arrays and therefore have no array methods.
const tags = ['js', 'web', 'node'];
console.log(typeof tags); // object
console.log(Array.isArray(tags)); // true
console.log(Array.isArray({ 0: 'a', length: 1 })); // false
console.log(Object.getPrototypeOf(tags) === Array.prototype); // true
Is length just a number you read?
length is special: it is writable, and assigning to it reshapes the array immediately. Setting it to a smaller number deletes everything beyond it. Setting it to a larger number creates holes — empty slots that read as undefined but still occupy a position and still count towards the length.
const queue = ['a', 'b', 'c'];
queue.length = 1;
console.log(queue); // [ 'a' ]
console.log(queue[2]); // undefined
const sparse = ['a', 'b'];
delete sparse[1];
console.log(sparse.length); // 2
console.log(sparse[1]); // undefined
console.log(JSON.stringify(sparse)); // ["a",null]
Note the last line. delete leaves a hole instead of closing the gap, so the array still reports a length of 2 and JSON.stringify turns the hole into null. Holes are nearly always an accident rather than a decision, which is why the rest of this module leans on filter and slice instead of delete.
Which array traps catch beginners most often?
Three, in rough order of how often they bite. First, assuming indices are contiguous. They are not guaranteed to be, so code that reads items[0] through items[items.length - 1] can meet undefined when an array contains a hole. Second, treating an array like a named-key object. It works, and then for...of silently ignores your named keys, because for...of only walks the indexed values. Third, forgetting that arrays are mutable references: handing an array to a function gives the function a pointer, so a function that pushes into it changes the array you passed in. That third trap explains a lot of confusing bugs, and it is the same rule that governs objects, so the objects module picks it up in full.
How do you build an array when you have no literal?
Array.of() takes its arguments as the items. Array.from() takes an iterable — another array, a Set, a string — and optionally a mapping function, so you can transform while you build. Array.from() also accepts array-like objects that merely have a length, which is how you create a fixed-size array of placeholders without a loop.
const scores = Array.of(1, 2, 3);
console.log(scores); // [ 1, 2, 3 ]
const doubled = Array.from([1, 2, 3], (n) => n * 2);
console.log(doubled); // [ 2, 4, 6 ]
console.log(Array.from({ length: 3 }, (_, i) => i)); // [ 0, 1, 2 ]
What is worth memorising before the next lesson?
Here is the deliberately short version. Each row gets its own lesson in this module.
| Expression | What it does | Watch out |
|---|---|---|
arr.length | Number of indexed slots | Writable; assigning truncates or extends the array |
arr[i] | Reads the item at index i | undefined past the end; negative indices do not work |
arr.at(i) | Reads by index, negative values count from the end | ES2022, so older browsers will not have it |
Array.isArray(x) | The only reliable array test | typeof will not do it |
for...of | Walks the values in order | Skips holes entirely |
Try it yourself: make a file called reading-list.js and put in an array of five objects, each with a title and a minutes number. Print the title of the first item with list[0].title, the title of the last with list.at(-1).title, and the total minutes with a for...of loop that adds to a running total. Then push a sixth item on and print the new length. Finally set list.length = 5 and print the length again — the sixth item should vanish without you deleting anything.