Implement squash() — Flatten Nested Objects to Dot Paths

squash({ a: { b: { c: 1 } } }) returns { 'a.b.c': 1 }. Build it with recursion, handle arrays with bracket notation, and optionally implement the reverse (unsquash).

10 min read
JavaScript
Interview
Implementation
Objects

TABLE OF CONTENTS

squash(obj) flattens a nested object into a single-level object with dot-path keys: { a: { b: { c: 1 } } } becomes { 'a.b.c': 1 }. The interviewer will then ask you to reverse it with unsquash().

Related deep-dive: Data #2 — Flatten & Unflatten Nested Objects


What is squash()?

squash(obj) converts a deeply nested object into a flat, single-level object where each key is a dot-notation path to the original value. For example, { a: { b: { c: 1 } } } becomes { "a.b.c": 1 }. Arrays use bracket notation: { users: [{ name: "Alice" }] } becomes { "users[0].name": "Alice" }.

This is the reverse of unflatten / unsquash, which takes a flat dot-path object and reconstructs the nested hierarchy. Together they form a serialization/deserialization pair.

The implementation is a recursive depth-first walk. At each level:

  • Primitive or null — set the accumulated path key to this value
  • Plain object — recurse into each entry, appending .key to the accumulated path
  • Array — recurse into each element, appending [index] to the path

The accumulator is a path string that grows as you descend, and you only write to the result object at leaf nodes.

Real-world use cases:

  • Form libraries — convert nested form state into flat { "user.name": "Alice" } for validation or submission
  • URL query params — flatten a config object into query parameters: filter[status]=active&filter[date]=today
  • Database updates — MongoDB and Firebase use dot-notation for updating nested fields without overwriting siblings
  • Diffing — compare two nested objects by flattening both and doing a shallow diff on the flat versions

The interviewer will typically ask for unsquash() as the reverse operation. Building both tests whether you can think in both directions — encoding a hierarchy into paths and decoding paths back into a hierarchy.


The Problem

"Implement squash(obj) that converts a nested object into a flat object with dot-separated keys. Handle both objects and arrays (arrays use bracket notation)."

"Now implement unsquash(obj) — the reverse operation."


Thought Process

squash is a DFS traversal where you accumulate a path as you go down. At each leaf (non-object, non-array value), you set result[path] = value and bubble back up.

Key decisions:

  • Separator: dot by default, but make it configurable
  • Array notation: use parent[index] bracket syntax
  • Unsquash: split the path, walk/create the nested structure, set the leaf

Step 1 — Squash (Objects Only)

Loading editor...


Step 2 — Squash with Arrays

Loading editor...


Step 3 — Unsquash (Reverse)

Loading editor...


Step 4 — Edge Cases

Already flat: squash({ a: 1 }) returns { a: 1 }. Works — no object leaves to recurse into.

Null values: { a: null }{ a: null }. null is not an object, so it's treated as a leaf.

Empty objects/arrays: { a: {} }{} (no keys). { a: [] }{} (no indices). The walk function has nothing to iterate.

Numbers as object keys: { 0: 'a' } — treated as any other key. On unsquash, numeric keys create objects with numeric string keys, not arrays.


Full Solution

Loading editor...


What Interviewers Are Testing

  • Path construction — building dot-notation keys during DFS
  • Array index notation — bracket syntax for array elements
  • Reverse operation — parsing paths back into nested structure
  • Empty container handling — deciding what to do with {} and []

Complexity

TimeSpace
squashO(N) — each leaf visited onceO(N) — new flat object
unsquashO(K × P) — K keys, P path depthO(N) — new nested object

Interview Tips

  • Use array for path, join at the end — building strings incrementally (path + '.' + key) is error-prone. Accumulate segments in an array and join once.
  • Ask about array notation preferencea[0].b vs a.0.b. The bracket convention is standard because it avoids ambiguity with numeric keys.
  • Handle empty objects/arrays explicitly — state your choice and why: "I'll preserve empty containers as-is rather than dropping them."
  • Mention the real lodash_.get / _.set are the production equivalents; squash/unsquash test whether you can implement their core logic.

Related Questions


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI