JavaScript Array Methods: map, filter, reduce and the Interview Problems They Solve

Work through a filter-map-reduce chain and a frequency-table accumulator, then separate non-mutating array methods from the methods that change their input.

KnowledgeGate Team

Exam prep & CS education

Updated 21 Aug 20266 min read

map, filter and reduce appear in almost every JavaScript interview because they expose whether you can trace callbacks, not because their names are difficult. The questions usually ask you to predict a chain or build a frequency count, group-by operation or flattening step.

Most wrong answers come from losing track of reduce's accumulator and initial value. Write the accumulator's value down at every step and the answer stops being a guess.

The callback signature everyone forgets

An array callback can receive three arguments:

(element, index, array) => result

Most transformations need only element. The index is available when position matters, as in arr.map((x, i) => `${i}: ${x}`). The third argument is the array being traversed, but code rarely needs it.

Do not confuse map with forEach:

  • map uses each callback result to build a new array of the same length.

  • forEach returns undefined and is used for side effects such as logging or updating something outside the traversal.

Calling map and ignoring its returned array is a code smell. Calling forEach when you need a transformed array forces manual mutation that map already handles.

The callbacks also close over values from their surrounding scope, so a callback created inside a loop can capture the variable rather than the value it had at that iteration. That behaviour belongs to lexical scope, and it is worked out in Closures in JavaScript: lexical scope and the var loop trap.

map and filter, worked together

map transforms every element. filter asks a yes-or-no question and keeps only the elements for which the callback returns a truthy value. Both return new arrays and leave the source array's structure unchanged.

Start with:

const nums = [1, 2, 3, 4, 5, 6];

Keep only the even values:

const evens = nums.filter(n => n % 2 === 0);
// [2, 4, 6]

The test is true for 2, 4 and 6, so the new array has three elements. Now square each retained value:

const squares = evens.map(n => n * n);
// [4, 16, 36]

The full chain is:

const squares = nums
  .filter(n => n % 2 === 0)
  .map(n => n * n);

The original nums still holds [1, 2, 3, 4, 5, 6]. A callback could still mutate an object contained inside an array, so "non-mutating" describes what these methods do to the array itself, not a magical deep copy.

reduce, properly

reduce folds an array into one accumulator. Its result can be a number, string, object, array or any other value you design.

array.reduce((acc, cur) => nextAcc, initialValue)

Continue the worked chain by adding the squares:

const total = squares.reduce((acc, n) => acc + n, 0);

Trace it rather than jumping to the answer:

  1. Initial accumulator: 0

  2. Read 4: 0 + 4 = 4

  3. Read 16: 4 + 16 = 20

  4. Read 36: 20 + 36 = 56

Therefore the complete filter, map and reduce chain produces 56.

Pass the initial value. Without it, reduce uses the first array element as the accumulator and begins the callback at the second element. That sometimes works for a non-empty numeric sum, but it changes the trace and makes an empty array throw an error.

reduce for a frequency-count problem

Now count repeated words:

const words = ["a", "b", "a", "c", "b", "a"];

const counts = words.reduce((acc, w) => {
  acc[w] = (acc[w] || 0) + 1;
  return acc;
}, {});

The {} initial value says that the accumulator is an object. Every step reads the previous count, uses zero when the key is absent, adds one and returns the same accumulator object for the next step.

Step

Current w

Accumulator before

Accumulator after

1

"a"

{}

{ a: 1 }

2

"b"

{ a: 1 }

{ a: 1, b: 1 }

3

"a"

{ a: 1, b: 1 }

{ a: 2, b: 1 }

4

"c"

{ a: 2, b: 1 }

{ a: 2, b: 1, c: 1 }

5

"b"

{ a: 2, b: 1, c: 1 }

{ a: 2, b: 2, c: 1 }

6

"a"

{ a: 2, b: 2, c: 1 }

{ a: 3, b: 2, c: 1 }

Counting the source array by hand gives three a values, two b values and one c, which is exactly what the final accumulator holds: { a: 3, b: 2, c: 1 }.

Six-row trace table of the words.reduce accumulator, from an empty object to a:3, b:2, c:1, above a strip reading filter [2,4,6], map [4,16,36], reduce 4+16+36=56.

The same accumulator shape solves group-by. Compute a key, create the array for that key when it is missing, then push the current item:

const people = [
  { name: "Asha", dept: "cs" },
  { name: "Ravi", dept: "ec" },
  { name: "Neha", dept: "cs" }
];

const byDept = people.reduce((acc, p) => {
  acc[p.dept] = acc[p.dept] || [];
  acc[p.dept].push(p.name);
  return acc;
}, {});
// { cs: ["Asha", "Neha"], ec: ["Ravi"] }

Asha creates cs, Ravi creates ec, and Neha finds cs already there and appends to it. Flattening one level is the same fold with an array as the accumulator:

[[1, 2], [3], [4, 5]].reduce((acc, cur) => acc.concat(cur), []);
// [1, 2, 3, 4, 5]

flat returns the same array in one call, so use reduce for flattening only when the step carries extra logic such as filtering or renaming as it goes.

Mutating methods and the sort trap

Know which calls change the source array:

Return a new result without restructuring the source

Mutate the source array

map, filter, reduce, slice, concat

push, pop, splice, sort, reverse

reduce is in the left column because the method does not rearrange the source array, although your reducer can mutate the accumulator you supplied.

sort has two traps. It mutates the original array, and its default comparison converts values to strings:

[1, 2, 10].sort();
// [1, 10, 2]

For ascending numeric order, provide a comparator:

[1, 2, 10].sort((a, b) => a - b);
// [1, 2, 10]

If the original order must survive, copy first, for example [...nums].sort((a, b) => a - b).

Traps and interview patterns

An empty array reduced without an initial value throws TypeError: Reduce of empty array with no initial value. A block-bodied reducer that forgets return acc passes undefined into the next step. A numeric sort without a comparator produces lexicographic order. These are predictable errors, not obscure trivia.

Interviews commonly ask you to predict a chain, compare map with forEach, build group-by with reduce, or explain whether a method mutates. The same patterns sit inside the wider set covered in JavaScript interview questions for freshers 2026.

KnowledgeGate's JavaScript-basics topic holds over 70 practice questions inside a wider 600+ full-stack MERN set. That is enough volume to practise callbacks as a reasoning skill rather than memorising isolated outputs.

The short version and next step

map transforms, filter selects, and reduce folds into an accumulator. Pass an initial value, return the next accumulator on every step, and remember that sort mutates and compares as strings until given a comparator.

Build the language foundation with the Complete JavaScript course, then use the Mera Placement Hoga bundle for interview-problem practice. The Coding and Skill Development catalog connects this topic with the rest of your coding path.