Medium

Deep Remove Falsy

Prompt

Implement a function deepRemoveFalsy(value) that returns a new value with all falsy values removed at every level of nesting. The input can be a primitive, an array, or an object.

The falsy values are: false, null, 0, '' (empty string), undefined, and NaN.

Empty objects {} and empty arrays [] that result from removing falsy values should also be removed.

Playground

Hint 1

Handle three cases: primitives (return as-is), arrays (filter out falsy values, recurse into truthy ones), and objects (skip falsy values, recurse into truthy ones). Use Array.isArray() to distinguish arrays from objects.

Hint 2

After recursively cleaning a nested object or array, check if it's now empty. If an object has no keys or an array has no elements, it should be treated as "empty" and excluded from the result.

Solution

Explanation

This function cleans out all the junk from a data structure. Nulls, empty strings, zeros, false values, all of it, at every level of nesting. And if removing those values leaves an object or array empty, that gets removed too.

The three cases

The function handles three types of input:

Primitives (numbers, strings, booleans, null): just return them as-is. The filtering happens at the parent level, not here. If a parent sees a falsy primitive, it won't include it.

Arrays: first filter out falsy values with .filter(Boolean). Then recursively clean each remaining element with .map(deepRemoveFalsy). Finally, filter out any elements that became empty objects {} or empty arrays [] after cleaning.

Objects: loop through each key-value pair. Skip falsy values with if (!val) continue. For truthy values, recursively clean them. If the cleaned result is an empty object or array, don't include it in the output.

Why do we check for empty after cleaning?

Consider this input:

deepRemoveFalsy({ a: { b: null, c: 0 } });

After removing falsy values from { b: null, c: 0 }, we're left with {}. An empty object. If we kept it, the result would be { a: {} }, which isn't useful. So we check: after cleaning, is this object empty? If yes, skip it entirely. The final result is {}.

This is the part that makes this question trickier than a simple filter(Boolean). You need to clean recursively, then check if the result is worth keeping.

Walking through a nested example

For { a: null, b: [false, { c: 0, d: 2 }], e: { f: '', g: 'hello' } }:

  1. Key "a", value null. Falsy. Skip.
  2. Key "b", value [false, { c: 0, d: 2 }]. It's an array. Process it:
    • Filter falsy: [{ c: 0, d: 2 }] (false removed)
    • Recurse into { c: 0, d: 2 }: key "c" is 0 (falsy, skip), key "d" is 2 (truthy, keep). Result: { d: 2 }
    • Not empty, keep it. Array becomes [{ d: 2 }]
  3. Key "e", value { f: '', g: 'hello' }. It's an object:
    • Key "f" is '' (falsy, skip). Key "g" is 'hello' (truthy, keep).
    • Result: { g: 'hello' }
  4. Final: { b: [{ d: 2 }], e: { g: 'hello' } }

JavaScript's Boolean function is a handy shorthand for filtering falsy values. [0, 1, false, 2, ''].filter(Boolean) is the same as .filter(item => !!item). It converts each value to a boolean and keeps only the truthy ones.