Episode 1 — Fundamentals / 1.22 — Array Methods
Interview Questions: Array Methods
Model answers for map, filter, reduce, forEach, some/every, slice/splice, sort, flat/flatMap, and functional patterns — covering Beginner, Intermediate, and Advanced tiers.
How to use this material (instructions)
- Read lessons in order —
README.md, then1.22.a→1.22.j. - Practice out loud — definition → example → pitfall.
- Pair with exercises —
1.22-Exercise-Questions.md. - Quick review —
1.22-Quick-Revision.md.
Beginner (Q1–Q6)
Q1. What is the difference between map and forEach?
Why interviewers ask: Tests whether you understand return values and when to use each.
Model answer:
map() calls a function on each element and returns a new array of the transformed values. forEach() also calls a function on each element but always returns undefined — it is designed for side effects (logging, DOM updates, API calls). Use map when you need the result array; use forEach when you just need to do something for each element. A common mistake is using forEach to build an array manually when map would be cleaner, or using map purely for side effects (which creates a wasted array).
Q2. How does filter work? What does it return if nothing matches?
Why interviewers ask: Verifies you know filter never returns null/undefined.
Model answer:
filter() iterates through each element, passes it to a callback, and keeps the element if the callback returns a truthy value. It returns a new array containing only the passing elements. If no element passes, it returns an empty array [] — never null or undefined. This is safe for chaining: arr.filter(...).map(...) works even when filter finds nothing.
Q3. Explain reduce in simple terms. Why should you always provide an initial value?
Why interviewers ask: reduce is the most misunderstood array method.
Model answer:
reduce() processes each element left-to-right, carrying an accumulator from iteration to iteration, and produces a single output value (number, object, array, string, etc.). You should always provide an initialValue (second argument) because: (1) calling reduce on an empty array without it throws a TypeError, (2) with a single-element array the callback is never called — the element is returned directly, (3) it makes the type of the accumulator explicit and readable.
[].reduce((a, b) => a + b); // TypeError!
[].reduce((a, b) => a + b, 0); // 0 — safe
Q4. What is the difference between slice and splice?
Why interviewers ask: Classic confusion pair — mutating vs non-mutating.
Model answer:
slice(start, end) extracts a portion of the array into a new array — it does not mutate the original. The end index is exclusive. splice(start, deleteCount, ...items) modifies the array in place — it can delete, insert, or replace elements. It mutates the original and returns an array of the deleted elements. The key distinction: slice is safe/immutable, splice is powerful but dangerous.
Q5. Why does [10, 2, 1].sort() give [1, 10, 2]?
Why interviewers ask: Tests awareness of the lexicographic default.
Model answer:
Without a compare function, sort() converts each element to a string and sorts by Unicode code points (lexicographic order). The string "10" comes before "2" because "1" (code 49) < "2" (code 50). To sort numbers correctly, provide a compare function: sort((a, b) => a - b) for ascending. This is one of the most common JavaScript gotchas.
Q6. What does some do? What does every do? What happens with an empty array?
Why interviewers ask: Tests logic understanding and vacuous truth.
Model answer:
some() returns true if at least one element's callback returns truthy — it short-circuits on the first true. every() returns true only if all elements' callbacks return truthy — it short-circuits on the first false. For empty arrays: [].some(...) returns false (no element can satisfy the condition), [].every(...) returns true (vacuous truth — there are no counterexamples). This matches formal logic.
Intermediate (Q7–Q12)
Q7. Why can't you use await inside forEach?
Why interviewers ask: Common async bug in real codebases.
Model answer:
forEach ignores the return value of its callback. When the callback is async, it returns a Promise, but forEach does not await it — the Promises fire off concurrently and forEach completes immediately. Code after the forEach runs before the async operations finish. The fix is to use a for...of loop with await for sequential execution, or Promise.all(arr.map(async ...)) for concurrent execution with proper waiting.
// WRONG
urls.forEach(async url => await fetch(url));
console.log("done"); // runs immediately!
// RIGHT (sequential)
for (const url of urls) { await fetch(url); }
// RIGHT (concurrent)
await Promise.all(urls.map(url => fetch(url)));
Q8. Explain method chaining with array methods. What makes it possible?
Why interviewers ask: Tests understanding of return types and composability.
Model answer:
Method chaining works because methods like filter, map, slice, and sort return arrays, so you can call another array method on the result immediately. Each step in the chain transforms the data and passes it to the next step. The typical pattern is filter (narrow the dataset) → map (transform each element) → sort (order) → reduce (aggregate). This creates a pipeline that reads top-to-bottom. forEach breaks the chain because it returns undefined.
Q9. What is flatMap and when would you use it over map?
Why interviewers ask: Tests knowledge of newer APIs and one-to-many transformations.
Model answer:
flatMap(callback) is equivalent to map(callback).flat(1) but more efficient (single pass, no intermediate array). Use flatMap when each element should produce zero, one, or multiple output elements. Return [] to remove an element, [value] to keep/transform, [a, b, c] to expand. Common uses: splitting strings into words, extracting nested sub-items, combined filter+map in one step.
["hello world", "foo bar"].flatMap(s => s.split(" "));
// ["hello", "world", "foo", "bar"]
[1, 2, 3].flatMap(n => n % 2 === 0 ? [n * 10] : []);
// [20] — filter and map in one pass
Q10. How would you sort an array of objects by multiple criteria?
Why interviewers ask: Real-world sorting is almost always multi-level.
Model answer:
In the compare function, check the primary sort key first. If the values are equal, fall through to the secondary key:
people.sort((a, b) => {
if (a.age !== b.age) return a.age - b.age; // primary: age ascending
return a.name.localeCompare(b.name); // secondary: name A-Z
});
This pattern extends to any number of levels. Since ES2019, JavaScript's sort is stable, meaning elements that compare equal retain their original relative order.
Q11. How do you update an array immutably (without mutating)?
Why interviewers ask: Essential for React/Redux state management.
Model answer:
Use non-mutating methods and spread syntax:
| Operation | Mutating | Immutable alternative |
|---|---|---|
| Add to end | push(x) | [...arr, x] |
| Add to start | unshift(x) | [x, ...arr] |
| Remove at index | splice(i, 1) | [...arr.slice(0, i), ...arr.slice(i + 1)] |
| Replace at index | arr[i] = x | arr.map((el, idx) => idx === i ? x : el) |
| Sort | sort() | [...arr].sort() or toSorted() (ES2023) |
| Reverse | reverse() | [...arr].reverse() or toReversed() |
Q12. Implement groupBy using reduce.
Why interviewers ask: Tests reduce mastery with a practical utility.
Model answer:
function groupBy(arr, keyFn) {
return arr.reduce((groups, item) => {
const key = typeof keyFn === "function" ? keyFn(item) : item[keyFn];
if (!groups[key]) groups[key] = [];
groups[key].push(item);
return groups;
}, {});
}
// Usage:
groupBy([{ n: "A", t: "x" }, { n: "B", t: "y" }, { n: "C", t: "x" }], "t");
// { x: [{n:"A",t:"x"}, {n:"C",t:"x"}], y: [{n:"B",t:"y"}] }
Key points: always provide {} as initial value, always return acc, initialize the array for new keys.
Advanced (Q13–Q18)
Q13. Can you build map and filter using only reduce?
Why interviewers ask: Demonstrates deep understanding of reduce as the universal combinator.
Model answer:
// map via reduce
function myMap(arr, fn) {
return arr.reduce((acc, el, i) => {
acc.push(fn(el, i, arr));
return acc;
}, []);
}
// filter via reduce
function myFilter(arr, fn) {
return arr.reduce((acc, el, i) => {
if (fn(el, i, arr)) acc.push(el);
return acc;
}, []);
}
This proves reduce is the "universal" array method — every other transformation can be expressed through it. In practice, use the specific methods for clarity.
Q14. What are the performance implications of chaining filter(...).map(...).reduce(...)?
Why interviewers ask: Tests awareness of intermediate array allocation.
Model answer:
Each method in the chain creates an intermediate array. For filter→map→reduce, that is two intermediate arrays plus the final value. This means three passes over the data. For most applications this is negligible (arrays under 100k elements). For performance-critical paths (millions of elements, 60fps animations), a single for loop or a single reduce that does everything in one pass avoids intermediate allocations. Profile first — premature optimization harms readability.
Q15. Explain the toSorted, toReversed, toSpliced, and with methods (ES2023).
Why interviewers ask: Tests awareness of modern JS evolution.
Model answer:
ES2023 introduced non-mutating counterparts to mutating array methods:
| Mutating | Non-mutating (ES2023) |
|---|---|
sort() | toSorted(compareFn) |
reverse() | toReversed() |
splice(i, n, ...items) | toSpliced(i, n, ...items) |
arr[i] = val | arr.with(i, val) |
All return new arrays. This eliminates the need for the [...arr].sort() workaround and aligns with functional/immutable patterns used in React and state management.
Q16. How does sort stability affect real-world applications?
Why interviewers ask: Tests understanding of sort stability guarantees.
Model answer:
A stable sort preserves the relative order of elements that compare as equal. Since ES2019, Array.prototype.sort is guaranteed stable. This matters for multi-level sorts: if you sort by department first, then by salary, employees in the same salary bracket within a department retain their original order. Before ES2019, V8 (Chrome) used TimSort (stable) for arrays > 10 elements but insertion sort for smaller arrays, and other engines varied. Now the guarantee is universal.
Q17. What is the time complexity of common array method patterns?
Why interviewers ask: Tests algorithmic thinking.
Model answer:
| Pattern | Time complexity |
|---|---|
map, filter, forEach, reduce (single pass) | O(n) |
sort | O(n log n) |
filter → map → reduce (chained) | O(n) each = O(3n) = O(n) |
some, every (short-circuit) | O(n) worst case, often O(1) to O(k) |
find, findIndex (short-circuit) | O(n) worst case |
Nested: filter(x => arr.includes(x)) | O(n * m) |
| Reduce to build lookup, then filter | O(n + m) |
The nested includes in a loop is a common performance trap. Converting one array to a Set for O(1) lookups brings it to O(n + m).
Q18. Write a pipeline that processes a large dataset efficiently.
Why interviewers ask: Tests practical optimization skills.
Model answer:
// Inefficient: 4 passes, 3 intermediate arrays
const result = data
.filter(d => d.active)
.map(d => ({ ...d, score: d.points / d.games }))
.filter(d => d.score > threshold)
.reduce((sum, d) => sum + d.score, 0);
// Efficient: single pass
const result2 = data.reduce((sum, d) => {
if (!d.active) return sum;
const score = d.points / d.games;
if (score <= threshold) return sum;
return sum + score;
}, 0);
The single-pass version is harder to read but creates zero intermediate arrays. The right choice depends on context: prefer readability for business logic, optimize for hot paths identified by profiling.
Quick-fire
| # | Question | One-line answer |
|---|---|---|
| 1 | map returns? | New array, same length |
| 2 | filter returns? | New array, subset (or empty) |
| 3 | reduce returns? | Single value of any type |
| 4 | forEach returns? | undefined |
| 5 | some([]) returns? | false |
| 6 | every([]) returns? | true |
| 7 | slice mutates? | No |
| 8 | splice mutates? | Yes |
| 9 | sort mutates? | Yes |
| 10 | toSorted mutates? | No (ES2023) |
| 11 | flat() default depth? | 1 |
| 12 | flatMap flat depth? | 1 (always) |
| 13 | Break from forEach? | Not possible |
| 14 | await in forEach? | Does not work correctly |
| 15 | [10,2].sort() | [10, 2] (lexicographic) |
← Back to 1.22 — Array Methods (README)