When choosing a JavaScript array API, ask two questions first: do you want a new array or do you want to mutate the existing one? Do you need early exit or sequential await? map, filter, and slice return new arrays. splice, sort, and reverse mutate the array. When you need break, continue, or sequential async work, use for...of.
Mutating vs non-mutating methods
| Returns new value or array | Mutates the original array |
|---|---|
map, filter, slice, concat |
push, pop, shift, unshift |
toSorted, toReversed, toSpliced |
sort, reverse, splice |
find, some, every, reduce |
fill, copyWithin |
In React state, reducers, and data pipelines, prefer non-mutating methods unless mutation is deliberate and local.
Add and remove items
const tools = ["Raycast", "IINA"]
tools.push("AppCleaner")
const last = tools.pop()
Those methods modify tools. To keep the original array unchanged:
const withBrew = [...tools, "Homebrew"]
const withoutIINA = tools.filter((tool) => tool !== "IINA")
This matters when references are used to detect updates.
slice vs splice
slice(start, end) returns a new array for the half-open range [start, end):
const values = [0, 1, 2, 3, 4]
values.slice(1, 4) // [1, 2, 3]
values // [0, 1, 2, 3, 4]
splice(start, deleteCount, ...items) mutates the array and returns removed items:
const values = ["a", "b", "c"]
const removed = values.splice(1, 1, "B", "B2")
removed // ["b"]
values // ["a", "B", "B2", "c"]
Modern runtimes also provide toSpliced for a non-mutating version:
const next = values.toSpliced(1, 1, "new")
map, filter, and reduce
Use map to transform each item:
const names = tools.map((tool) => tool.name)
Use filter to keep items that match a condition:
const freeTools = tools.filter((tool) => tool.price === 0)
Use reduce to collapse a list into one result:
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
If the reducer callback needs many comments, a plain loop may be clearer. Do not compress business logic into reduce just to look functional.
find, some, and every
const iina = tools.find((tool) => tool.slug === "iina")
const hasFreeTool = tools.some((tool) => tool.price === 0)
const allAvailable = tools.every((tool) => tool.available)
findreturns the first matching item orundefined.somestops once it findstrue.everystops once it findsfalse.
If you only need an existence check, do not filter the whole array and then check .length.
sort and toSorted
sort mutates the original array, and its default comparison is string-based:
[10, 2, 30].sort() // [10, 2, 30]
For numeric sorting:
const ascending = numbers.toSorted((a, b) => a - b)
If toSorted is unavailable:
const ascending = [...numbers].sort((a, b) => a - b)
Comparison functions should be stable and deterministic. Avoid random comparison functions.
for, forEach, for...of, and for...in
Use for when you need indexes, exact stepping, or two-pointer logic:
for (let index = 0; index < items.length; index += 1) {
if (items[index].hidden) continue
render(items[index])
}
Use forEach for simple synchronous side effects, but remember that you cannot break and it does not await async callbacks:
items.forEach((item) => console.log(item.id))
Use for...of for iterables, early exit, and sequential async work:
for (const item of items) {
if (!item.valid) break
await save(item)
}
for...in iterates enumerable string property names and is not the right default for arrays.
Quick selection table
| Need | Use |
|---|---|
| Transform each item | map |
| Keep matching items | filter |
| Find the first item | find |
| Check whether any item matches | some |
| Aggregate into one result | reduce or a clear loop |
| Copy a range | slice |
| Insert or remove in place | splice |
Need break, continue, or sequential await |
for...of |
| Need index control | for |
Related FreeMac guides
- For destructuring and copying objects, read JavaScript Object Copy: Destructuring, Shallow Copy, and Deep Copy.
- For safe optional access, read JavaScript Optional Chaining: When to Use
?.. - For async ordering, read JavaScript Event Loop: Tasks, Microtasks, and Rendering.
Continue reading
JavaScript Event Loop: Tasks, Microtasks, and Rendering
Understand the browser event loop through the call stack, tasks, microtasks, Promise callbacks, queueMicrotask, async/await, rendering opportunities, and long tasks.
7 min readWhy var and let Behave Differently in for Loops
Use the classic JavaScript closure example to understand why var in a for loop prints 3 3 3, while let creates per-iteration bindings and prints 0 1 2.
8 min readJavaScript var vs let: Function Scope, Block Scope, and Closures
Understand the difference between var and let in JavaScript through function scope, block scope, for loops, closures, and why let avoids the classic 3 3 3 result.
Subscribe to FreeMac
Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.