Skip to main content

📦 Lesson 9: Arrays

Variables hold a single value — but what if you need a list of values? Arrays let you store, access, and manipulate ordered collections of data.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Create arrays using literal syntax and the Array constructor
  • Access and update elements by index
  • Use length to find and control the size of an array
  • Add and remove elements with push, pop, unshift, and shift
  • Insert, remove, or replace elements with splice
  • Search arrays with includes, indexOf, and find
  • Combine and extract arrays with concat, slice, and the spread operator
  • Iterate over arrays with for, for...of, and forEach

Estimated Time: 50 minutes

Project: Build a to-do list manager using array operations

📑 In This Lesson

Introduction

So far, every variable you've created holds a single value — one number, one string, one boolean. But real programs almost always work with collections of data: a list of usernames, a set of quiz scores, a shopping cart full of items.

That's what arrays are for. An array is an ordered list of values, and it's the most commonly used data structure in JavaScript. You'll use arrays in virtually every project you build.

graph LR A["Array: fruits"] --- B["[0] 'apple'"] A --- C["[1] 'banana'"] A --- D["[2] 'cherry'"] A --- E["[3] 'date'"] style A fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style D fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style E fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b

Each value in an array is called an element, and each element has a numbered position called its index. Indexes start at 0, not 1 — this is true across almost all programming languages.

Creating Arrays

The most common way to create an array is with square bracket notation (an array literal). You can also create empty arrays and fill them later.


// Array literal — the standard way
const fruits = ["apple", "banana", "cherry"];

// Empty array — start with nothing, add later
const scores = [];

// Arrays can hold any type — and mixed types
const mixed = [42, "hello", true, null, undefined];

// Nested arrays (arrays inside arrays)
const grid = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];
                

⚠️ Avoid the Array Constructor

You can create arrays with new Array(), but it has a confusing quirk: new Array(3) creates an empty array with 3 slots, not an array containing the number 3. Stick with square brackets.


// Confusing constructor behavior
const a = new Array(3);     // [ , , ] — 3 empty slots!
const b = new Array("3");   // ["3"] — one element

// ✅ Just use square brackets
const c = [3];              // [3] — one element, no confusion
                    

const with Arrays

Declaring an array with const prevents you from reassigning the variable, but you can still modify the contents. The const protects the reference, not the data inside.


const colors = ["red", "green", "blue"];

// ✅ Modifying contents is allowed
colors.push("yellow");       // ["red", "green", "blue", "yellow"]
colors[0] = "crimson";       // ["crimson", "green", "blue", "yellow"]

// ❌ Reassigning the variable is NOT allowed
// colors = ["purple"];      // TypeError: Assignment to constant variable
                

Accessing & Modifying Elements

Use square brackets with an index number to read or change any element. Remember: the first index is 0.


const animals = ["cat", "dog", "bird", "fish"];

// Reading elements
console.log(animals[0]);     // "cat"   (first element)
console.log(animals[2]);     // "bird"  (third element)
console.log(animals[3]);     // "fish"  (last element in this array)

// Updating elements
animals[1] = "wolf";
console.log(animals);        // ["cat", "wolf", "bird", "fish"]

// Accessing out of bounds — no error, just undefined
console.log(animals[99]);    // undefined
                

Accessing the Last Element

To get the last element, you can use length - 1 or the modern at() method with a negative index.


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

// Classic approach
console.log(letters[letters.length - 1]);  // "e"

// Modern approach — at() supports negative indexes
console.log(letters.at(-1));   // "e"  (last)
console.log(letters.at(-2));   // "d"  (second to last)
console.log(letters.at(0));    // "a"  (same as letters[0])
                

Destructuring Arrays

You can unpack array values directly into variables with destructuring.


const rgb = [255, 128, 0];

// Without destructuring
const red = rgb[0];
const green = rgb[1];
const blue = rgb[2];

// With destructuring — cleaner!
const [r, g, b] = rgb;
console.log(r);  // 255
console.log(g);  // 128
console.log(b);  // 0

// Skip elements with commas
const [first, , third] = ["a", "b", "c"];
console.log(first);  // "a"
console.log(third);  // "c"
                

The length Property

Every array has a length property that tells you how many elements it contains. It updates automatically as you add or remove elements.


const items = ["pen", "paper", "eraser"];
console.log(items.length);  // 3

items.push("ruler");
console.log(items.length);  // 4

// Common pattern: check if array is empty
if (items.length === 0) {
    console.log("No items!");
} else {
    console.log(`You have ${items.length} items.`);
}
                

💡 length Is Not Read-Only

You can set length to truncate an array or extend it (with empty slots). This is rarely useful but good to know.


const nums = [1, 2, 3, 4, 5];
nums.length = 3;
console.log(nums);  // [1, 2, 3] — last two elements removed!

nums.length = 5;
console.log(nums);  // [1, 2, 3, empty × 2] — extended with empty slots
                    

Adding & Removing Elements

JavaScript provides four core methods to add or remove elements from the beginning or end of an array.

graph LR UNS["unshift()"] -->|"Add to start"| ARR["[ a, b, c ]"] ARR -->|"Remove from start"| SHI["shift()"] PUS["push()"] -->|"Add to end"| ARR ARR -->|"Remove from end"| POP["pop()"] style ARR fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style PUS fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style POP fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style UNS fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style SHI fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b

push() and pop() — End of Array


const stack = ["a", "b", "c"];

// push — add one or more elements to the END
stack.push("d");
console.log(stack);  // ["a", "b", "c", "d"]

stack.push("e", "f");
console.log(stack);  // ["a", "b", "c", "d", "e", "f"]

// push returns the NEW length
const newLength = stack.push("g");
console.log(newLength);  // 7

// pop — remove the LAST element
const removed = stack.pop();
console.log(removed);  // "g"
console.log(stack);    // ["a", "b", "c", "d", "e", "f"]
                

unshift() and shift() — Beginning of Array


const queue = ["b", "c", "d"];

// unshift — add to the BEGINNING
queue.unshift("a");
console.log(queue);  // ["a", "b", "c", "d"]

// shift — remove from the BEGINNING
const first = queue.shift();
console.log(first);  // "a"
console.log(queue);  // ["b", "c", "d"]
                

💡 Performance Note

push/pop are fast — they work on the end of the array without moving other elements. unshift/shift are slower because every other element needs to shift position. For small arrays this doesn't matter, but keep it in mind for large datasets.

Method Position Action Returns Modifies Original?
push()EndAddNew lengthYes
pop()EndRemoveRemoved elementYes
unshift()StartAddNew lengthYes
shift()StartRemoveRemoved elementYes

Splice — The Swiss Army Knife

The splice() method can remove, insert, or replace elements at any position in an array. It's the most versatile array method.


// Syntax: array.splice(startIndex, deleteCount, ...newItems)

const colors = ["red", "green", "blue", "yellow", "purple"];
                

Removing Elements


const colors = ["red", "green", "blue", "yellow", "purple"];

// Remove 2 elements starting at index 1
const removed = colors.splice(1, 2);
console.log(removed);  // ["green", "blue"]
console.log(colors);   // ["red", "yellow", "purple"]
                

Inserting Elements


const colors = ["red", "yellow", "purple"];

// Insert at index 1, delete 0 elements
colors.splice(1, 0, "orange", "green");
console.log(colors);  // ["red", "orange", "green", "yellow", "purple"]
                

Replacing Elements


const colors = ["red", "orange", "green", "yellow", "purple"];

// At index 2, remove 1 element and insert "blue"
colors.splice(2, 1, "blue");
console.log(colors);  // ["red", "orange", "blue", "yellow", "purple"]

// Replace multiple elements
colors.splice(0, 2, "crimson");
console.log(colors);  // ["crimson", "blue", "yellow", "purple"]
                
graph TD A["splice(start, deleteCount, ...items)"] --> B["Delete only
splice(1, 2)"] A --> C["Insert only
splice(1, 0, 'new')"] A --> D["Replace
splice(1, 1, 'new')"] style A fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style B fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style C fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style D fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b

⚠️ splice vs. slice

Don't confuse splice and slice! They sound similar but behave very differently. splice modifies the original array. slice (covered below) copies a portion without changing the original.

Searching Arrays

JavaScript gives you several ways to check if an array contains a value or find where it is.

includes() — Does It Exist?


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

console.log(fruits.includes("banana"));   // true
console.log(fruits.includes("grape"));    // false

// Common pattern: conditional check
if (fruits.includes("cherry")) {
    console.log("Cherry is in stock!");
}
                

indexOf() — Where Is It?


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

console.log(fruits.indexOf("banana"));   // 1 (first occurrence)
console.log(fruits.indexOf("grape"));    // -1 (not found)

// Classic pattern: check existence before indexOf existed
if (fruits.indexOf("cherry") !== -1) {
    console.log("Found cherry!");
}
                

find() — Get the First Match

find() takes a function and returns the first element that makes the function return true. This is especially useful with arrays of objects (which you'll learn about next lesson).


const numbers = [3, 7, 12, 5, 22, 8];

// Find the first number greater than 10
const big = numbers.find(num => num > 10);
console.log(big);  // 12

// Find the first even number
const even = numbers.find(num => num % 2 === 0);
console.log(even);  // 12

// If nothing matches, returns undefined
const huge = numbers.find(num => num > 100);
console.log(huge);  // undefined
                

findIndex() — Where Is the First Match?


const numbers = [3, 7, 12, 5, 22, 8];

const index = numbers.findIndex(num => num > 10);
console.log(index);  // 2 (the index of 12)

const notFound = numbers.findIndex(num => num > 100);
console.log(notFound);  // -1
                
Method Returns Best For
includes(value)true / falseSimple existence check
indexOf(value)Index or -1Finding position of exact value
find(fn)Element or undefinedFinding first match by condition
findIndex(fn)Index or -1Finding position of first match

Combining & Extracting

concat() — Merge Arrays

concat() creates a new array by joining two or more arrays together. The originals are unchanged.


const front = ["a", "b"];
const back = ["c", "d"];
const extra = ["e"];

const combined = front.concat(back);
console.log(combined);   // ["a", "b", "c", "d"]
console.log(front);      // ["a", "b"] — unchanged!

const all = front.concat(back, extra);
console.log(all);        // ["a", "b", "c", "d", "e"]
                

Spread Operator — The Modern Way

The spread operator (...) "spreads" an array's elements into a new array. It's the preferred modern approach to combining arrays.


const front = ["a", "b"];
const back = ["c", "d"];

// Spread into a new array
const combined = [...front, ...back];
console.log(combined);  // ["a", "b", "c", "d"]

// Add extra elements in between
const withMiddle = [...front, "X", "Y", ...back];
console.log(withMiddle);  // ["a", "b", "X", "Y", "c", "d"]

// Copy an array (shallow copy)
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original);  // [1, 2, 3] — unaffected
console.log(copy);      // [1, 2, 3, 4]
                

slice() — Extract a Portion

slice() returns a new array containing a portion of the original. It does not modify the original. Think of it as copying a "slice" out of the array.


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

// slice(startIndex, endIndex) — end is NOT included
console.log(letters.slice(1, 3));   // ["b", "c"]
console.log(letters.slice(2));      // ["c", "d", "e"] (from index 2 to end)
console.log(letters.slice(-2));     // ["d", "e"] (last 2 elements)
console.log(letters.slice());       // ["a", "b", "c", "d", "e"] (full copy)

// Original is unchanged
console.log(letters);  // ["a", "b", "c", "d", "e"]
                

✅ Mutating vs. Non-Mutating Methods

Some methods change the original array (mutating), while others return a new one and leave the original alone (non-mutating). Knowing the difference prevents bugs!

  • Mutating: push, pop, shift, unshift, splice, sort, reverse
  • Non-mutating: concat, slice, map, filter, find, includes

Iterating Over Arrays

You'll often need to do something with every element in an array. Here are the three main approaches.

Classic for Loop

Gives you full control — you get the index and can break early, skip iterations, or go backwards.


const scores = [85, 92, 78, 95, 88];

for (let i = 0; i < scores.length; i++) {
    console.log(`Score ${i + 1}: ${scores[i]}`);
}
// Score 1: 85
// Score 2: 92
// ...
                

for...of Loop

Cleaner when you just need the values and don't care about indexes.


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

for (const fruit of fruits) {
    console.log(fruit.toUpperCase());
}
// APPLE
// BANANA
// CHERRY
                

forEach() Method

forEach calls a function for each element. It gives you both the element and its index.


const colors = ["red", "green", "blue"];

colors.forEach((color, index) => {
    console.log(`${index}: ${color}`);
});
// 0: red
// 1: green
// 2: blue
                

💡 Which Loop Should I Use?

  • for...of — default choice for most situations. Clean and readable.
  • for — when you need the index, or need break/continue.
  • forEach — when you want a callback pattern. Note: you can't break out of forEach.

Building New Arrays from Loops


const prices = [10, 20, 30, 40];

// Add tax to each price
const withTax = [];
for (const price of prices) {
    withTax.push(price * 1.08);
}
console.log(withTax);  // [10.8, 21.6, 32.4, 43.2]

// In the next lesson (Array Methods), you'll learn
// a cleaner way: prices.map(price => price * 1.08)
                

Hands-on Exercise

🏋️ Exercise: To-Do List Manager

Objective: Build a set of functions that manage a to-do list using array methods.


// Start with this to-do list
const todos = ["Buy groceries", "Walk the dog", "Finish homework"];

// TODO: Write these functions:

// 1. addTodo(list, task)
//    - Add a task to the end of the list
//    - Return the updated list

// 2. removeTodo(list, task)
//    - Find and remove the task from the list
//    - If the task doesn't exist, log "Task not found: [task]"
//    - Return the updated list

// 3. insertTodoAt(list, index, task)
//    - Insert a task at a specific position
//    - Return the updated list

// 4. listTodos(list)
//    - Log each task with a number: "1. Buy groceries"
//    - If the list is empty, log "No tasks — you're all caught up!"

// 5. completeTodo(list)
//    - Remove and return the FIRST task (treat the list as a queue)
//    - Log "Completed: [task]"
//    - If list is empty, log "Nothing to complete!"

// Test your functions:
// addTodo(todos, "Read a book");
// listTodos(todos);
// removeTodo(todos, "Walk the dog");
// completeTodo(todos);
// insertTodoAt(todos, 1, "Call dentist");
// listTodos(todos);
                    
💡 Hint

addTodo: Use push().

removeTodo: Use indexOf() to find the task's position, then splice() to remove it. Check for -1 first (not found).

insertTodoAt: Use splice(index, 0, task) to insert without removing.

listTodos: Use forEach and check length === 0 first.

completeTodo: Use shift() to remove and return the first element.

✅ Solution

const todos = ["Buy groceries", "Walk the dog", "Finish homework"];

function addTodo(list, task) {
    list.push(task);
    return list;
}

function removeTodo(list, task) {
    const index = list.indexOf(task);
    if (index === -1) {
        console.log(`Task not found: ${task}`);
    } else {
        list.splice(index, 1);
    }
    return list;
}

function insertTodoAt(list, index, task) {
    list.splice(index, 0, task);
    return list;
}

function listTodos(list) {
    if (list.length === 0) {
        console.log("No tasks — you're all caught up!");
        return;
    }
    list.forEach((task, i) => {
        console.log(`${i + 1}. ${task}`);
    });
}

function completeTodo(list) {
    if (list.length === 0) {
        console.log("Nothing to complete!");
        return;
    }
    const done = list.shift();
    console.log(`Completed: ${done}`);
    return done;
}

// Testing
addTodo(todos, "Read a book");
listTodos(todos);
// 1. Buy groceries
// 2. Walk the dog
// 3. Finish homework
// 4. Read a book

removeTodo(todos, "Walk the dog");
listTodos(todos);
// 1. Buy groceries
// 2. Finish homework
// 3. Read a book

completeTodo(todos);
// Completed: Buy groceries

insertTodoAt(todos, 1, "Call dentist");
listTodos(todos);
// 1. Finish homework
// 2. Call dentist
// 3. Read a book
                        

🎯 Quick Quiz

Question 1: What is the index of the first element in a JavaScript array?

Question 2: Which method adds an element to the END of an array?

Question 3: What does splice() do?

Question 4: What does ["a","b","c"].includes("b") return?

Summary

🎉 Key Takeaways

  • Arrays store ordered collections of values in a single variable
  • Indexes start at 0 — the first element is array[0]
  • push/pop add/remove from the end; unshift/shift add/remove from the start
  • splice() can insert, remove, or replace elements at any position
  • includes checks existence; indexOf finds position; find finds by condition
  • slice and the spread operator create copies without modifying the original
  • for...of is the cleanest way to loop through arrays
  • const arrays can have their contents changed — const only locks the variable binding

📚 Additional Resources

🚀 What's Next?

Now that you can create and manipulate arrays, the next lesson covers objects — JavaScript's other essential data structure. Objects let you store data with named keys instead of numbered indexes, which is perfect for representing real-world things like users, products, and settings.