📦 Working with Arrays
Almost every program you write handles lists of things: products in a cart, messages in a chat, rows returned from a database. The array is JavaScript's fundamental tool for holding an ordered collection in a single named container — and mastering it is the difference between fighting your data and flowing with it.
Week 2 · Day 1 (Monday: Arrays and Objects) · Lecture 1
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Create arrays three different ways and choose the array literal by default
- Access, update, and count elements using zero-based indexing and
length - Add and remove items from either end with
push,pop,unshift, andshift - Search arrays with
indexOf,lastIndexOf, andincludes - Distinguish the non-mutating
slicefrom the mutatingsplice - Explain why arrays are reference types and copy them safely
Estimated Time: 60 minutes
Practice: Build reverse, de-duplicate, second-largest, and rotate helper functions.
In This Lesson
What Are Arrays?
Imagine a shopping list. Instead of inventing a separate variable for every item — item1, item2, item3 — an array lets you store the whole list in one ordered container. Think of it as a train: a single named engine pulls a line of numbered cars, and each car holds one piece of data.
The key words are ordered and indexed. Every element has a position — its index — and those indices start at 0, not 1. That single fact is the source of more beginner bugs than any other, so keep it front of mind: the first element lives at index 0, and the last lives at index length - 1.
Creating Arrays
Just as there are different ways to make a sandwich, there are several ways to create an array. In practice you'll reach for the first one almost every time.
// Method 1: Array literal — the idiomatic, most common way
const fruits = ['apple', 'banana', 'orange'];
// Method 2: Array constructor — rarely needed, and easy to misuse
const numbers = new Array(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
const oops = new Array(3); // [ <3 empty slots> ] — NOT [3]!
// Method 3: Empty array you fill in later
const emptyBasket = [];
// Arrays can hold any mix of data types
const mixedBag = ['text', 42, true, null, { name: 'John' }];
⚠️ The new Array() gotcha
new Array(3) does not create [3] — it creates an empty array of length 3. That inconsistency is exactly why the array literal [] is preferred: it always means what it looks like.
Real-world example: a shopping cart
Arrays really shine when each element is itself a small object. A shopping cart is a classic case — an ordered list of line items.
// A shopping cart in an e-commerce site
const shoppingCart = [
{ product: 'Laptop', price: 999.99, quantity: 1 },
{ product: 'Mouse', price: 29.99, quantity: 2 },
{ product: 'Keyboard', price: 89.99, quantity: 1 }
];
console.log(shoppingCart.length); // 3 line items
console.log(shoppingCart[0].product); // "Laptop"
Indexing & Length
Arrays are like an apartment building: each item has its own "apartment number" (index), and the numbering starts on the ground floor at 0.
0 to length - 1. To reach the last element, use arr[arr.length - 1].const colors = ['red', 'green', 'blue', 'yellow'];
// Accessing elements by index
console.log(colors[0]); // 'red' (first element)
console.log(colors[2]); // 'blue' (third element)
// length — how many elements the array holds
console.log(colors.length); // 4
// Modifying an element — like repainting one apartment
colors[1] = 'lime';
console.log(colors); // ['red', 'lime', 'blue', 'yellow']
// Reaching the last element reliably
console.log(colors[colors.length - 1]); // 'yellow'
// Modern shortcut (ES2022): .at() accepts negative indices
console.log(colors.at(-1)); // 'yellow'
console.log(colors.at(-2)); // 'blue'
💡 Use .at(-1) for the last element
The .at() method (ES2022) lets you count from the end with negative indices, so arr.at(-1) is a clean, readable way to grab the final element without the length - 1 arithmetic.
Adding & Removing at the Ends
Four methods add or remove elements from the ends of an array. They come in two pairs — one for the end and one for the start.
push & pop — work at the end
const playlist = ['Song A', 'Song B'];
// push() — add one (or more) to the END; returns the new length
playlist.push('Song C');
console.log(playlist); // ['Song A', 'Song B', 'Song C']
// pop() — remove the LAST element; returns the removed item
const lastSong = playlist.pop();
console.log(lastSong); // 'Song C'
console.log(playlist); // ['Song A', 'Song B']
unshift & shift — work at the start
const queue = ['Person 2', 'Person 3'];
// unshift() — add to the FRONT; returns the new length
queue.unshift('Person 1');
console.log(queue); // ['Person 1', 'Person 2', 'Person 3']
// shift() — remove the FIRST element; returns the removed item
const firstPerson = queue.shift();
console.log(firstPerson); // 'Person 1'
console.log(queue); // ['Person 2', 'Person 3']
| Method | Where | Action | Returns |
|---|---|---|---|
push() | End | Add | New length |
pop() | End | Remove | Removed item |
unshift() | Start | Add | New length |
shift() | Start | Remove | Removed item |
Combining these gives you two classic data structures: push + pop makes a stack (last in, first out), while push + shift makes a queue (first in, first out).
Finding Elements
Searching an array is like looking for a book in a library. JavaScript gives you three quick tools depending on whether you want a position or a simple yes/no.
const books = ['JavaScript', 'Python', 'Java', 'C++', 'Python'];
// indexOf() — position of the FIRST match, or -1 if absent
console.log(books.indexOf('Python')); // 1
console.log(books.indexOf('Ruby')); // -1 (not found)
// lastIndexOf() — position of the LAST match
console.log(books.lastIndexOf('Python')); // 4
// includes() — a clean true/false existence check
console.log(books.includes('Java')); // true
console.log(books.includes('Go')); // false
✅ Prefer includes() for existence checks
Older code often wrote if (books.indexOf('Java') !== -1). Since ES2016, if (books.includes('Java')) says exactly the same thing and reads far better. Reserve indexOf for when you actually need the position.
slice vs splice — Array Surgery
These two look almost identical but behave completely differently. The one-letter difference in their names hides a critical distinction: slice copies and leaves the original alone; splice cuts into the original and changes it.
slice() — take a copy of a range (non-mutating)
const cake = ['layer1', 'layer2', 'layer3', 'layer4', 'layer5'];
// slice(start, end) — end is EXCLUSIVE; returns a new array
const middlePiece = cake.slice(1, 4);
console.log(middlePiece); // ['layer2', 'layer3', 'layer4']
console.log(cake); // original UNCHANGED
// A no-argument slice() is a quick way to shallow-copy an array
const copy = cake.slice();
splice() — remove and/or insert in place (mutating)
const ingredients = ['flour', 'sugar', 'eggs', 'butter', 'salt'];
// splice(startIndex, deleteCount) — removes and RETURNS removed items
const removed = ingredients.splice(1, 2);
console.log(removed); // ['sugar', 'eggs']
console.log(ingredients); // ['flour', 'butter', 'salt'] — MODIFIED
// splice can also insert: splice(startIndex, deleteCount, ...itemsToAdd)
ingredients.splice(1, 0, 'milk', 'vanilla');
console.log(ingredients); // ['flour', 'milk', 'vanilla', 'butter', 'salt']
📖 Remember the difference
slice = copy. It never touches the original and returns the extracted range. splice is surgery: it mutates the array in place and returns whatever it removed. If you find yourself confused mid-project, that mnemonic will save you.
Arrays Are Reference Types
This is the concept that trips up nearly every JavaScript learner. When you assign one array to another variable, you are not making a copy — both names point at the same underlying array in memory. Change it through one name and the other "sees" the change too.
const original = [1, 2, 3];
const copy = original; // NOT a copy — just a second label for the same array!
copy[0] = 99;
console.log(original); // [99, 2, 3] — the "original" changed too
To make an independent copy, use one of these. Each produces a new array so edits don't leak back:
const original = [1, 2, 3];
const actualCopy1 = [...original]; // spread operator (most common)
const actualCopy2 = original.slice(); // slice with no arguments
const actualCopy3 = Array.from(original); // Array.from
actualCopy1[0] = 99;
console.log(original); // [1, 2, 3] — safe, untouched
⚠️ These are shallow copies
Spread and slice copy the top level only. If your array holds objects, both arrays still share those nested objects. For a fully independent deep copy of simple data, structuredClone(original) (built into modern browsers and Node 17+) is the modern tool.
Practice & Quiz
🏋️ Exercise 1: Reverse without .reverse()
Goal: Write reverseArray(arr) that returns a new array with the elements in reverse order — without calling the built-in reverse().
function reverseArray(arr) {
// TODO: build and return a new reversed array
}
console.log(reverseArray([1, 2, 3, 4])); // should log: [4, 3, 2, 1]
💡 Hint
Start with an empty result array. Loop from the last index (arr.length - 1) down to 0, and push each element onto the result.
✅ Solution
function reverseArray(arr) {
const result = [];
for (let i = arr.length - 1; i >= 0; i--) {
result.push(arr[i]);
}
return result;
}
console.log(reverseArray([1, 2, 3, 4])); // [4, 3, 2, 1]
🏋️ Exercise 2: Remove duplicates
Goal: Write removeDuplicates(arr) that returns a new array with each value appearing only once.
💡 Hint
A Set stores only unique values. Spread the array into a Set, then spread it back into an array.
✅ Solution
function removeDuplicates(arr) {
return [...new Set(arr)];
// Classic alternative:
// return arr.filter((item, index) => arr.indexOf(item) === index);
}
console.log(removeDuplicates([1, 2, 2, 3, 4, 4, 5])); // [1, 2, 3, 4, 5]
🏋️ Exercise 3: Second largest & rotate
Goal: Write secondLargest(arr) (returns the second-biggest number, or null) and rotateArray(arr, n) (shifts elements n places to the left).
✅ Solution
function secondLargest(arr) {
if (arr.length < 2) return null;
let first = -Infinity, second = -Infinity;
for (const num of arr) {
if (num > first) {
second = first;
first = num;
} else if (num > second && num < first) {
second = num;
}
}
return second === -Infinity ? null : second;
}
console.log(secondLargest([10, 40, 30, 40, 20])); // 30
function rotateArray(arr, n) {
n = n % arr.length; // handle n larger than length
return [...arr.slice(n), ...arr.slice(0, n)];
}
console.log(rotateArray([1, 2, 3, 4, 5], 2)); // [3, 4, 5, 1, 2]
🎯 Quick Quiz
Question 1: Given const a = ['x', 'y', 'z'];, what is a[a.length - 1]?
Question 2: Which method changes the original array in place?
Question 3: After const b = a; you run b.push(9). What happened to a?
Best Practices & Pitfalls
✅ Do
- Declare arrays with
const— you can stillpush/pop;constonly blocks reassignment of the variable - Use the array literal
[]instead ofnew Array() - Reach for
includes()for existence checks and.at(-1)for the last element - Copy before mutating when you need to keep the original:
[...arr]
❌ Don't
- Use
delete arr[i]— it leaves a "hole" (a sparse array); usesplice()instead - Assume
const copy = originalmakes a copy — it shares the same array - Modify an array's length while iterating over it — you'll skip or repeat elements
- Forget that array indices are zero-based (the classic off-by-one bug)
⚠️ delete creates sparse arrays
const sparse = [1, 2, 3];
delete sparse[1]; // leaves a hole, length stays 3
console.log(sparse); // [1, <empty>, 3] ← bad
const dense = [1, 2, 3];
dense.splice(1, 1); // removes AND shifts everything down
console.log(dense); // [1, 3] ← what you actually wanted
Sparse arrays behave unpredictably with loops and methods. Always remove elements with splice(), not delete.
Summary
🎉 Key Takeaways
- Arrays store an ordered, zero-indexed collection in one container — create them with a literal
[] - Reach elements by index; the last one is
arr[arr.length - 1]orarr.at(-1) push/popwork the end,unshift/shiftwork the startslicecopies (non-mutating);spliceoperates in place (mutating)- Arrays are reference types — copy with
[...arr]before you mutate
📚 Additional Resources
🚀 What's Next?
Arrays hold data by position. Next you'll meet the other half of JavaScript's data toolkit — data organized by name: Object Literals and Properties, where you'll store related values under descriptive keys.
🎉 Great work!
Arrays are the building blocks of nearly every data structure and algorithm you'll write. You now have the fundamentals to manipulate them with confidence.