Skip to content

Kata: flatten with depth

Open arrays only, count depth down per level, write to a new list, and keep your own stack when nesting runs deep.

Predict first

Before you read: predict

ExamHall's question navigator numbers every question of an exam 1..N. Sections hold questions and groups of questions, and the navigator builds its list with one line:

What do list.length and list[2] hold?

Code for this question
const sections = [['q1', 'q2'], [['q3', 'q4'], 'q5'], 'q6'];export const list = [].concat(...sections);
Pick one

The 30-second version

flatten(arr, depth) returns a new array. It opens arrays only, while depth stays above 0, handing each nested list depth - 1; strings, objects and null stay whole, and empty slots are skipped, as flat() does. [].concat(...arr) opens one level. For deep nesting, keep your own stack of frames, not the call stack.

Open arrays only, count depth down per level, write to a new list, and keep your own stack when nesting runs deep.

How it works

Step 1 of 13

Ask, then say the contract. Default depth? Strings? Empty slots? Cycles? Then: a new array, arrays opened to depth, everything else kept whole.

Analogy: A county record office cataloguing a family deposit (heritage records)

A family deposits its papers at a county record office: boxes holding letters and folders, folders holding more letters. The archivist writes the catalogue, one numbered line per item.

At the record office In the kata
the catalogue is a new ledger; the deposit stays boxed as it came a new out; the input is unchanged (step 7)
the family says how many levels to open: "boxes only", or "boxes and folders" depth, counted down per level (step 4)
a letter gets one line, never one line per page strings stay whole (step 3)
a pocket whose deed was withdrawn gets no line; an envelope marked "blank" gets one holes skipped, undefined kept (step 8)
tipping each box onto the table once leaves the folders shut [].concat(...arr) (step 2)
a slip on each open box: "next: item 4" a frame { list, i, d } (step 12)
unopened boxes piled up come off the top first, so a pile alone writes the catalogue backwards a plain stack reverses (step 11)

In real terms: flattening is a walk over nested lists that appends each leaf to a new list in reading order, opening containers only to the given depth.

Breaks down when: an archivist sees a folder's contents at a glance; the code reads one slot at a time. And a deposit never contains itself, while an array can: ask about cycles (step 1).

Contrasting case: an archivist who tips every box onto the table once and numbers what falls out.

Examples, run for real

4.1 ExamHall EXH-3550: a one-level list, and the test list

ExamHall's navigator built its numbered list with [].concat(...sections). An exam with groups inside sections, and a group inside a group, got too few numbers (EXH-3550). MR !3850 replaced it with the reference answer and added the ten core tests plus the ladder test. The list also runs on two common answers: M1 is the one-line concat, M2 is the recursive walk (step 9).

const sections = [['Q1', 'Q2', ['Q3', 'Q4', 'Q5']], [['Q6', 'Q7'], 'Q8'], ['Q9', ['Q10', ['Q11', 'Q12']]], ['Q13']];
4.1 EXH-3550 [].concat(...sections): 8 numbers for 13 questions; 3 numbers land on a whole group
4.1 MR !3850 flatten(sections): 13 numbers for 13 questions, Q1..Q13 in reading order
4.1 reference.mjs              11 of 11 pass (10 core, 1 ladder)
4.1 M1, [].concat(...arr)      3 of 11 pass · red: T1, T4, T6, T7, T8, T9, T10, L1
4.1 M2, a recursive walk       10 of 11 pass · red: L1 (RangeError)

M1 ignores the depth and opens exactly one level, so it fails every test needing another number of levels, and the holes test: the spread turns an empty slot into undefined. M2 passes the core list and fails only L1, where each level costs a call frame (step 10).

4.2 Chat: a thread export, collapsed and expanded

A chat app exports a thread in which each reply's replies sit one level deeper. The collapsed view asks for depth 1, the full export for every level:

const thread = ['Ana: lunch?', ['Ben: yes', ['Ana: 12:30', ['Ben: ok']]], 'Cy: me too', ['Ana: great']];
4.2 flatten(thread, 1): 5 entries, 1 still a list (the deeper replies, collapsed)
4.2 flatten(thread): 6 entries, 0 lists
4.2 equal to thread.flat(1) and thread.flat(Infinity): true
4.2 thread unchanged after both calls: true

Depth 1 opens the thread's own lists once, so the reply chain below stays one list (step 4); with no depth, every level opens (step 6). Both match flat() given the same depth, and the thread is untouched (step 7).

Try it

Answer first, then check. Nothing is saved.

Question 1 of 8

ExamHall's navigator keeps its own stack of frames, one per open list. The loop's six lines are shuffled:

In what order does the loop run them, top first?

Code for this question
export function flatten(arr, depth = Infinity) {  const out = [];  const stack = [{ list: arr, i: 0, d: depth }];  while (stack.length) {    /* the six lines, in order */  }  return out;}

Your order

  1. 1empty
  2. 2empty
  3. 3empty
  4. 4empty
  5. 5empty
  6. 6empty

Tap the lines in the order you expect.

Question 2 of 8

A video app flattens its nested playlists with a recursive kata answer that is complete apart from its condition:

The team's list has 10 tests; their items include titles, objects, null and undefined.

Which condition passes all 10?

Code for this question
export function flatten(arr, depth = Infinity) {  const out = [];  for (let i = 0; i < arr.length; i++) {    if (!(i in arr)) continue;    const v = arr[i];    if (/* the condition */) out.push(...flatten(v, depth - 1));    else out.push(v);  }  return out;}
Pick one

Question 3 of 8

A chat client shows a thread with replies nested under their message. Its kata answer uses a plain stack of values:

With thread = ['m1', ['r1', 'r2'], 'm2'], what does flatten(thread).join(' ') return?

Code for this question
export function flatten(arr) {  const stack = [...arr];  const out = [];  while (stack.length) {    const v = stack.pop();    if (Array.isArray(v)) stack.push(...v);    else out.push(v);  }  return out;}
Pick one

Question 4 of 8

A box office flattens its seat map with a recursive kata answer that is complete apart from the recursive call:

Which call passes all 10 tests of the list?

Code for this question
export function flatten(arr, depth = Infinity) {  const out = [];  walk(arr, depth, out);  return out;} function walk(list, depth, out) {  for (let i = 0; i < list.length; i++) {    if (!(i in list)) continue;    const v = list[i];    if (Array.isArray(v) && depth > 0) /* the call */;    else out.push(v);  }}
Pick one

Question 5 of 8

the interviewer says: "Implement flatten(arr, depth); with no depth, flatten every level. Don't change the input, and don't split strings." You have 15 minutes and a plain editor, and ExamHall's question navigator is the use case.

Before you type: say your test list out loud.

Name at least six tests, each with its input and what it expects.

Question 6 of 8

the interviewer says: "Implement flatten(arr, depth); with no depth, flatten every level. Don't change the input, and don't split strings." You have 15 minutes and a plain editor, and ExamHall's question navigator is the use case.

Now write it, in 15 minutes, from a blank file that exports flatten.

Question 7 of 8

A ledger export follows each posting with a list of its adjustments; a correction to an adjustment sits one list further down. A script flattens the lines by repeating one step until no list is left:

What is report?

Code for this question
let list = ['P-1', ['ADJ-1'], 'P-2', ['ADJ-2', ['ADJ-3']]];let passes = 0;while (list.some(Array.isArray)) {  list = [].concat(...list);  passes++;}export const report = `passes: ${passes} · entries: ${list.length}`;
Pick one

Question 8 of 8

A shop lists the products of a category tree with this kata answer:

For ['p1', ['p2', 'p3'], 'p4'] the page must show p1 p2 p3 p4, and it does not.

Which one change makes it show that?

Code for this question
export function flatten(arr) {  const stack = [...arr];  const out = [];  while (stack.length) {    const v = stack.pop();    if (Array.isArray(v)) stack.push(...v.toReversed());    else out.push(v);  }  return out;}
Pick one

Go deeper

Pro tips
  1. When a runner is allowed, check your answer against arr.flat(depth), and pass the depth. When: practising, or when the interviewer allows a runner (step 6). Test T7 uses flat() as an oracle over 20 cases; flat() defaults to 1, not Infinity. Verify (RAN, tips.mjs): (The curriculum row's tip, [SELF].)
    tip 1 T7, flat(depth) as the oracle over 20 cases: reference agrees · M1, [].concat(...arr) differs
  2. Run your own answer against the list before you call it done: KATA_IMPL=./my-flatten.mjs node --test kata-core.test.mjs, then the ladder. When: practising the kata at home (step 13). Verify (RAN, tips.mjs):
    tip 2 KATA_IMPL=./reference.mjs: exit 0 · tests 10 · pass 10 · fail 0
    tip 2 KATA_IMPL=<temp>/m2-flatten.mjs (a recursive walk): core exit 0 · pass 10 of 10 · ladder exit 1 · red: L1
Source

Vendor and guide pages change, so their claims are labelled as of 2026-10-12; every behaviour this lesson grades is executed on Node 24.

  • MDN, Array.prototype.flat() (the curriculum row's first source), https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat (mdn-array-flat.txt). flat() "creates a new array with all sub-array elements concatenated into it recursively up to the specified depth"; its depth "Defaults to 1." (step 6); it "does not alter this but instead returns a shallow copy" (step 7); and "The flat() method removes empty slots in arrays" (step 8). As of 2026-10-12.
  • Front End Interview Handbook, JavaScript coding (the row's second source), https://www.frontendinterviewhandbook.com/coding/javascript-utility-function (feih-javascript-coding.txt). Its question list includes "Flatten (Free)"; it says candidates take "around 10-15 minutes for a basic question" (the time box); it advises preparing by "writing test cases" (step 13), and to "clarify that there are no self-references/cycles within the object" (step 1). As of 2026-10-12.

Checked against the tools and docs as of 2026-10-12.