052: Arrays and Indexed Collections
Outcomes
By the end of this lesson, you can:
- create arrays with literal syntax;
- read and replace elements by zero-based index;
- use
lengthand calculate the last valid index; - explain array mutation and constant bindings;
- add and remove end elements at an introductory level;
- traverse an array safely; and
- access values in a nested array.
Prerequisites and Retrieval
Retrieve your understanding of types, loops, functions, and callback basics before starting.
- What does
Array.isArray(value)answer? - Why can an array bound to
conststill change internally? - What loop condition safely traverses all array indexes?
Lesson 053 examines specific mutation and copying methods in depth. This lesson establishes the collection model you need before those methods become a larger topic.
Terms
- array: Ordered collection of values with length and index access. — Source: MDN: Array
- element: A value stored at some position within an array. — Source: MDN: Array
- index: Zero-based position of an element inside an array. — Source: MDN: Array
- array literal: [a, b, c] notation creating an array directly. — Source: MDN: Array
- length: Property holding the element count; writable with surprising effects. — Source: MDN: Array.length
- mutation: Modifying the array in place (push, splice, sort). — Source: MDN: Glossary — Mutable
- reference: Copyable pointer to the same underlying array/object. — Source: MDN: Data structures
- dense array: Array containing a value at every index through length-1. — Source: MDN: Array
- sparse array: Array with holes — missing indices between elements. — Source: MDN: Sparse arrays
- nested array: An array stored as an element of another array. — Source: MDN: Array
- Array (official): "An array is an ordered list of values, indexed from 0." — Source: MDN: Array
- Mutation vs Copy: "Mutation changes the original object; copying creates a new object with the same data." — Source: MDN: Mutating vs non-mutating methods
Beginner Explanation and Mental Model
When related values need to stay in order, an array gives you one collection with a numbered position for each value. JavaScript starts those numeric indexes at zero:
const subjects = ["HTML", "CSS", "JavaScript"];
// indexes: 0 1 2
Think of each index as an offset from the beginning of the array, not as a human-facing position number. Bracket notation reads the value at a particular index:
console.log(subjects[0]); // HTML
console.log(subjects[2]); // JavaScript
Why zero? The first value is zero positions away from the start, which is the convention JavaScript uses. When you already know an index, do not subtract one before accessing it. The subtraction belongs when you calculate the final valid index from length:
const lastIndex = subjects.length - 1;
console.log(subjects[lastIndex]); // JavaScript
For a dense array, length is the number of elements. Reading an index outside the current range produces undefined; JavaScript does not throw an error for that read:
console.log(subjects[99]); // undefined
That behavior can conceal an off-by-one error. Treat undefined as a result to investigate, not as proof that the requested data is genuinely absent. Check the index and the array's contents when debugging.
Creating and changing arrays
For ordinary arrays, use literal syntax:
const tasks = ["Plan", "Code", "Test"];
const emptyList = [];
The literal form is easy to read and avoids a common constructor trap: [3] is an array containing one value, 3, while Array(3) creates an array with length 3 and empty slots.
Replace an existing element by assigning through its index:
tasks[1] = "Build";
To add or remove values at the end, use push() and pop():
tasks.push("Review");
const removedTask = tasks.pop();
Both operations mutate the existing array. push() returns the new length. pop() returns the removed value, or undefined when the array is empty. Front operations and copying methods are compared fully in the next lesson; for now, focus on what changes and what each method returns.
The array's binding can still be const because the binding continues to identify the same array. const prevents reassignment of the binding; it does not freeze the array's contents:
const skills = ["HTML"];
skills.push("CSS"); // mutation is allowed
// skills = ["JavaScript"]; // reassignment is not allowed
References
Assigning an array to another variable does not clone its contents. It creates another binding to the same array:
const firstList = ["A"];
const secondName = firstList;
secondName.push("B");
console.log(firstList); // ["A", "B"]
There is one array here and two bindings pointing to it. Decide deliberately whether a function or caller should mutate that shared array, because an unexpected in-place change can surprise every other user of the reference.
Worked Example: Task List
const tasks = ["Read lesson", "Write notes", "Practice code"];
console.log("Task count:", tasks.length);
console.log("First task:", tasks[0]);
console.log("Last task:", tasks[tasks.length - 1]);
tasks[1] = "Summarize notes";
tasks.push("Review mistakes");
console.log("Updated tasks:");
for (let index = 0; index < tasks.length; index += 1) {
const position = index + 1;
console.log(`${position}. ${tasks[index]}`);
}
const completedTask = tasks.pop();
console.log("Removed from end:", completedTask);
console.log("Remaining count:", tasks.length);
Expected output:
Task count: 3
First task: Read lesson
Last task: Practice code
Updated tasks:
1. Read lesson
2. Summarize notes
3. Practice code
4. Review mistakes
Removed from end: Review mistakes
Remaining count: 3
The loop uses the zero-based index for array access, while position adds one only for the number shown to a person. That distinction is deliberate: human-facing numbering starts at 1, but the data structure still starts at 0. push() changes the length from 3 to 4, and pop() brings it back to 3. const tasks remains valid because the binding itself is never replaced.
Intermediate Example: Product Stock Summary
An array can contain any values, including other arrays. Objects are covered later, so each product here is represented by [name, price, stock]:
const products = [
["Notebook", 80, 5],
["Pen", 20, 0],
["Folder", 50, 3],
];
let inventoryValue = 0;
for (const product of products) {
const name = product[0];
const price = product[1];
const stock = product[2];
const productValue = price * stock;
inventoryValue += productValue;
console.log(`${name}: ${stock} in stock, value ${productValue}`);
}
console.log("Inventory value:", inventoryValue);
console.log("Second product name:", products[1][0]);
Expected output:
Notebook: 5 in stock, value 400
Pen: 0 in stock, value 0
Folder: 3 in stock, value 150
Inventory value: 550
Second product name: Pen
products[1] selects the second inner array. The following [0] selects the first value in that row, which is the product name. This is the basic rule for nested arrays: use one index for each level. Nested arrays fit grids and compact structures, but positional fields become difficult to remember as a structure grows. Later, objects will let you write clearer names such as product.price.
Optional Advanced Extension: Copying the Outer Array
Calling slice() with no arguments creates a shallow copy of the outer array:
const original = ["HTML", "CSS"];
const copy = original.slice();
copy.push("JavaScript");
console.log(original); // ["HTML", "CSS"]
console.log(copy); // ["HTML", "CSS", "JavaScript"]
console.log(original === copy); // false
The word "shallow" matters as soon as nested arrays are involved. The outer array is new, but references to inner arrays are shared. This is not a deep clone. Lesson 053 uses slice mainly for selecting ranges.
Mistakes and Debugging
- Starting at index 1: the first element is index 0.
- Using
array[array.length]for the last item: that position is immediately after the last; uselength - 1. - Using
<= array.lengthin traversal: use< array.length. - Confusing position and index: human item 1 has index 0.
- Reassigning a constant array: mutate deliberately or create a new constant with a copy.
- Expecting assignment to clone: two bindings then share one array.
- Creating gaps: assigning
items[10]to a short array creates empty slots. Prefer normal append operations. - Using
delete items[index]: it leaves an empty slot and does not reduce length. Use array methods such assplicetomorrow. - Mixing unrelated types: JavaScript permits it, but homogeneous collections are easier to process.
When debugging, inspect the array itself, its length, and the indexes you are using:
console.log(items);
console.log(items.length);
Some consoles show a later view of a referenced array rather than the state at the moment of logging. Logging items.slice() records a shallow snapshot of the outer array, which can make the earlier state easier to inspect.
Best Practices
- Prefer
constfor an array binding and literal[]syntax. - Use plural names for collections and singular names for current elements.
- Keep arrays dense and usually hold values with a consistent meaning.
- Calculate last index as
length - 1; handle empty arrays before using it. - Use
for...ofwhen only values matter and an indexed loop when the index matters. - Do not mutate arrays unexpectedly inside reusable functions.
- Capture return values from removing operations when they matter.
- Choose objects later when nested positions become cryptic.
Tiered Exercises
Core
Create an array of four colors. Log its length, first value, third value, and last value. Replace the second color, append one color, then print every color with its index.
Practice
Manage ["plan", "code", "test"]: replace "code" with "build", append "deploy", remove and store the last task, and print the remaining list and removed task.
Professional Extension
Create a 3 by 3 number grid as nested arrays. Use nested loops to print every coordinate and value, then calculate the total of all values.
Complete Solutions
const colors = ["red", "green", "blue", "yellow"];
console.log(colors.length); // 4
console.log(colors[0]); // red
console.log(colors[2]); // blue
console.log(colors[colors.length - 1]); // yellow
colors[1] = "purple";
colors.push("orange");
for (let index = 0; index < colors.length; index += 1) {
console.log(index, colors[index]);
}
const tasks = ["plan", "code", "test"];
tasks[1] = "build";
tasks.push("deploy");
const removedTask = tasks.pop();
console.log(tasks); // ["plan", "build", "test"]
console.log(removedTask); // deploy
const grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
let total = 0;
for (let row = 0; row < grid.length; row += 1) {
for (let column = 0; column < grid[row].length; column += 1) {
const value = grid[row][column];
console.log(`(${row}, ${column}) = ${value}`);
total += value;
}
}
console.log("Total:", total); // 45
Recap and Exit Questions
Arrays are ordered collections whose indexes begin at zero. length describes their current size and therefore the range from which valid indexes are calculated. Bracket notation reads or replaces elements, while mutation changes the existing collection. A const binding does not make an array's contents immutable, and a nested array requires one index for each level you need to enter.
- What is the last valid index of an array with length 5?
- What happens when an out-of-range index is read?
- How do reassignment and mutation differ?
- Why does assigning an array to another variable not clone it?
- How do you access the second value in the first nested row?
Official References
References checked 2026-08-24.
