Skip to content
C Codeloom
JavaScript

JavaScript Arrays: Create, Iterate, Transform

A complete beginner's guide to JavaScript arrays — creation, indexing, the essential mutation methods, iteration, map/filter/reduce, and a first look at spread and destructuring.

·10 min read · By Yash Kesharwani
Beginner 11 min read

What you'll learn

  • Every way to create an array
  • Indexing, length, and the gotchas of holes
  • The mutation methods you will use constantly
  • How to iterate cleanly with for-of, forEach, and friends
  • map, filter, reduce — the holy trinity of array work
  • Why "copying" matters and how to do it safely

Prerequisites

  • JavaScript strings — see Strings

An array is an ordered, mutable collection of values. It is the most-used container type in JavaScript, and once you are comfortable with arrays, large parts of the standard library and most third-party data tools become much easier.

Creating an array

The most common way to create an array is with square brackets:

const fruits = ["apple", "banana", "cherry"];
const numbers = [1, 2, 3, 4, 5];
const empty = [];

An array can hold values of any type, including a mix:

const mixed = [1, "two", 3.0, true, null];

Mixing types is legal but rare in good code — most arrays hold values of the same kind.

You can also create an array from any iterable using Array.from:

Array.from("hello");        // ['h', 'e', 'l', 'l', 'o']
Array.from({ length: 5 }, (_, i) => i);   // [0, 1, 2, 3, 4]

Or use the spread operator on an iterable:

const chars = [..."hello"];   // ['h', 'e', 'l', 'l', 'o']

For a pre-sized array of a single value:

new Array(3).fill(0);         // [0, 0, 0]

Indexing and length

Arrays use zero-based indexing, exactly like strings:

const fruits = ["apple", "banana", "cherry"];
console.log(fruits[0]);        // 'apple'
console.log(fruits[2]);        // 'cherry'
console.log(fruits.at(-1));    // 'cherry'  — like Python's [-1]
console.log(fruits.length);    // 3

Reading past the end gives undefined, not an error:

console.log(fruits[10]);       // undefined

Writing past the end expands the array, creating empty holes:

const a = [1, 2, 3];
a[6] = 99;
console.log(a);                // [ 1, 2, 3, <3 empty items>, 99 ]
console.log(a.length);         // 7

Avoid this pattern. Build arrays with push, not by writing to far-off indices.

Arrays are mutable — and const doesn’t protect them

A reminder from the variables post:

const fruits = ["apple", "banana"];
fruits.push("cherry");         // fine — mutates the array
fruits[0] = "apricot";         // fine — mutates an element
console.log(fruits);           // ['apricot', 'banana', 'cherry']

fruits = ["pear"];             // TypeError — reassignment

const prevents the binding from changing, not the array’s contents.

Adding and removing items

The everyday mutation methods you’ll reach for constantly.

Ends — fast

const a = [1, 2, 3];
a.push(4);            // add to the end
console.log(a);       // [1, 2, 3, 4]

const last = a.pop(); // remove & return last
console.log(last, a); // 4 [1, 2, 3]

Starts — slower (everything has to shift)

const a = [1, 2, 3];
a.unshift(0);            // add to the start
console.log(a);          // [0, 1, 2, 3]

const first = a.shift(); // remove & return first
console.log(first, a);   // 0 [1, 2, 3]

Anywhere — splice

splice(start, deleteCount, ...items) is the Swiss army knife. It mutates the array in place:

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

a.splice(2, 1);              // remove 1 item starting at index 2
console.log(a);              // [1, 2, 4, 5]

a.splice(1, 0, "x", "y");    // insert at index 1 without deleting
console.log(a);              // [1, 'x', 'y', 2, 4, 5]

a.splice(0, 2, "first");     // replace first two items with 'first'
console.log(a);              // ['first', 'y', 2, 4, 5]

splice is powerful but easy to misuse. For most needs, push, pop, filter, and spread are cleaner.

Slicing

slice(start, end) returns a new array containing a portion — it doesn’t mutate the original:

const a = [10, 20, 30, 40, 50];
console.log(a.slice(1, 4));    // [20, 30, 40]
console.log(a.slice(2));       // [30, 40, 50]
console.log(a.slice(-2));      // [40, 50]
console.log(a);                // unchanged: [10, 20, 30, 40, 50]

The mnemonic that helps everyone: slice returns, splice mutates.

Searching

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

nums.includes(20);    // true
nums.indexOf(20);     // 1   — first match
nums.lastIndexOf(20); // 3
nums.indexOf(99);     // -1  — not found

For complex matches, use find or findIndex with a callback:

const users = [
  { name: "Ada", admin: false },
  { name: "Linus", admin: true },
];

users.find((u) => u.admin);          // { name: 'Linus', admin: true }
users.findIndex((u) => u.admin);     // 1

Iterating over an array

There are four common iteration styles. Pick by what you actually need.

for…of — the modern default

const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
  console.log(fruit);
}

Clean, readable, works with any iterable (arrays, strings, Maps, Sets).

for…of with entries() — when you need the index

for (const [i, fruit] of fruits.entries()) {
  console.log(i, fruit);
}

Classic for loop — when you need fine control

for (let i = 0; i < fruits.length; i++) {
  console.log(i, fruits[i]);
}

Useful when you need to skip elements, iterate backwards, or stop early.

forEach — functional style

fruits.forEach((fruit, i) => {
  console.log(i, fruit);
});

Note: you cannot break or return early from forEach. If you need that, use for...of.

A warning about for...in: it iterates over keys (which on an array are stringified indices) and includes inherited properties. Use it for plain objects, not arrays.

Try it yourself. Given const scores = [82, 47, 91, 56, 73];, write three versions of code that prints each score with its index:

  1. Using for...of with entries()
  2. Using a classic for loop
  3. Using forEach

Pick the one you find most readable. That’s the one you should reach for first.

map, filter, reduce — the holy trinity

These three methods cover most data-shaping work. Each returns a new array (or value) and leaves the original untouched.

map — transform every element

const nums = [1, 2, 3, 4];
const squares = nums.map((n) => n * n);
console.log(squares);          // [1, 4, 9, 16]

const names = ["ada", "linus", "grace"];
const titled = names.map((n) => n[0].toUpperCase() + n.slice(1));
console.log(titled);           // ['Ada', 'Linus', 'Grace']

filter — keep elements that pass a test

const nums = [1, 2, 3, 4, 5, 6];
const evens = nums.filter((n) => n % 2 === 0);
console.log(evens);            // [2, 4, 6]

const users = [
  { name: "Ada", active: true },
  { name: "Linus", active: false },
  { name: "Grace", active: true },
];
const activeNames = users.filter((u) => u.active).map((u) => u.name);
console.log(activeNames);      // ['Ada', 'Grace']

reduce — collapse to a single value

const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => acc + n, 0);
console.log(sum);              // 10

const product = nums.reduce((acc, n) => acc * n, 1);
console.log(product);          // 24

The callback receives the running accumulator and the current item, and returns the new accumulator. The second argument to reduce is the starting value.

reduce is the most powerful and the most often misused of the three. If a for...of loop is clearer, prefer the loop.

Useful companions

nums.some((n) => n > 10);      // false  — true if any pass
nums.every((n) => n > 0);      // true   — true if all pass
nums.flat();                   // flattens one level of nested arrays
nums.flatMap((n) => [n, n]);   // map then flatten one level

Sorting

sort() mutates the array in place. By default it converts items to strings, which gives surprising results for numbers:

const a = [10, 2, 33, 4];
a.sort();
console.log(a);     // [10, 2, 33, 4] sorted as strings → [10, 2, 33, 4]

For numeric sort, pass a comparator:

const a = [10, 2, 33, 4];
a.sort((x, y) => x - y);
console.log(a);     // [2, 4, 10, 33]

The comparator returns a negative number if x should come first, positive if y should, zero for equal. To sort by a property:

const users = [{ age: 30 }, { age: 18 }, { age: 25 }];
users.sort((a, b) => a.age - b.age);

For a sorted copy that doesn’t mutate, use the modern toSorted (Node 20+, modern browsers):

const a = [3, 1, 2];
const b = a.toSorted();
console.log(a, b);            // [3, 1, 2] [1, 2, 3]

Spread and destructuring

Two modern shorthands you’ll see constantly.

Spread — expand an iterable into elements

const a = [1, 2, 3];
const b = [0, ...a, 4];           // [0, 1, 2, 3, 4]

const merged = [...a, ...[10, 20]]; // [1, 2, 3, 10, 20]

Math.max(...[3, 1, 4, 1, 5]);     // 5

const copy = [...a];               // shallow copy

Destructuring — unpack into variables

const [first, second, ...rest] = [10, 20, 30, 40, 50];
console.log(first);    // 10
console.log(second);   // 20
console.log(rest);     // [30, 40, 50]

const [a, , c] = [1, 2, 3];   // skip the middle
console.log(a, c);            // 1 3

Copying an array

Assigning an array does not copy it. Both names point to the same underlying array:

const a = [1, 2, 3];
const b = a;
b.push(4);
console.log(a);    // [1, 2, 3, 4] — surprise

For an actual (shallow) copy, any of these work:

const b = [...a];
const b = a.slice();
const b = Array.from(a);

All three produce a new outer array. Nested objects inside are still shared. For deep copies, use structuredClone(a) (modern Node and browsers).

Try it yourself. Create an array of three names. Assign it to a second variable with =, push to the second, and observe what happens to the first. Then repeat using [...] to copy and confirm the original stays intact.

A small worked example

Putting it all together — average a list of scores, find the top scorer, and list the passers.

const names = ["Alice", "Bob", "Carol", "Dave", "Eve"];
const scores = [82, 47, 91, 56, 73];

const average = scores.reduce((s, n) => s + n, 0) / scores.length;

const topIndex = scores.indexOf(Math.max(...scores));
const topName = names[topIndex];

const passers = names.filter((_, i) => scores[i] >= 60);

console.log(`Average: ${average.toFixed(1)}`);
console.log(`Top scorer: ${topName} with ${scores[topIndex]}`);
console.log(`Passers: ${passers.join(", ")}`);

Every concept in this post appears in those few lines. This is the standard texture of real JavaScript.

Recap

You now know:

  • Arrays are ordered, mutable collections created with [] or Array.from
  • push/pop at the end are fast; shift/unshift at the start are slow
  • slice returns a new array; splice mutates in place
  • for...of is the default iteration; use entries() when you need the index
  • map, filter, reduce are the everyday transformation tools
  • Sort numbers with a comparator: arr.sort((a, b) => a - b)
  • Spread ([...a]) and destructuring make copying, merging, and unpacking concise
  • Assigning an array does not copy it — use spread or slice when you need a real copy

Next steps

Arrays are ordered collections accessed by integer index. The other essential container in JavaScript is the object — an unordered collection accessed by string key.

→ Next: JavaScript Objects: The Most Useful Data Structure

Questions or feedback? Email codeloomdevv@gmail.com.