Mid-level 5 min · March 06, 2026

JavaScript Array Methods — Async forEach Silent Failures

forEach with async callbacks fired 100 API calls at once, silently dropping 15% to rate limits.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • map transforms every element (one-to-one) → returns new array. Use for data shape conversion (extract IDs, format dates).
  • filter selects elements passing a test → returns new array (0 to original length). Use for removing falsy values, filtering by condition.
  • reduce accumulates to single value → returns anything. Always provide initial value. Without it, empty array throws error.
  • forEach fires async callbacks without awaiting → use for-of for sequential, Promise.all for parallel.
  • Performance: chaining 3 methods on 100k items creates 3 intermediate arrays (memory pressure). Single reduce does one pass.
  • React/Redux requires immutability: sort mutates original. Use toSorted() or spread: [...arr].sort().
  • Biggest production mistake: forEach with async functions — notifications lost, data incomplete, no errors logged.
Plain-English First

Imagine you have a basket of fruit. Array methods are the different things you can DO with that basket — you can count the fruit, remove the ones that are rotten, transform each piece into juice, or find a specific one. JavaScript arrays are just that basket, and array methods are your toolkit for working with everything inside it. You don't have to manually dig through the basket one piece at a time — these methods do the heavy lifting for you.

Every real-world JavaScript application works with lists of data. A shopping cart is a list of products. A news feed is a list of posts. If you can't confidently manipulate lists, you'll hit a wall almost immediately.

Array methods are the single most-used feature in modern JavaScript. Senior devs use them every day — not because they're fancy, but because they express intent clearly. items.filter(available).map(price).reduce(sum) reads like a sentence.

Before them you wrote manual loopsfor (let i = 0; i < arr.length; i++). That's ceremony, not meaning. Map, filter, and reduce are the 3 rules that changed that. This article covers the methods, their traps (async forEach, missing initial value), and the performance trade-offs that matter in production.

map — Transform Every Element

Creating a new array by transforming every element of an existing array: const squares = numbers.map(x => x * x). The callback receives three arguments: (currentValue, index, array). Only the first is required. map always returns a new array of the same length as the original. It never mutates the original array. Use map when you need a one-to-one transformation — every input element produces exactly one output element.

The most common pitfall? Forgetting to assign the result. arr.map(x => x*2) on its own line does nothing — the new array is created and immediately discarded. It's not a bug that throws an error, it's a bug that silently does nothing.

Another trap: using map for side effects. If the callback doesn't return a value, the new array is filled with undefined. You still get an array of the same length, just useless. Use forEach for side effects and map for transformation — separate concerns.

Senior devs reach for map when they need to convert one data shape to another — like extracting IDs from objects or formatting dates. If you're doing anything else, step back and check if map is the right tool.

io/thecodeforge/javascript/arrayMapExample.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// TheCodeForge — map example
// Transform every element in an array

const prices = [10, 20, 30, 40, 50];

// Add 10% tax to every price (one-to-one transformation)
const withTax = prices.map(price => price * 1.10);
console.log('Original:', prices);
console.log('With tax:', withTax);

// Extract specific field from objects
const users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
  { id: 3, name: 'Charlie', email: 'charlie@example.com' }
];
const names = users.map(user => user.name);
console.log('User names:', names);

// map with index — useful for JSX keys in React
const indexed = users.map((user, index) => `${index + 1}. ${user.name}`);
console.log('Indexed users:', indexed);

// BAD: using map when you don't need the return value (use forEach instead)
// const discarded = users.map(user => sendEmail(user)); // wrong
// users.forEach(user => sendEmail(user)); // correct
map Returns a New Array — Don't Forget to Assign It!
  • map returns a new array — it does NOT modify the original.
  • Always assign the result: const doubled = numbers.map(n => n * 2);
  • map without assignment is a no-op (but still iterates the array).
  • map callback must return a value. If you don't return, the new array element is undefined.
  • map expects one-to-one transformation. For filtering, use filter, not map.
Production Insight
map always returns an array of the same length as the original. For conditional inclusion, use filter, not map.
Returning undefined from map callback still creates an array element with undefined.
I once saw a codebase where every map call was followed by a filter(Boolean) because the dev thought map could skip elements. It can't.
Rule: Use map for one-to-one transformation. Use filter for selection. Use reduce for accumulation.
Key Takeaway
map transforms every element: input array length = output array length.
Always assign the result — map does NOT modify the original array.
Rule: map is for transformation, not iteration with side effects. For side effects, use forEach.

filter — Keep Only What You Need

Creating a new array containing only elements that pass a test: const adults = users.filter(user => user.age >= 18). The callback should return true to keep the element, false to discard it. filter never mutates the original array. It returns a new array that may be shorter than the original (or empty). Use filter when you need to exclude elements based on a condition. Common use cases: removing falsy values (filter(Boolean)), filtering by property, or searching with a predicate.

The one-liner filter(Boolean) is a classic trick. It removes null, undefined, 0, false, NaN, and empty strings. But know the edge: if 0 is a valid value in your array, filter(Boolean) silently removes it. That's a bug that only shows when a valid zero appears. Use explicit conditions for production code.

Chain filter before map wherever possible. If you filter first, map processes fewer elements. That's free performance, no downside.

One more thing: filter doesn't stop early. If you're looking for a single element, use find instead — it returns on the first match and stops iterating.

io/thecodeforge/javascript/arrayFilterExample.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// TheCodeForge — filter example
// Keep only elements that pass a condition

const products = [
  { name: 'Laptop', price: 1200, inStock: true },
  { name: 'Mouse', price: 25, inStock: false },
  { name: 'Keyboard', price: 75, inStock: true },
  { name: 'Monitor', price: 350, inStock: true },
  { name: 'Desk', price: 450, inStock: false }
];

// Keep only in-stock products
const available = products.filter(product => product.inStock);
console.log('In-stock products:', available);

// Keep only products under $100
const affordable = products.filter(product => product.price < 100);
console.log('Affordable products:', affordable);

// Common pattern: remove falsy values
const mixed = [0, 'hello', false, 42, null, 'world', undefined];
const truthy = mixed.filter(Boolean); // keeps 'hello', 42, 'world'
console.log('Truthy values:', truthy); // [ 'hello', 42, 'world' ]

// Filter by text search
const searchTerm = 'key';
const matches = products.filter(product =>
  product.name.toLowerCase().includes(searchTerm.toLowerCase())
);
console.log('Products matching "key":', matches); // [ { name: 'Keyboard', ... } ]
filter(Boolean) Removes Falsy Values
  • filter returns a new array — does NOT modify the original.
  • Callback returns true to keep, false to discard.
  • filter(Boolean) removes all falsy values (0, null, undefined, false, NaN, '').
  • Chain filter before map for better performance (fewer elements to transform).
Production Insight
filter(Boolean) is a common pattern, but it removes valid numeric 0 and false booleans.
If your data legitimately includes 0 or false, use explicit condition: filter(item => item !== null && item !== undefined).
I saw a dashboard that filtered out all users with score=0 — they lost half the data.
Rule: When chaining filter and map, put filter first — it reduces the number of elements map must process, improving performance.
Key Takeaway
filter selects elements based on a condition: true = keep, false = discard.
Always assign the result — filter does NOT modify the original.
Rule: Use filter for selection, map for transformation, reduce for accumulation. Each has a distinct job.

find, some, every — Condition Checks Without New Arrays

Not every operation needs a new array. find returns the first element that matches a condition — or undefined if none match. some returns true if at least one element passes the test. every returns true only if all elements pass. These three are your go-tos for boolean checks and single-element lookups.

The key difference from filter: they stop early. find returns the first match and stops iterating. some stops at the first truthy callback return. every stops at the first falsy callback return. That's a performance win for large arrays — don't use filter when you only need one element or a boolean.

Common mistake: using filter(...).length > 0 instead of some(...). filter creates an entire new array just to check if it's non-empty. That's wasteful.

io/thecodeforge/javascript/arrayFindSomeEvery.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// TheCodeForge — find, some, every examples

const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Charlie', role: 'user' },
  { id: 4, name: 'Diana', role: 'moderator' }
];

// find: first admin
const admin = users.find(user => user.role === 'admin');
console.log('First admin:', admin); // { id: 1, ... }

// some: is there at least one moderator?
const hasModerator = users.some(user => user.role === 'moderator');
console.log('Has moderator?', hasModerator); // true

// every: are all roles defined?
const allHaveRoles = users.every(user => user.role !== undefined);
console.log('All have roles?', allHaveRoles); // true

// Performance trap: don't do this
const hasAdmin = users.filter(u => u.role === 'admin').length > 0; // bad
const hasAdminBetter = users.some(u => u.role === 'admin'); // good
Don't Use filter When You Mean find or some
  • find returns the first matching element or undefined — stops at first match.
  • some returns true if at least one element passes — stops at first truthy return.
  • every returns true only if all elements pass — stops at first falsy return.
  • All three short-circuit: performance win over filter for single results.
  • findIndex returns the index of the first match — useful when you need the position.
Production Insight
When checking if an array contains something, reach for some not filter(...).length.
I once saw a production pipeline that called filter on a 200k-item array every second just to check existence. CPU high, GC pauses, app slow. Replacing with some cut response time by 40%.
Rule: If you only need a boolean or the first element, use some/find/every — not filter.
Key Takeaway
find returns the first match or undefined. some returns true if any match. every returns true if all match.
All three short-circuit: they stop iterating as soon as they have an answer.
Rule: Use find/some/every for existence checks. Use filter only when you need all matching elements.

reduce — Accumulate to a Single Value

reduce is the Swiss Army knife of array methods. It iterates through the array, maintaining an accumulator value, and returns a single result. The callback receives (accumulator, currentValue, index, array) and returns the new accumulator value. reduce also takes an initial value as the second argument.

Use reduce for: summing numbers, flattening arrays, grouping objects by property, or building complex data structures from arrays. It's more powerful than map or filter, but also more complex. If map or filter can do the job, use them instead.

Always provide an initial value. Without it, reduce uses the first element as the initial accumulator and starts from the second element — and throws TypeError if the array is empty. This is a common production bug.

The initial value also determines the accumulator type. Start with 0 for sums, [] for arrays, {} for objects, '' for strings.

io/thecodeforge/javascript/arrayReduceExample.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// TheCodeForge — reduce example
// Accumulate array into a single value

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

// Sum all numbers (initial value 0)
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log('Sum:', sum); // 15

// Find maximum value
const max = numbers.reduce((acc, curr) => Math.max(acc, curr), -Infinity);
console.log('Max:', max); // 5

// Flatten array of arrays
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, curr) => acc.concat(curr), []);
console.log('Flattened:', flat); // [1, 2, 3, 4, 5, 6]

// Group objects by property
const people = [
  { name: 'Alice', city: 'New York' },
  { name: 'Bob', city: 'London' },
  { name: 'Charlie', city: 'New York' },
  { name: 'Diana', city: 'London' }
];

const byCity = people.reduce((acc, person) => {
  const city = person.city;
  if (!acc[city]) acc[city] = [];
  acc[city].push(person);
  return acc;
}, {});
console.log('Grouped by city:', byCity);

alert(byCity);

// Count occurrences
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const counts = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});
console.log('Fruit counts:', counts); // { apple: 3, banana: 2, orange: 1 }
Always Provide an Initial Value
  • reduce returns a single value, not necessarily an array.
  • Always provide initial value — even if you think the array is never empty.
  • Initial value sets the type and starting point of the accumulator.
  • Empty array + no initial value = TypeError: Reduce of empty array with no initial value.
  • For complex accumulators (objects, arrays), return the same accumulator reference each iteration.
Production Insight
reduce without initial value crashed a dashboard when an API returned an empty array. The team assumed the array would never be empty. A maintenance change added a new data source that sometimes returned no results.
Fix: Always provide initial value: items.reduce(callback, initialValue). There is no valid reason to omit it in production.
I've seen this same bug in three different codebases. Always. Provide. Initial. Value.
Rule: Never omit the initial value. The one character saved is not worth the crash.
Key Takeaway
reduce accumulates an array into a single value — sum, object, array, string, or anything else.
Always provide an initial value. Without it, reduce throws on empty arrays and skips the first element otherwise.
Rule: reduce is powerful but complex. Prefer map/filter for simple transformations. Use reduce only when output shape differs from input.

Immutability: Which Methods Mutate and Which Don't

JavaScript array methods fall into two camps: those that mutate the original array and those that return a new one. Getting this wrong is one of the most common sources of bugs in production.

Mutating methods: sort(), reverse(), splice(), push(), pop(), shift(), unshift(), fill(), copyWithin(). These change the array in place. If you called sort() on an array and later use that same array expecting its original order, you'll get a nasty surprise.

Non-mutating (immutable) methods: map(), filter(), reduce(), slice(), concat(), flat(), flatMap(), toSorted(), toReversed(), toSpliced() (ES2023). These return a new array. They never touch the original.

The trap: many devs assume sort() returns a new array — it doesn't. It mutates and also returns the same reference. So const sorted = arr.sort() — now both arr and sorted point to the same mutated array.

In React and Redux, immutability is critical. If you mutate state directly, React may not detect the change and re-render. Always create a copy before mutating: [...arr].sort() or use the new ES2023 methods: toSorted(), toReversed(), toSpliced().

io/thecodeforge/javascript/immutabilityExample.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// TheCodeForge — immutability check

const original = [3, 1, 2];

// Mutating: sort changes original
const sorted = original.sort();
console.log('Same reference?', original === sorted); // true
console.log('Original is now sorted:', original); // [1, 2, 3]

// Immutable: toSorted returns new array
const original2 = [3, 1, 2];
const sorted2 = original2.toSorted();
console.log('Same reference?', original2 === sorted2); // false
console.log('Original unchanged:', original2); // [3, 1, 2]
console.log('New sorted:', sorted2); // [1, 2, 3]

// Safe mutation pattern for React/Redux
const data = [5, 2, 8];
const sortedCopy = [...data].sort((a, b) => a - b);
console.log('Original unchanged:', data); // [5, 2, 8]
console.log('Sorted copy:', sortedCopy); // [2, 5, 8]
The Immutability Mental Model
  • Methods that return a new array: safe to chain, no side effects.
  • Methods that mutate: use only when you explicitly want to modify in place.
  • React state must be immutable — never mutate, always spread or use immutable methods.
  • Copy before mutating: [...arr].sort() or arr.slice().sort().
  • ES2023 added toSorted(), toReversed(), toSpliced() — use them for immutable operations.
Production Insight
A React component was sorting its state array with sort() directly. The sorted array was stored in state, but the original reference was also used elsewhere. Every sort triggered double renders and data corruption.
In Redux reducers, mutating state is the #1 cause of silent bugs. The reducer returns the same reference, so React doesn't re-render.
Rule: Assume all array methods mutate unless you know otherwise. In React/Redux, create copies before mutating.
Key Takeaway
sort, reverse, splice mutate. map, filter, reduce, slice, concat do not.
Always create a copy before mutating if you need the original.
Rule: In React and Redux, never mutate arrays. Use spread or immutable methods to create new references.

Chaining Methods and Performance

One of the biggest advantages of array methods is chaining: data.filter(...).map(...).reduce(...). It reads like a pipeline — filter out what you don't need, transform what remains, then aggregate. No intermediate variables. It's expressive.

But each method call creates a new array. A three-method chain creates three intermediate arrays. For small arrays (<1000 elements) the overhead is negligible. For arrays with 100,000+ elements, that's three full copies of the data in memory at once. Memory spikes, GC pressure, slower execution.

The fix: for large datasets, use a single reduce or a for loop. reduce can combine filtering and transformation in one pass: items.reduce((acc, item) => { if (item.active) acc.push({name: item.name, score: item.score * 2}); return acc; }, []) This does filter+map in one pass — one array, not three.

Another performance trap: chaining sort after filter or map. sort mutates in place, but the preceding methods create new arrays. The sort mutates the last intermediate array. If you need to preserve the original order, copy before sort.

A practical rule: profile first. If your data is small, readability wins. Only optimise when you see a bottleneck.

io/thecodeforge/javascript/chainingPerformance.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// TheCodeForge — chaining vs single reduce

const data = [/* imagine 100,000 items with {active, name, score} */];

// CHAIN: creates 3 arrays in memory
const result = data
  .filter(item => item.active)
  .map(item => ({ name: item.name, doubledScore: item.score * 2 }))
  .reduce((acc, item) => {
    acc[item.name] = item.doubledScore;
    return acc;
  }, {});

// ONE PASS: same logic, one iteration, one output array
const resultOnePass = data.reduce((acc, item) => {
  if (item.active) {
    acc[item.name] = item.score * 2;
  }
  return acc;
}, {});

// Which is faster? For 100k items, the one-pass version is ~2-3x faster.
// For 10 items, the chain is more readable. Write for readability first,
// then profile and optimise the hot paths.
Performance Rule of Thumb
Below 1000 elements: prefer readability (chaining). Above 10,000 elements: consider single reduce or for loop. Test in your own environment; data shapes vary.
Production Insight
A real-time dashboard chained filter → map → sort on a 500k-item array every 5 seconds. Memory usage climbed until the browser tab crashed.
The fix: reduce the data on the server side and only send the processed subset.
If you must process large arrays client-side, use a single reduce or a for loop. And always measure: console.time('process') / console.timeEnd('process').
Rule: Write readable chains first, then profile. Only optimise when you have evidence of a bottleneck.
Key Takeaway
Chaining methods creates intermediate arrays — fine for small data, costly for large.
Use single reduce or for loop for large arrays (>10k elements) to avoid multiple passes and memory overhead.
Rule: Profiling before optimisation. Readable code wins unless proven otherwise.
● Production incidentPOST-MORTEMseverity: high

The Async forEach That Lost 15% of Notifications

Symptom
Payment logs showed transactions succeeded, but customer support ticket volume about missing confirmations spiked. No errors in server logs. The notification queue drained successfully according to monitoring, but customers weren't receiving emails.
Assumption
The team assumed forEach would wait for each async callback to complete before moving to the next. They didn't know forEach fires callbacks and moves on without awaiting anything. They also assumed try/catch inside the callback would catch any error.
Root cause
The notification service used transactionIds.forEach(async (id) => { await sendNotification(id); }). forEach does NOT wait for Promises. All 100 notification API calls fired simultaneously, overloading the notification service (rate limiting). The service started rejecting requests with 429 Too Many Requests. But the rejection errors were inside the async callback, not propagated to the outer scope. No uncaught exception handler ran. The processing loop continued as if nothing happened. The team had no visibility into the 15% of notifications that failed.
Fix
1. Replaced forEach with sequential processing: for (const id of transactionIds) { await sendNotification(id); } 2. Added retry with exponential backoff and dead-letter queue for persistent failures. 3. Switched to Promise.allSettled for parallel but failure-tolerant batch processing. 4. Added explicit error logging for every notification attempt, regardless of success. 5. Created a CloudWatch alarm on DLQ depth > 0 to page on-call for any notification failure. After the fix, all notifications were either delivered or explicitly logged to DLQ, and the team could monitor the dead-letter queue for failures.
Key lesson
  • forEach with async callbacks is NOT sequential and does NOT wait. Use for-of loop with await for sequential async operations.
  • Errors inside forEach callbacks are silently ignored by the outer scope. Always wrap Promise-based operations with .catch() or try/catch inside the callback.
  • Promise.all fires all promises simultaneously. Use Promise.allSettled for failure-tolerant batch processing.
  • In production, assume any async operation can fail. Implement retries, dead-letter queues, and explicit monitoring for batch jobs.
Production debug guideSymptom → Action mapping for common array method failures in production JavaScript applications.5 entries
Symptom · 01
Data processing skipped some items — missing array elements in result
Fix
Check if your callback uses index incorrectly for filtering. filter returns a new array; if you mutate the original array while filtering, you get unexpected results. Also check for off-by-one errors in custom predicates. Add console.log inside filter callback to see which items are rejected.
Symptom · 02
No console logs or errors appear when an array method's callback throws an exception
Fix
For forEach, exceptions inside the callback do not stop the loop or propagate to outer try/catch. Wrap each callback iteration in own try/catch and log errors explicitly. For map, an exception in one callback stops the entire operation. Use Promise.allSettled for error-tolerant mapping of async operations.
Symptom · 03
Original array unexpectedly changed after calling an array method
Fix
Methods like sort, reverse, splice, and push mutate the original array. Methods like map, filter, reduce, slice, concat return new arrays and do not mutate. If you intended immutability, use the non-mutating version or copy first: [...arr].sort().
Symptom · 04
Array method inside React component causes unnecessary re-renders
Fix
map, filter, reduce return new array references on every call. In React, a new array prop triggers re-render even if contents are identical. Use useMemo to memoize derived arrays: const processed = useMemo(() => items.map(f), [items]).
Symptom · 05
reduce returns undefined or incorrect initial value
Fix
Always provide initial value for reduce, especially when reducing an empty array. Without initial value and empty array, reduce throws TypeError. The initial value also determines the type of the accumulator. For objects, start with {}; for arrays, start with []; for numbers, start with 0.
★ JavaScript Array Debug Cheat SheetFast diagnostics for array-related issues in production JavaScript. Run these checks before refactoring.
Data silently missing from array after processing
Immediate action
Log the array before and after the operation, plus the filter/map predicate results
Commands
console.table('Before:', items); const result = items.filter(x => { const keep = test(x); console.log(`Item ${x}: keep=${keep}`); return keep; }); console.table('After:', result);
console.log('Result length:', result.length, 'Original length:', items.length);
Fix now
If your predicate depends on mutable state (time, user session, external flag), capture that value before the loop to ensure deterministic filtering.
Async operations inside forEach not completing+
Immediate action
Check if you're awaiting inside forEach — forEach doesn't wait. Switch to for-of loop
Commands
console.log('Starting forEach'); await Promise.all(arr.map(async item => { await process(item); })); console.log('Done');
console.time('batch'); await Promise.allSettled(arr.map(process)); console.timeEnd('batch');
Fix now
Replace arr.forEach(async x => await fn(x)) with for (const x of arr) { await fn(x); } for sequential or Promise.all(arr.map(fn)) for parallel.
React component re-rendering too often because of array methods+
Immediate action
Check if array methods are creating new references on every render
Commands
console.log('Derived array reference changed'); const derived = items.map(f);
console.log(derived === previousDerived); // Should be true if memoized
Fix now
Wrap derived arrays in useMemo: const activeItems = useMemo(() => items.filter(i => i.active), [items]);
reduce result is NaN or undefined unexpectedly+
Immediate action
Check the initial value and ensure callback returns a value on every iteration
Commands
const result = items.reduce((acc, curr, idx) => { console.log(`Index ${idx}: acc=${acc}, curr=${curr}`); return acc + curr.value; }, 0);
console.log('Result type:', typeof result, 'Value:', result);
Fix now
Always provide initial value matching the expected return type (0 for number, '' for string, [] for array, {} for object). For empty arrays, initial value is returned as-is.
Array method modifying original array unexpectedly+
Immediate action
Check if method is mutating or immutable — sort, reverse, splice mutate; map, filter, slice, concat don't
Commands
console.log('Original before:', original); const result = original.sort(); console.log('Original after:', original, 'Result:', result);
console.log('Are they same reference?', original === result); // true for mutating methods
Fix now
Create a copy before mutating: const sorted = [...original].sort(); or use toSorted() (ES2023) for non-mutating sort.
JavaScript Array Methods Comparison
MethodReturnsOriginal array changed?Use when...Callback should return...
map()New array (same length)NoTransform every element one-to-oneTransformed element
filter()New array (0 to original length)NoKeep elements that pass a testtrue to keep, false to discard
reduce()Single value (any type)NoAccumulate into a single valueNew accumulator value
forEach()undefinedNot intended to (can mutate externally)Side effects (logging, DOM updates)Nothing (ignored)
find()First matching element or undefinedNoGet first element matching conditiontrue when found
some()booleanNoCheck if any element passes testtrue/false predicate
every()booleanNoCheck if all elements pass testtrue/false predicate
sort()Same array (mutated)YesSort elements in placeComparison number (-1, 0, 1)
splice()Array of removed elementsYesInsert/remove elements at indexN/A (side effect in array)
slice()New array (shallow copy)NoCopy portion of arrayN/A (returns new array)
concat()New arrayNoMerge arraysN/A (combines arrays)
flat()New arrayNoFlatten nested arraysN/A (depth parameter)

Key takeaways

1
map transforms every element (one-to-one) → returns new array.
2
filter selects elements that pass a test → returns new array (0 to original length).
3
reduce accumulates to a single value → returns anything (number, object, array, string). Always provide initial value.
4
forEach is for side effects only
it returns undefined. Use for-of with await for sequential async operations, not forEach.
5
find returns first match or undefined; some returns boolean; every returns boolean
all short-circuit.
6
map, filter, reduce, slice, concat are immutable. sort, reverse, splice, push, pop are mutable.
7
Sorting numbers requires compare function
sort((a, b) => a - b). Default sort is lexicographic.
8
Chaining methods creates intermediate arrays
fine for small data, costly for large (>10k items).
9
filter(Boolean) removes falsy values but also removes valid 0 and false
use explicit condition when those matter.
10
Never mutate arrays in React or Redux
always create new references to trigger re-renders.

Common mistakes to avoid

7 patterns
×

Using map when you don't need a new array — discarding the result

Symptom
Code runs but nothing changes. The array appears unchanged after calling .map() because you forgot to assign the result. No error, no warning — just silent failure.
Fix
Always assign the result of map to a variable: const doubled = numbers.map(n => n * 2);. If you don't need a new array (e.g., for side effects), use forEach instead.
×

Using forEach with async callbacks — expecting sequential await

Symptom
Async operations inside forEach run in parallel, not sequential. Errors are swallowed. The loop finishes before any async work completes. Incomplete processing, race conditions, silent failures.
Fix
For sequential async use for (const item of arr) { await process(item); }. For parallel use await Promise.all(arr.map(item => process(item)));. Never use forEach with async callbacks in production.
×

Forgetting to return a value in map/filter/reduce callback

Symptom
The new array contains undefined elements (map) or empty array (filter). reduce accumulator becomes undefined. No error, just wrong data.
Fix
Always explicitly return from callback. Use explicit return in arrow function: arr.map(x => x 2) (implicit return) or arr.map(x => { return x 2; }) (explicit).
×

Using reduce without an initial value

Symptom
TypeError: Reduce of empty array with no initial value. Or works in dev but fails in prod when an edge case empty array appears. Subtle bug that crashes after deployment.
Fix
Always provide initial value matching expected return type: reduce((acc, x) => acc + x, 0) for sums, reduce((acc, x) => [...acc, x], []) for arrays, reduce((acc, x) => ({ ...acc, [x.id]: x }), {}) for objects.
×

Assuming sort returns a new array — forgetting it mutates in place

Symptom
Original array is unexpectedly changed after calling sort. Debugging shows sort altered the original reference when you expected a new sorted copy.
Fix
Create a copy before sorting: const sorted = [...original].sort(). For ES2023, use toSorted() which returns a new array without mutating the original.
×

Modifying the array while iterating with forEach/map/filter

Symptom
Unexpected items processed twice or skipped. The iteration index doesn't account for added/removed elements. Hard-to-reproduce bugs.
Fix
Never modify the original array while iterating. If you need to filter, use filter. If you need to transform, use map. Create a new array, don't mutate the old one.
×

Using filter when you should use find or some

Symptom
Unnecessary memory allocation and slower performance. You only need a boolean or a single element but filter creates a full new array.
Fix
Use find() for the first matching element, some() for a boolean check. Both short-circuit and don't allocate arrays.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the difference between map and forEach? When would you choose on...
Q02SENIOR
How does reduce work? Provide an example of using reduce to group object...
Q03JUNIOR
What happens when you call sort on an array of numbers without a compare...
Q04SENIOR
Explain the concept of immutability in the context of JavaScript arrays....
Q05SENIOR
How would you transform an array of objects to a different shape using m...
Q01 of 05SENIOR

What is the difference between map and forEach? When would you choose one over the other?

ANSWER
map returns a new array of the same length, transforming each element. It's for one-to-one transformation. forEach returns undefined and is for side effects only — logging, DOM updates, mutations. Choose map when you need a transformed array. Choose forEach when you need to perform an action on each element but don't need a new array. Using map for side effects is misleading: it creates an unused array, wasting memory. Using forEach for transformation is verbose and requires manual array building. The rule of thumb: if you need a result array, use map; if you just need to do something with each element, use forEach.
FAQ · 8 QUESTIONS

Frequently Asked Questions

01
What is the difference between map and forEach?
02
How do I remove duplicates from an array?
03
Why does an array method not change the original array?
04
How do I sort an array of objects by a property?
05
Why is .forEach with async functions dangerous?
06
What's the difference between find and filter?
07
Can I break out of a forEach loop early?
08
What's the difference between splice and slice?
🔥

That's JS Basics. Mark it forged?

5 min read · try the examples if you haven't

Previous
null vs undefined in JavaScript
14 / 16 · JS Basics
Next
Object Methods in JavaScript