JavaScript Zero to Advanced #5: Arrays & Array Methods
Almost every app works with lists: products in a cart, messages in a chat, rows from a database. In JavaScript, lists are stored in arrays. In this part you will learn how to create and modify arrays and, more importantly, how to transform them with the built-in methods that make modern JavaScript so expressive. Everything from Part 4 about callbacks and arrow functions pays off here.
Creating and accessing arrays
An array is an ordered list of values written between square brackets. Items are accessed by their index, which starts at 0:
const fruits = ["apple", "banana", "cherry"]
console.log(fruits[0])
console.log(fruits[2])
console.log(fruits[5])
console.log(fruits.length)
console.log(fruits.at(-1)) // last item
console.log(Array.isArray(fruits))
Output:
apple
cherry
undefined
3
cherry
true
Reading an index that does not exist gives undefined, not an error. at(-1) is a modern, readable way to get the last item.
Adding, removing and finding items
const queue = ["Budi", "Siti"]
queue.push("Andi") // add to the end
queue.unshift("Dewi") // add to the start
console.log(queue)
const last = queue.pop() // remove from the end
const first = queue.shift() // remove from the start
console.log(first, last, queue)
const colors = ["red", "green", "blue", "yellow"]
colors.splice(1, 2, "purple") // at index 1, remove 2, insert "purple"
console.log(colors)
console.log(colors.indexOf("yellow"))
console.log(colors.includes("blue"))
console.log(colors.slice(0, 2)) // copy without changing the original
console.log(colors.join(" | "))
Output:
[ 'Dewi', 'Budi', 'Siti', 'Andi' ]
Dewi Andi [ 'Budi', 'Siti' ]
[ 'red', 'purple', 'yellow' ]
2
false
[ 'red', 'purple' ]
red | purple | yellow
push,pop,shift,unshift,spliceandsortchange (mutate) the original array.slice,map,filter,concatandjoinreturn something new and leave the original untouched.
map: transform every item
map calls a function on each item and returns a new array of the same length with the results. The callback also receives the index as a second argument.
const prices = [10000, 25000, 40000]
const withTax = prices.map((price) => price * 1.1)
console.log(withTax)
const labels = prices.map((price, index) => `#${index + 1}: Rp${price}`)
console.log(labels)
const users = [
{ name: "Budi", age: 28 },
{ name: "Siti", age: 34 }
]
console.log(users.map((user) => user.name))
Output:
[ 11000, 27500.000000000004, 44000 ]
[ '#1: Rp10000', '#2: Rp25000', '#3: Rp40000' ]
[ 'Budi', 'Siti' ]
Notice 27500.000000000004: that is the floating point effect we saw in Part 2. For money, round with Math.round or toFixed before displaying it.
filter, find, some and every
filter keeps only the items for which the callback returns a truthy value. find returns the first matching item (or undefined). some and every answer yes/no questions.
const products = [
{ name: "Laptop", price: 12000000, inStock: true },
{ name: "Mouse", price: 150000, inStock: false },
{ name: "Keyboard", price: 450000, inStock: true },
{ name: "Monitor", price: 2500000, inStock: true }
]
const available = products.filter((p) => p.inStock)
console.log(available.map((p) => p.name))
const cheap = products.find((p) => p.price < 500000)
console.log(cheap)
const tablet = products.find((p) => p.name === "Tablet")
console.log(tablet)
console.log(products.some((p) => p.price > 10000000))
console.log(products.every((p) => p.inStock))
console.log(products.findIndex((p) => p.name === "Keyboard"))
Output:
[ 'Laptop', 'Keyboard', 'Monitor' ]
{ name: 'Mouse', price: 150000, inStock: false }
undefined
true
false
2
reduce: boil an array down to one value
reduce is the most flexible method. It walks through the array carrying an accumulator, and whatever the callback returns becomes the accumulator for the next item. The second argument is the starting value.
const cart = [
{ item: "Coffee", price: 25000, qty: 2 },
{ item: "Bread", price: 15000, qty: 3 },
{ item: "Milk", price: 18000, qty: 1 }
]
const total = cart.reduce((sum, line) => sum + line.price * line.qty, 0)
console.log(total)
// Count occurrences
const votes = ["yes", "no", "yes", "yes", "no"]
const tally = votes.reduce((acc, vote) => {
acc[vote] = (acc[vote] || 0) + 1
return acc
}, {})
console.log(tally)
Output:
113000
{ yes: 3, no: 2 }
Let's trace the total: start at 0, then 0 + 50000 = 50000, then 50000 + 45000 = 95000, then 95000 + 18000 = 113000. Always pass an initial value; without it, reduce uses the first item, and it throws an error on an empty array.
Chaining methods
Because map and filter return arrays, you can chain them into a readable data pipeline:
const orders = [
{ customer: "Budi", amount: 120000, status: "paid" },
{ customer: "Siti", amount: 80000, status: "pending" },
{ customer: "Andi", amount: 200000, status: "paid" },
{ customer: "Dewi", amount: 50000, status: "paid" }
]
const paidRevenue = orders
.filter((o) => o.status === "paid")
.map((o) => o.amount)
.reduce((sum, amount) => sum + amount, 0)
console.log(paidRevenue)
Output:
370000
sort and its traps
sort orders the array in place. By default it converts items to strings and compares them alphabetically, which gives surprising results for numbers:
const numbers = [10, 1, 25, 3, 100]
console.log([...numbers].sort())
const ascending = [...numbers].sort((a, b) => a - b)
const descending = [...numbers].sort((a, b) => b - a)
console.log(ascending)
console.log(descending)
console.log(numbers) // original untouched because we sorted copies
const people = [
{ name: "Siti", age: 34 },
{ name: "Budi", age: 28 },
{ name: "Andi", age: 41 }
]
const byAge = [...people].sort((a, b) => a.age - b.age)
console.log(byAge.map((p) => `${p.name} (${p.age})`))
const byName = [...people].sort((a, b) => a.name.localeCompare(b.name))
console.log(byName.map((p) => p.name))
Output:
[ 1, 10, 100, 25, 3 ]
[ 1, 3, 10, 25, 100 ]
[ 100, 25, 10, 3, 1 ]
[ 10, 1, 25, 3, 100 ]
[ 'Budi (28)', 'Siti (34)', 'Andi (41)' ]
[ 'Andi', 'Budi', 'Siti' ]
The compare function should return a negative number if a comes first, a positive number if b comes first, and 0 if they are equal. a - b does exactly that for numbers, and localeCompare does it for strings. The [...numbers] syntax makes a copy (we will cover spread in Part 6). Modern runtimes also offer toSorted(), which returns a sorted copy directly.
Other useful helpers
console.log([1, [2, [3, [4]]]].flat(2))
console.log(["a b", "c d"].flatMap((s) => s.split(" ")))
console.log(Array.from({ length: 5 }, (_, i) => i * i))
console.log([..."hello"].reverse().join(""))
console.log([3, 7, 2].forEach((n) => n * 2)) // forEach returns undefined
Output:
[ 1, 2, 3, [ 4 ] ]
[ 'a', 'b', 'c', 'd' ]
[ 0, 1, 4, 9, 16 ]
olleh
undefined
forEach just runs a function for each item and returns nothing. Use it for side effects such as logging; use map when you want a new array.
Common mistakes
- Forgetting to return in
map/filter.arr.map((x) => { x * 2 })gives an array ofundefined. - Sorting numbers without a compare function.
[10, 1, 3].sort()gives[1, 10, 3]. - Mutating when you did not mean to.
sort,reverseandsplicechange the original. Copy first if other code depends on it. - Using
filterwhen you need one item.findstops at the first match and returns the item, not an array. - Comparing arrays with
===.[1, 2] === [1, 2]isfalse, because arrays are compared by reference.
Exercise
Given an array of students such as { name: "Budi", score: 82 } (create at least five), write code that: (1) returns the names of students who passed (score 75 or more), (2) calculates the average score with reduce, (3) finds the first student who scored below 60, and (4) prints a ranking from the highest to the lowest score without modifying the original array.
Summary
- Arrays are zero-indexed lists;
length,at,includesandindexOfhelp you inspect them. push/pop/shift/unshift/splicemutate;slice,mapandfilterreturn new arrays.maptransforms,filterselects,findpicks one,some/everytest,reducecombines.- Always give
sorta compare function for numbers, and sort a copy if you need the original.
In Part 6 we will explore objects, destructuring, the spread and rest operators, and JSON.
Related articles
PostgreSQL Zero to Advanced #5: Aggregation, GROUP BY & HAVING
Turn raw orders into business numbers: total revenue, average order value, sales per month and top customers using COUNT, SUM, AVG, GROUP BY, HAVING and FILTER.
PHP Zero to Advanced #5: Arrays & Array Functions
Master PHP arrays: indexed and associative arrays, nested data, destructuring, and the essential functions array_map, array_filter, array_reduce, usort, and friends.
Ruby on Rails Zero to Advanced #5: Routing, Controllers & Views
Follow a request through Rails: define routes, use resources, read params in controllers, and render dynamic HTML with ERB views and partials.