Chapter 1 of ?
js 21 min read

JavaScript Mastery — Chapter 12: JS Arrays & Array Methods

MODULE 2 — CHAPTER 12 22 min read

JS Arrays & Array Methods

Arrays are JavaScript's fundamental ordered collection. In this chapter, master how arrays store indexed data in contiguous memory, every essential mutating method (push, pop, splice...), every non-mutating method (slice, concat, indexOf...), sorting, spreading, and multi-dimensional arrays — with fully interactive visualizers throughout.

12.1 Array Fundamentals & Memory Layout

An Array is an ordered, indexed collection of values. Unlike plain objects whose keys are arbitrary strings, array elements are accessed by zero-based integer indices. JavaScript arrays are dynamic (automatically resize), heterogeneous (can hold any mix of types), and are technically special objects that inherit from Array.prototype.

How Arrays Are Stored in Memory

Like all objects, arrays live in the Memory Heap. The variable only holds a reference pointer. Modern JavaScript engines use an optimized flat memory structure for arrays that contain a single uniform type (called a fast array). When you mix types, the engine downgrades to a slower dictionary-mode array. Keeping arrays homogeneous is a key performance best practice.

Architecture Diagram 1: Array Index-to-Value Memory Layout
CALL STACK fruits ptr 0x3AF1 alias ptr 0x3AF1 MEMORY HEAP — fruits @ 0x3AF1 index 0 "apple" index 1 "banana" index 2 "cherry" index 3 "date" property length 4 Both "fruits" and "alias" point to the same array! Last index = length - 1 | fruits.at(-1) returns "date"

Arrays Are Reference Types

const fruits = ["apple", "banana", "cherry", "date"];

// Reference semantics — alias points to the SAME array
const alias = fruits;
alias.push("elderberry");
console.log(fruits.length); // 5 — fruits was mutated!

// typeof and Array detection
console.log(typeof fruits);          // "object" -- typeof lies!
console.log(Array.isArray(fruits));  // true -- use this
console.log(fruits instanceof Array);// true

// .length is NOT read-only — you can truncate!
fruits.length = 2;
console.log(fruits); // ["apple", "banana"]
Try it Yourself »

12.2 Creating Arrays & Sparse Array Pitfalls

There are four common ways to create an array. Prefer array literal syntax [] in almost all situations — it's faster, clearer, and avoids an infamous Array() constructor pitfall.

Four Array Creation Patterns

// 1. Array Literal — Preferred
const colors = ["red", "green", "blue"];

// 2. Array Constructor — Single number creates HOLES (sparse!)
const trap = new Array(3);       // [ empty x 3 ] — NOT [3]!
const safe = new Array(1, 2, 3); // [1, 2, 3] — multiple args is fine

// 3. Array.from() — Convert iterables / build with fill function
const letters = Array.from("hello");         // ["h","e","l","l","o"]
const squares = Array.from({length: 5}, (_, i) => i * i); // [0,1,4,9,16]

// 4. Array.of() — Consistent single-number creation (no trap)
const singleItem = Array.of(3);  // [3] — predictable!

// Sparse arrays — avoid!
const sparse = [1, , , 4];  // [ 1, empty x 2, 4 ]
console.log(sparse[1]);     // undefined (but the slot is "empty")
console.log(1 in sparse);   // false — index 1 doesn't exist!
Try it Yourself »
The new Array(n) Pitfall

new Array(3) does NOT create [3]. It creates an array with 3 empty slots (a sparse array). Many iteration methods (forEach, map) silently skip empty slots. Always prefer Array.from({length: n}, () => defaultValue) or new Array(n).fill(0) to create a dense filled array.

12.3 Accessing, Modifying & Destructuring

Access elements via bracket notation arr[index] with zero-based indices. Out-of-bounds access returns undefined — no runtime error. ES2022 added the ergonomic arr.at(-1) method for negative indexing.

Access, Mutate & Destructure

const scores = [95, 87, 72, 100, 68];

// Access
console.log(scores[0]);          // 95 — first
console.log(scores[4]);          // 68 — last (explicit)
console.log(scores.at(-1));      // 68 — last (ES2022)
console.log(scores.at(-2));      // 100 — second to last
console.log(scores[99]);         // undefined — no error

// Mutate a slot directly
scores[2] = 80;                  // [95, 87, 80, 100, 68]

// Array Destructuring
const [first, second, ...rest] = scores;
console.log(first);  // 95
console.log(second); // 87
console.log(rest);   // [80, 100, 68]

// Swap two variables — elegant!
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

// Skip elements with commas
const [,, third] = [10, 20, 30, 40];
console.log(third); // 30
Try it Yourself »

12.4 Mutating Methods — Add & Remove

Mutating methods modify the original array in place. The four core add/remove methods operate on the ends of the array:

push() pop() unshift() shift() reverse() sort() fill() splice()
Diagram 2: push / pop / shift / unshift — Complexity & Direction
Array: ["A", "B", "C"] "A" [0] "B" [1] "C" [2] "D" push() pop() removes push() appends "Z" unshift() shift() removes unshift() prepends shift() and unshift() are O(n) — re-index ALL elements Avoid in performance-critical loops push() and pop() are O(1) — best for stack patterns No re-indexing needed when operating at the tail

push, pop, shift, unshift in Action

const queue = ["Alice", "Bob", "Carol"];

// push() — appends to end, returns NEW length
const newLen = queue.push("Dave", "Eve");
console.log(queue);   // ["Alice","Bob","Carol","Dave","Eve"]
console.log(newLen);  // 5

// pop() — removes from end, returns REMOVED element
const last = queue.pop();
console.log(last);    // "Eve"
console.log(queue);   // ["Alice","Bob","Carol","Dave"]

// unshift() — prepends to front, returns NEW length
queue.unshift("Zara");
console.log(queue);   // ["Zara","Alice","Bob","Carol","Dave"]

// shift() — removes from front, returns REMOVED element
const first = queue.shift();
console.log(first);   // "Zara"
console.log(queue);   // ["Alice","Bob","Carol","Dave"]

// Stack pattern (LIFO) — use push/pop only
const stack = [];
stack.push(10);
stack.push(20);
stack.push(30);
console.log(stack.pop()); // 30 — LIFO
console.log(stack.pop()); // 20
Try it Yourself »

fill() — Bulk Value Assignment

// fill(value, startIndex, endIndex) — end is exclusive
const nums = [1, 2, 3, 4, 5];
nums.fill(0, 1, 4);         // fill 0 from index 1 to 3
console.log(nums);          // [1, 0, 0, 0, 5]

// Create a filled array from scratch — better than new Array(n)
const zeros = new Array(5).fill(0);   // [0, 0, 0, 0, 0]

// Safe 2D matrix initialization
const matrix = new Array(3).fill(null).map(() => new Array(3).fill(0));
// Creates [[0,0,0],[0,0,0],[0,0,0]]
Try it Yourself »

12.5 splice() — Surgical Array Editing

splice(start, deleteCount, ...items) is the Swiss Army knife of array mutation. It can remove, insert, and replace elements at any position simultaneously, returning an array of removed elements.

splice() vs slice() — Don't Confuse Them!

splice() = mutates the original array. slice() = does NOT mutate, returns a shallow copy. The single letter difference causes many bugs!

splice() — Remove, Insert, Replace

const months = ["Jan", "Feb", "Mar", "Apr", "May"];

// Remove 2 elements starting at index 1
const removed = months.splice(1, 2);
console.log(removed); // ["Feb", "Mar"]
console.log(months);  // ["Jan", "Apr", "May"]

// Insert at index 1 WITHOUT removing (deleteCount = 0)
months.splice(1, 0, "Feb", "Mar");
console.log(months);  // ["Jan", "Feb", "Mar", "Apr", "May"] (restored)

// Replace 1 element at index 2 with 2 new elements
months.splice(2, 1, "March", "BONUS");
console.log(months);  // ["Jan", "Feb", "March", "BONUS", "Apr", "May"]

// Negative start — count from end
months.splice(-1, 1); // Remove last element
console.log(months);  // ["Jan", "Feb", "March", "BONUS", "Apr"]
Try it Yourself »

12.6 Non-Mutating Methods — slice, concat, join

Non-mutating methods never modify the original array — they always return a new value (array, string, or boolean). These are safe to use on shared state and form the foundation of functional programming with arrays.

slice() concat() join() indexOf() lastIndexOf() includes() flat() toReversed() toSorted() toSpliced()

slice() — Extract a Shallow Copy

// slice(start, end) — end is EXCLUSIVE, original unchanged
const animals = ["cat", "dog", "fox", "gnu", "hen"];

const mid = animals.slice(1, 4);  // indexes 1, 2, 3
console.log(mid);      // ["dog", "fox", "gnu"]
console.log(animals);  // unchanged!

// Negative indices
const last2 = animals.slice(-2); // ["gnu", "hen"]

// Clone an array with slice()
const clone = animals.slice();   // entire array
clone.push("ibis");
console.log(animals.length);     // 5 — original untouched
Try it Yourself »

concat() & join()

const a = [1, 2, 3];
const b = [4, 5];
const c = [6];

// concat() — merge arrays into new array
const merged = a.concat(b, c, [7, 8]);
console.log(merged); // [1, 2, 3, 4, 5, 6, 7, 8]
console.log(a);      // [1, 2, 3] — unchanged

// join() — converts array to string
const words = ["JavaScript", "is", "awesome"];
console.log(words.join(" "));  // "JavaScript is awesome"
console.log(words.join("-"));  // "JavaScript-is-awesome"
console.log(words.join(""));   // "JavaScriptisawesome"
console.log(words.join());     // "JavaScript,is,awesome" (default comma)

// split() is the inverse — String to Array
const csv = "Alice,Bob,Carol";
console.log(csv.split(","));  // ["Alice", "Bob", "Carol"]
Try it Yourself »

indexOf(), lastIndexOf() & includes()

const tags = ["js", "css", "html", "js", "python"];

// indexOf() — first occurrence, returns -1 if not found
console.log(tags.indexOf("js"));       // 0
console.log(tags.indexOf("js", 1));    // 3 (search from index 1)
console.log(tags.indexOf("ruby"));     // -1

// lastIndexOf() — last occurrence
console.log(tags.lastIndexOf("js"));   // 3

// includes() — returns boolean (preferred existence check)
console.log(tags.includes("css"));     // true
console.log(tags.includes("ruby"));    // false

// Important: NaN edge case
const nums = [1, NaN, 3];
console.log(nums.indexOf(NaN));   // -1  -- fails (uses ===)
console.log(nums.includes(NaN)); // true -- uses SameValueZero
Try it Yourself »

12.7 Searching & Sorting Arrays

JavaScript's built-in sort() has one infamous gotcha that trips up every developer. Understanding its default behaviour — and how to override it with a comparator function — is essential.

The sort() Gotcha — Default is Lexicographic!

By default, sort() converts every element to a string and compares Unicode code points. This means [10, 9, 100].sort() gives [10, 100, 9] — not [9, 10, 100]! Always pass a comparator for numeric sorting.

Correct Numeric Sorting & Custom Comparators

// Wrong — lexicographic sort
const nums = [10, 9, 100, 2, 50];
console.log([...nums].sort());          // [10, 100, 2, 50, 9] — WRONG!

// Correct — numeric ascending
console.log([...nums].sort((a, b) => a - b)); // [2, 9, 10, 50, 100]

// Numeric descending
console.log([...nums].sort((a, b) => b - a)); // [100, 50, 10, 9, 2]

// Sorting objects by property
const students = [
  { name: "Alice", grade: 88 },
  { name: "Bob",   grade: 95 },
  { name: "Carol", grade: 72 }
];
students.sort((a, b) => b.grade - a.grade);
console.log(students.map(s => s.name)); // ["Bob", "Alice", "Carol"]

// Case-insensitive string sort using localeCompare
const mixed = ["Banana", "apple", "Cherry"];
mixed.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(mixed); // ["apple", "Banana", "Cherry"]

// ES2023: toSorted() — NON-MUTATING sort
const original = [3, 1, 2];
const sorted = original.toSorted((a, b) => a - b);
console.log(original); // [3, 1, 2] — unchanged!
console.log(sorted);   // [1, 2, 3]
Try it Yourself »

reverse() and toReversed()

const letters = ["a", "b", "c", "d"];

// reverse() — MUTATES in place
letters.reverse();
console.log(letters); // ["d", "c", "b", "a"]

// ES2023: toReversed() — returns new array, non-mutating
const original = ["a", "b", "c"];
const reversed = original.toReversed();
console.log(original); // ["a", "b", "c"] — unchanged
console.log(reversed); // ["c", "b", "a"]

// Reverse a string using array helpers
const str = "JavaScript";
const rev = str.split("").reverse().join("");
console.log(rev); // "tpircSavaJ"
Try it Yourself »

12.8 Spread Operator & Multi-Dimensional Arrays

The spread operator (...) unpacks an iterable into individual elements. It's the modern replacement for concat(), apply() hacks, and manual copying. Multi-dimensional arrays are arrays of arrays — JavaScript has no native 2D array type, but nested arrays work perfectly.

Spread Operator — Clone, Merge, Pass

const a = [1, 2, 3];
const b = [4, 5, 6];

// Clone (shallow copy)
const clone = [...a];
clone.push(99);
console.log(a);     // [1, 2, 3] — original safe

// Merge arrays
const merged = [...a, ...b];       // [1, 2, 3, 4, 5, 6]
const inserted = [...a, 99, ...b]; // [1, 2, 3, 99, 4, 5, 6]

// Spread into function arguments
const nums = [15, 3, 9, 22, 7];
console.log(Math.max(...nums));  // 22
console.log(Math.min(...nums));  // 3

// Convert Set to Array (deduplicate)
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]

// Convert string to char array
const chars = [..."hello"]; // ["h", "e", "l", "l", "o"]
Try it Yourself »

Multi-Dimensional Arrays & flat()

// 2D matrix
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

// Access with double indexing: [row][column]
console.log(matrix[1][2]); // 6 (row 1, col 2)
console.log(matrix[2][0]); // 7 (row 2, col 0)

// flat() — flatten nested arrays
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat());         // [1, 2, 3, 4, [5, 6]] — 1 level
console.log(nested.flat(2));        // [1, 2, 3, 4, 5, 6]   — 2 levels
console.log(nested.flat(Infinity)); // fully flatten any depth
Try it Yourself »

Complete Array Methods Quick Reference

Method Mutates? Returns Description
push(...items)MutatesNew lengthAppends items to end
pop()MutatesRemoved elementRemoves last element
unshift(...items)MutatesNew lengthPrepends items to front
shift()MutatesRemoved elementRemoves first element
splice(start, del, ...items)MutatesRemoved elements arrayInsert/remove/replace at any index
reverse()MutatesReversed array (same ref)Reverses array in place
sort(compareFn)MutatesSorted array (same ref)Sorts in place — always use comparator for numbers!
fill(val, start, end)MutatesModified array (same ref)Fills range with value
slice(start, end)Read-onlyNew arrayShallow copy of a range
concat(...arrays)Read-onlyNew merged arrayMerges multiple arrays
join(sep)Read-onlyStringJoins elements into string
indexOf(val, from)Read-onlyIndex or -1First index of value (strict ===)
lastIndexOf(val)Read-onlyIndex or -1Last index of value
includes(val)Read-onlyBooleanTrue if value exists — handles NaN correctly
flat(depth)Read-onlyNew flattened arrayFlattens nested arrays
toReversed()Read-onlyNew reversed arrayES2023: non-mutating reverse
toSorted(compareFn)Read-onlyNew sorted arrayES2023: non-mutating sort
toSpliced(start, del, ...items)Read-onlyNew arrayES2023: non-mutating splice
at(index)Read-onlyElementES2022: supports negative index

12.9 Interactive Mini-Labs

Lab 1

Live Array Mutator

Watch the array update in real time as you push, pop, shift, unshift, or sort it.

Array ready — use the buttons above!
Lab 2

slice() vs splice() Side-by-Side

See the original array before and after — notice which method mutates it.

slice(1, 3)
Click button to run
splice(1, 2)
Click button to run
Lab 3

Sort Comparator Explorer

See the difference between default (lexicographic) sort and correct numeric sort.

Original: [10, 9, 100, 2, 50]

12.10 Coding Challenge: Shopping Cart Engine

Challenge Intermediate ~20 minutes

Build a Shopping Cart Array Engine

Build a complete shopping cart system using only the array methods from this chapter — no forEach/map/filter yet (those come in Chapter 13).

Requirements:
  • Create a cart array of item objects: { id, name, price, qty }
  • Implement addItem(item) — adds item or increments qty if id already exists (use splice to update)
  • Implement removeItem(id) — removes an item by id using splice
  • Implement getTotal() — calculates total price (use a for loop)
  • Implement sortByPrice() — sorts cart ascending by price using a comparator
  • Implement clearCart() — empties the cart using splice(0) (keeps same array reference)
  • Implement getItemNames() — returns a sorted copy of item names using slice + sort

Use a for loop and check cart[i].id === id. Store the found index in a variable. If the index is -1 (not found), add the item using push(); otherwise use splice(index, 1, updatedItem) to replace it in place.

function getTotal() {
  let total = 0;
  for (let i = 0; i < cart.length; i++) {
    total += cart[i].price * cart[i].qty;
  }
  return total.toFixed(2);
}

const cart = [];

function findIndex(id) {
  for (let i = 0; i < cart.length; i++) {
    if (cart[i].id === id) return i;
  }
  return -1;
}

function addItem(item) {
  const idx = findIndex(item.id);
  if (idx === -1) {
    cart.push({ ...item, qty: item.qty ?? 1 });
  } else {
    const existing = cart[idx];
    cart.splice(idx, 1, { ...existing, qty: existing.qty + 1 });
  }
}

function removeItem(id) {
  const idx = findIndex(id);
  if (idx !== -1) cart.splice(idx, 1);
}

function getTotal() {
  let total = 0;
  for (let i = 0; i < cart.length; i++) {
    total += cart[i].price * cart[i].qty;
  }
  return total.toFixed(2);
}

function sortByPrice() {
  cart.sort((a, b) => a.price - b.price);
}

function clearCart() {
  cart.splice(0); // mutates in place — same array reference!
}

function getItemNames() {
  const names = [];
  for (let i = 0; i < cart.length; i++) names.push(cart[i].name);
  return names.slice().sort();
}

// Test it!
addItem({ id: 1, name: "Laptop",  price: 999.99 });
addItem({ id: 2, name: "Mouse",   price: 29.99  });
addItem({ id: 3, name: "Monitor", price: 349.99 });
addItem({ id: 1, name: "Laptop",  price: 999.99 }); // qty becomes 2

console.log("Total: $" + getTotal()); // $2378.96
console.log("Names:", getItemNames()); // ["Laptop","Monitor","Mouse"]
sortByPrice();
console.log("Sorted:", cart.map(i => i.name)); // ["Mouse","Monitor","Laptop"]
removeItem(2);
clearCart();
console.log("After clear:", cart.length); // 0
Try it Yourself »

Chapter 12 — Key Takeaways

  • Arrays are ordered, zero-indexed, reference-type objects in the Heap
  • Use Array.isArray() — never typeof — to detect arrays
  • Prefer array literals [] over new Array() to avoid sparse traps
  • push/pop are O(1); shift/unshift are O(n)
  • splice() = mutates original; slice() = does NOT mutate
  • Always pass a comparator function for numeric sort()
  • includes() handles NaN correctly; indexOf() does not
  • Spread [...arr] = clean shallow clone; flat(Infinity) = deep flatten
  • ES2023: toSorted(), toReversed(), toSpliced() = non-mutating alternatives
  • Next Chapter: forEach, map, filter, reduce, and functional iteration
Done with this chapter?
Mark it complete to track your progress and unlock your certificate.
Next Up

Learner Reviews

Write a Review
Share your experience to help other learners.
Your Rating *