Programs spend most of their time working with lists: scores on a leaderboard, items in a shopping cart, messages in a thread. Without an array you would need a separate variable for every item — score1, score2, score3 — and your code would explode as the list grew.
An array is an ordered list of values. You create one with square brackets, separate items with commas, and reach an item by its index — and indexes start at 0, not 1:
const colors = ["red", "green", "blue"];
console.log(colors[0]); // "red"
console.log(colors[2]); // "blue"
console.log(colors.length); // 3
That zero-based indexing is the source of the single most common array bug beginners hit. We'll come back to it.
flowchart TD A["colors array"] --> B["index 0: red"] A --> C["index 1: green"] A --> D["index 2: blue"] style A fill:#e0900b,color:#fff
Creating and accessing
An array can hold any mix of types — numbers, strings, booleans, even other arrays. The index is always an integer, and trying to read past the end does not throw an error; it quietly gives back undefined:
const mixed = [42, "hi", true];
console.log(mixed[10]); // undefined — no error
You can replace an existing slot by assigning to its index: colors[1] = "yellow". This mutates the array in place, just like the methods you'll see next. The length property always reflects the highest numeric index plus one, so sparse holes can create surprising lengths.
const scores = [85, 92, 78]; console.log(scores[0]); console.log(scores.length); scores.push(95); console.log(scores); console.log(scores[scores.length - 1]);
Mutating methods: push, pop, shift, unshift
Arrays are mutable — you can change them after creation. Add to the end with push, remove the last item with pop. Need to work at the front instead? unshift adds to the start and shift removes it:
const q = ["a", "b"];
q.push("c"); // ["a","b","c"]
q.pop(); // removes "c", returns it
q.unshift("z"); // ["z","a","b"]
q.shift(); // removes "z", returns it
All four change the original array rather than returning a new one. push and unshift return the new length; pop and shift return the removed item. These methods are fast and convenient, but because they mutate, they can cause subtle bugs when the same array is shared across different parts of your program.
flowchart LR
A["['a','b']"] -->|"push('c')"| B["['a','b','c']<br/>same object"]
A -->|"slice(0,2)"| C["['a','b']<br/>new object"]
style B fill:#b45309,color:#fff
style C fill:#3776ab,color:#fff
Searching and slicing without mutation
Not every operation should destroy the original. includes tells you whether a value is present. indexOf returns the position, or -1 when it isn't found — a convention borrowed from early C and still used everywhere:
const nums = [5, 12, 8, 130, 44];
nums.includes(8); // true
nums.indexOf(130); // 3
nums.indexOf(99); // -1
slice(start, end) copies a portion into a brand-new array. The end index is exclusive — slice(1, 3) grabs indexes 1 and 2 but stops before 3. Because these methods return new values, you can chain them safely: nums.slice(1).includes(130).
const nums = [5, 12, 8, 130, 44]; console.log(nums.includes(8)); console.log(nums.indexOf(130)); console.log(nums.slice(1, 3)); console.log(nums);
The shared-reference trap
Assigning an array to a new variable does not copy it — both names point to the exact same list in memory. This catches almost every beginner eventually:
const a = [1, 2];
const b = a;
b.push(3);
console.log(a); // [1,2,3] — a changed too!
If you need an independent copy, spread the old array into a new one: const b = [...a];. Now b.push(3) leaves a untouched. The spread syntax is shallow — nested arrays are still shared — but for flat lists it is the idiom you will see in every modern codebase.
Write evens(nums) that returns a new array containing only the even numbers from nums. (Use filter and the remainder operator %.)
function evens(nums) {
// return only the even numbers
}
A number is even when
n % 2 === 0.Pass that test to filter: nums.filter((n) => n % 2 === 0).
function evens(nums) {
return nums.filter((n) => n % 2 === 0);
}
What does this print? map doubles each number, then we check the result.
const nums = [1, 2, 3]; const doubled = nums.map((n) => n * 2); console.log(doubled); console.log(doubled.includes(4));
Doubling 1, 2, 3 gives 2, 4, 6.
4 is in the doubled array, so includes returns true.
Write sumArray(nums) that returns the sum of all numbers in nums. Use a for...of loop. If the array is empty, return 0.
function sumArray(nums) {
// return the total
}
Start a total at 0 and add each number as you loop.
A
for...ofloop gives you each element directly.
function sumArray(nums) {
let total = 0;
for (const n of nums) {
total += n;
}
return total;
}
This function is supposed to return a reversed copy of the array without changing the original. It fails because reversed is just another name for the same array. Fix it so the original stays untouched.
function reverseCopy(arr) {
const reversed = arr;
reversed.reverse();
return reversed;
}
Use the spread syntax to make a real copy before reversing.
const reversed = [...arr]; creates a new array with the same items.
function reverseCopy(arr) {
const reversed = [...arr];
reversed.reverse();
return reversed;
}
Recap
- Arrays store ordered lists; the first item is at index 0.
push,pop,shift, andunshiftmutate the array in place.filter,map,slice, andincludesreturn new values and leave the original alone.- Assigning an array to a new variable with
=does not copy it — both names point to the same list. Use[...arr]for a shallow copy.
Next you'll dive deeper into map, filter, and reduce: the trio that lets you process entire arrays without writing a loop by hand.