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.
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:
- Using
for...ofwithentries() - Using a classic
forloop - 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
[]orArray.from push/popat the end are fast;shift/unshiftat the start are slowslicereturns a new array;splicemutates in placefor...ofis the default iteration; useentries()when you need the indexmap,filter,reduceare 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
slicewhen 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.