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 types, loops, functions, and callback basics.
- What does
Array.isArray(value)answer? - Why can an array bound to
conststill change internally? - What loop condition safely traverses all array indexes?
053 studies specific mutation and copying methods in depth. Today builds the collection mental model first.
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
An array is an ordered row of labeled positions. The labels are numeric indexes starting at zero:
const subjects = ["HTML", "CSS", "JavaScript"];
// indexes: 0 1 2
Use bracket notation to read an element:
console.log(subjects[0]); // HTML
console.log(subjects[2]); // JavaScript
Why zero? JavaScript follows a common programming convention in which an index is an offset from the start. The first value is zero positions away. Do not subtract one when accessing a known index. Do subtract one from length to calculate the final valid index:
const lastIndex = subjects.length - 1;
console.log(subjects[lastIndex]); // JavaScript
For a dense array, length is the number of elements. Reading an out-of-range index returns undefined; it does not throw:
console.log(subjects[99]); // undefined
That can hide an off-by-one bug, so verify the index rather than treating undefined as proof that the data is absent.
Creating and changing arrays
Prefer array literal syntax:
const tasks = ["Plan", "Code", "Test"];
const emptyList = [];
Literal syntax avoids the confusing difference between [3] (one element equal to 3) and Array(3) (length 3 with empty slots).
Replace an element by assignment:
tasks[1] = "Build";
Add and remove at the end:
tasks.push("Review");
const removedTask = tasks.pop();
Both methods mutate the existing array. push() returns the new length; pop() returns the removed value or undefined for an empty array. Their full comparison with front operations and copying methods comes tomorrow.
The binding can remain const because it still identifies the same array:
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 it:
const firstList = ["A"];
const secondName = firstList;
secondName.push("B");
console.log(firstList); // ["A", "B"]
Both bindings refer to one array. Make mutation choices explicit so callers are not surprised.
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 display position is index + 1 for human-friendly numbering, but access still uses the actual zero-based index. After push, length changes from 3 to 4. After pop, it returns to 3. const tasks remains valid throughout because the binding is never replaced.
Intermediate Example: Product Stock Summary
Arrays may hold any values, including small product arrays. Objects are taught later, so represent each product as [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 nested array; [0] then selects its first element. Nested arrays are useful for grids and compact structures, but positional meaning becomes hard to remember. Objects will later provide names such as product.price.
Optional Advanced Extension: Copying the Outer Array
slice() with no arguments creates a shallow outer-array copy:
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
"Shallow" matters for nested arrays: the outer array is new, but inner-array references are shared. Do not claim this is a deep clone. 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.
Inspect arrays with console.log(items), items.length, and explicit indexes. Some consoles display a later view of a referenced array; logging items.slice() captures a shallow snapshot of the outer array for clearer debugging.
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, zero-indexed collections. length describes their index range, bracket notation accesses elements, and mutation changes the existing collection. A constant binding does not make array contents immutable, and nested arrays require one index per level.
- 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.
