📦 Array Creation and Access
Arrays are the workhorse data structure of JavaScript — ordered, dynamic lists that hold anything. This lesson shows you every practical way to build one, how to reach in and read or change elements, and the subtle "empty slot" trap that catches even experienced developers.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Create arrays with literals,
Array(),Array.of(),Array.from(), the spread operator, andfill() - Read elements by index, with
at()for negative positions, and by destructuring - Explain the difference between sparse and dense arrays and why holes cause bugs
- Build and traverse multi-dimensional arrays and generate numeric ranges
- Reliably test whether a value is an array with
Array.isArray()
Estimated Time: 30–40 minutes • Difficulty: Intermediate
Hands-on: Build a deck of 52 cards and a blank tic-tac-toe grid using array-generation techniques.
In This Lesson
What Is an Array?
An array is an ordered collection of values stored under a single name. Picture a row of numbered lockers: each locker holds one value, and the number on its door — the index — is how you find it again. JavaScript numbers those lockers starting at 0, so the first element lives at index 0, the second at index 1, and so on.
fruits[2] returns "Cherry".Under the hood, a JavaScript array is a special kind of object whose keys are the numeric indices. That design gives arrays some traits that differ from arrays in lower-level languages like C or Java:
- Mixed types allowed — one array can hold numbers, strings, objects, even other arrays.
- Dynamically sized — they grow and shrink automatically; you never declare a fixed capacity.
- Zero-based indexing — the first element is always at index
0.
📖 Key Terms
Element: a single value stored in the array.
Index: the zero-based position number used to reach an element.
Length: the count of slots, always one more than the highest index in a dense array.
Ways to Create Arrays
There is rarely just one way to do anything in JavaScript, and array creation is no exception. Each technique has a sweet spot.
Array literal — your default
Square brackets are the clearest, most common way to write an array. Reach for this 90% of the time.
// Empty array
const emptyArray = [];
// With elements
const fruits = ["Apple", "Banana", "Cherry"];
// Mixed types are fine
const mixed = [42, "hello", true, { name: "Object" }, [1, 2, 3]];
console.log(fruits.length); // 3
console.log(mixed.length); // 5
The Array() constructor — and its trap
The constructor works, but a single numeric argument is interpreted as a length, not a value. This is a classic source of confusion.
const colors = new Array("Red", "Green", "Blue"); // ["Red", "Green", "Blue"]
// TRAP: one number means "make an array this long", not "put 5 in it"
const sparse = new Array(5);
console.log(sparse.length); // 5 (five EMPTY slots, no values)
// Array.of() fixes the quirk — it always treats args as values
const single = Array.of(5);
console.log(single); // [5]
⚠️ Watch out
new Array(5) gives you five holes, while Array.of(5) gives you the one-element array [5]. When in doubt, prefer literals or Array.of() — they never surprise you.
Array.from() — from anything iterable
Array.from() turns strings, Sets, Maps, NodeLists, and array-like objects into real arrays. An optional second argument maps each element as it is built.
// From a string
Array.from("Hello"); // ["H", "e", "l", "l", "o"]
// From a Set — a handy way to de-duplicate
Array.from(new Set([1, 2, 2, 3, 1, 4])); // [1, 2, 3, 4]
// With a mapping function as the 2nd argument
Array.from([1, 2, 3], x => x * 2); // [2, 4, 6]
// Generate a range 1..5 from a fake "array-like"
Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]
The spread operator — copy and combine
The three-dot spread (...) expands any iterable into a new array. It is the modern way to make a shallow copy or merge lists.
const original = [1, 2, 3];
const copy = [...original]; // [1, 2, 3] — a new, independent array
const a = [1, 2, 3];
const b = [4, 5, 6];
const combined = [...a, ...b]; // [1, 2, 3, 4, 5, 6]
const extended = [0, ...a, 4]; // [0, 1, 2, 3, 4]
const unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]
const letters = [..."hello"]; // ["h", "e", "l", "l", "o"]
⚠️ "Shallow" copy, not "deep"
Both [...arr] and arr.slice() copy the top level only. Nested objects and arrays are still shared references — change one and you change both. For a fully independent clone use structuredClone(arr).
Accessing Elements
Bracket notation and at()
Bracket notation is the fundamental way in. Reading past the end returns undefined rather than throwing an error. The modern at() method accepts negative indices that count from the end.
const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];
console.log(fruits[0]); // "Apple"
console.log(fruits[2]); // "Cherry"
console.log(fruits[fruits.length - 1]); // "Elderberry" (the old way to get the last item)
// at() reads from the end with negative indices (ES2022)
console.log(fruits.at(-1)); // "Elderberry"
console.log(fruits.at(-2)); // "Date"
console.log(fruits[10]); // undefined — no error thrown
Destructuring assignment
Array destructuring unpacks values into named variables in one line. It supports skipping, defaults, a rest pattern, and even a slick variable swap.
const colors = ["Red", "Green", "Blue", "Yellow", "Purple"];
const [first, second] = colors; // first = "Red", second = "Green"
const [primary, , tertiary] = colors; // skip the middle: primary = "Red", tertiary = "Blue"
const [head, ...tail] = colors; // head = "Red", tail = ["Green","Blue","Yellow","Purple"]
const [a, b, c = "Default"] = ["One", "Two"]; // c falls back to "Default"
let x = 1, y = 2;
[x, y] = [y, x]; // swap without a temp variable → x = 2, y = 1
Nested access
Multi-dimensional data is just arrays inside arrays, reached with chained brackets or nested destructuring.
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
console.log(matrix[1][2]); // 6 → row 1, column 2
const [[a1, a2]] = [[1, 2], [3, 4]];
console.log(a1, a2); // 1 2
Sparse vs. Dense Arrays
A dense array has a value at every index from 0 to length - 1. A sparse array has "holes" — indices that were never assigned. Holes are not the same as undefined values, and they make some methods behave inconsistently.
const dense = [1, 2, 3, 4, 5]; // every slot filled — good
// Several ways to accidentally (or deliberately) make holes:
const s1 = new Array(3); // [ <3 empty items> ]
const s2 = [1, , 3]; // hole at index 1
const arr = [1, 2, 3, 4];
delete arr[1]; // delete leaves a hole: [1, <empty>, 3, 4]
// Holes are skipped by some iterators but not others:
const sparse = [1, , 3];
console.log(1 in sparse); // false — index 1 does not exist
sparse.forEach(x => console.log(x)); // logs 1, then 3 — the hole is SKIPPED
for (const item of sparse) console.log(item); // logs 1, undefined, 3 — hole is VISITED
✅ Rule of thumb
Keep arrays dense. Use arr.length = 0 or arr.splice(i, 1) to remove items rather than delete arr[i], which leaves a hole. To create a pre-filled array, use Array(n).fill(0) instead of the bare Array(n).
Multi-Dimensional Arrays & Ranges
JavaScript has no built-in 2D array type — you compose one from arrays of arrays. Two generation patterns cover almost every need: ranges and grids.
Generating a numeric range
// 1 through 10
const oneToTen = Array.from({ length: 10 }, (_, i) => i + 1);
// A reusable range helper
function range(start, end) {
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
}
console.log(range(5, 10)); // [5, 6, 7, 8, 9, 10]
Building a grid — safely
A common beginner bug is Array(rows).fill(Array(cols).fill(0)), which shares one inner row across every row. Use a mapping function so each row is a distinct array.
function createGrid(rows, cols, value = 0) {
return Array.from({ length: rows }, () =>
Array.from({ length: cols }, () => value)
);
}
const grid = createGrid(3, 4);
grid[0][0] = "X";
console.log(grid[1][0]); // 0 — other rows are untouched (independent arrays)
⚠️ The fill-with-objects trap
new Array(3).fill({}) puts the same object into all three slots. Editing arr[0] changes arr[1] and arr[2] too. Whenever each slot needs its own object or array, build it with Array.from(..., () => ({})).
Is It Really an Array?
Because arrays are objects, typeof is useless for detecting them — it just says "object". Use Array.isArray(), which is reliable even across browser frames.
const arr = [1, 2, 3];
console.log(typeof arr); // "object" — unhelpful
console.log(Array.isArray(arr)); // true ✅ recommended
console.log(arr instanceof Array); // true, but breaks across iframes/realms
Hands-on Exercise
🏋️ Build a Deck and a Game Board
Objective: Practice array generation by producing two structures programmatically instead of typing them out.
Instructions:
- Create an array of the four suits and an array of the 13 ranks (
"2"…"10","J","Q","K","A"). - Use
flatMap()(or nestedmap()) to produce all 52"Rank of Suit"cards. Confirmdeck.length === 52. - Build an empty 3×3 tic-tac-toe board with
Array.from()so every row is independent. - Place an
"X"at the center and verify no other cell changed.
💡 Hint
Build ranks with ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]. For the deck, suits.flatMap(suit => ranks.map(rank => `${rank} of ${suit}`)). For the board, remember the grid pattern from Section 5 — a bare fill would share rows.
✅ Example solution
const suits = ["Clubs", "Diamonds", "Hearts", "Spades"];
const ranks = ["2","3","4","5","6","7","8","9","10","J","Q","K","A"];
const deck = suits.flatMap(suit => ranks.map(rank => `${rank} of ${suit}`));
console.log(deck.length); // 52
console.log(deck[0]); // "2 of Clubs"
const board = Array.from({ length: 3 }, () => Array.from({ length: 3 }, () => ""));
board[1][1] = "X";
console.log(board[0][1]); // "" — center placement did not leak into other rows
console.log(board);
// [ ["", "", ""],
// ["", "X", ""],
// ["", "", ""] ]
🎯 Quick Quiz
Question 1: What is the value and length of new Array(3)?
Question 2: Which expression returns the last element of arr most cleanly?
Question 3: How should you reliably check that a value is an array?
Best Practices
✅ Do
- Use array literals
[]for everyday creation. - De-duplicate with
[...new Set(arr)]. - Generate ranges and grids with
Array.from({ length: n }, ...). - Test with
Array.isArray(); read the tail withat(-1).
⚠️ Don't
- Don't call
new Array(n)expecting a value — it makes holes. - Don't
fill()with a shared object or array when each slot must be distinct. - Don't use
delete arr[i]to remove items — it leaves a hole; usesplice(). - Don't rely on a shallow copy (
[...arr]) to isolate nested data.
Summary & Quiz
🎉 Key Takeaways
- Arrays are ordered, dynamically sized, zero-indexed collections that can hold mixed types.
- Create them with literals,
Array.of(),Array.from(), spread, orfill()— and beware thenew Array(n)length quirk. - Access elements by index, by
at()for negative positions, or by destructuring. - Keep arrays dense; holes cause inconsistent iteration.
- Detect arrays with
Array.isArray(), nevertypeof.
📚 Further Reading
🚀 What's Next?
Now that you can build and read arrays, the next lesson covers the mutator and accessor methods — push, pop, shift, splice, slice, and friends — that add, remove, and reshape their contents.
🎉 Well done!
You've got a solid grip on where array data lives and how to reach it. Time to start moving it around.