2705. Compact Object
Problem Description
You are given an object or array obj
that needs to be "compacted" by removing all falsy values from it.
A compact object is created by:
- Removing any keys/properties that contain falsy values
- Applying this removal process recursively to all nested objects and arrays
- Treating arrays as objects where the indices serve as keys
A value is considered falsy when Boolean(value)
returns false
. In JavaScript/TypeScript, falsy values include:
false
0
""
(empty string)null
undefined
NaN
The compacting process works as follows:
- For objects: Remove any key-value pairs where the value is falsy
- For arrays: Remove falsy elements (which may change indices)
- For nested structures: Apply the same rules recursively
The input obj
is guaranteed to be valid JSON (as if it came from JSON.parse
).
Example:
If the input is {a: null, b: [false, 1], c: {d: "", e: 2}}
, the output would be {b: [1], c: {e: 2}}
because:
- Key
a
is removed (value isnull
) - In array
b
,false
is removed, keeping only1
- In nested object
c
, keyd
is removed (value is empty string), keeping onlye: 2
Intuition
The key insight is that we need to traverse the entire data structure and make decisions at each level about what to keep and what to remove. Since the structure can be nested to any depth, recursion becomes the natural choice.
Let's think about what we're dealing with:
- Primitive values: These can't have "keys" to remove, so we return them as-is if they're truthy
- Arrays: We need to filter out falsy elements and process remaining elements recursively
- Objects: We need to check each key-value pair, keep only truthy values, and process those values recursively
The recursive pattern emerges from recognizing that at each level, we perform the same operation: check if values are falsy, remove them if they are, and for any remaining values that are objects or arrays, apply the same logic to their contents.
For arrays, filter(Boolean)
is a clean way to remove falsy values since Boolean
acts as a predicate function that returns false
for falsy values. After filtering, we still need to recursively process each remaining element with map(compactObject)
because those elements might themselves be objects or arrays containing falsy values.
For objects, we iterate through all entries, and only add a key-value pair to our result if the value is truthy. But before adding, we recursively process the value to ensure any nested structures are also compacted.
The base case of our recursion is when we encounter a non-object value (including null
) - these are leaf nodes in our data structure tree that can't be traversed further, so we simply return them if they're truthy or exclude them if they're falsy (handled by the calling context).
Solution Approach
The implementation uses recursion to traverse and compact the data structure. Let's walk through how the solution handles each case:
Base Case - Non-objects and null:
if (!obj || typeof obj !== 'object') { return obj; }
When obj
is not an object or is null
, we return it as-is. This handles primitive values (numbers, strings, booleans) and null
. Since these values don't have nested properties, there's nothing to compact.
Array Processing:
if (Array.isArray(obj)) {
return obj.filter(Boolean).map(compactObject);
}
For arrays, we apply a two-step process:
filter(Boolean)
removes all falsy elements. TheBoolean
constructor acts as a predicate that returnsfalse
for falsy values (0
,""
,false
,null
,undefined
,NaN
)map(compactObject)
recursively processes each remaining element to ensure nested structures within the array are also compacted
Object Processing:
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value) {
acc[key] = compactObject(value);
}
return acc;
}, {} as Obj);
For regular objects, we:
- Use
Object.entries(obj)
to get an array of[key, value]
pairs - Use
reduce
to build a new object, starting with an empty object{}
- For each key-value pair:
- Check if the value is truthy with
if (value)
- If truthy, recursively call
compactObject(value)
to process nested structures - Add the compacted value to the accumulator object with the same key
- Check if the value is truthy with
- Return the accumulated object containing only keys with truthy values
The recursion ensures that the compacting process applies to all levels of nesting. Each recursive call either:
- Returns a primitive value unchanged (base case)
- Returns a compacted array with all falsy elements removed
- Returns a compacted object with all falsy-valued keys removed
This depth-first approach guarantees that by the time a value is checked for truthiness at any level, all of its nested content has already been compacted.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through a concrete example to see how the recursive solution works:
Input: {a: 0, b: {c: false, d: "hi"}, e: [1, "", 2]}
Step-by-step execution:
-
Initial call -
compactObject({a: 0, b: {c: false, d: "hi"}, e: [1, "", 2]})
- Input is an object (not null, not primitive), so we process it as an object
- We iterate through entries:
[["a", 0], ["b", {c: false, d: "hi"}], ["e", [1, "", 2]]]
-
Processing key "a":
- Value is
0
(falsy) - Skip adding to result (fails
if (value)
check)
- Value is
-
Processing key "b":
- Value is
{c: false, d: "hi"}
(truthy - objects are truthy) - Recursively call
compactObject({c: false, d: "hi"})
- It's an object, so iterate through entries:
[["c", false], ["d", "hi"]]
- "c" has value
false
(falsy) → skip - "d" has value
"hi"
(truthy) → recursively callcompactObject("hi")
- It's a primitive string, return
"hi"
- It's a primitive string, return
- Result:
{d: "hi"}
- It's an object, so iterate through entries:
- Add to result:
acc["b"] = {d: "hi"}
- Value is
-
Processing key "e":
- Value is
[1, "", 2]
(truthy - arrays are truthy) - Recursively call
compactObject([1, "", 2])
- It's an array, so:
- First, filter:
[1, "", 2].filter(Boolean)
→[1, 2]
(empty string removed) - Then map:
[1, 2].map(compactObject)
compactObject(1)
→ returns1
(primitive)compactObject(2)
→ returns2
(primitive)
- Result:
[1, 2]
- Add to result:
acc["e"] = [1, 2]
- Value is
-
Final result:
{b: {d: "hi"}, e: [1, 2]}
The key observations:
- Falsy primitive values at the top level of objects/arrays are removed
- The recursion ensures nested structures are cleaned at all depths
- Arrays maintain their order but with falsy elements filtered out
- Objects only keep keys whose values are truthy after recursive processing
Solution Implementation
1def compactObject(obj):
2 """
3 Recursively removes all falsy values from an object or array
4
5 Args:
6 obj: The input object (dict), list, or any value to compact
7
8 Returns:
9 A new object/list with all falsy values removed
10 """
11 # Base case: if input is not a dict or list, return as-is
12 if not isinstance(obj, (dict, list)):
13 return obj
14
15 # Handle list case: filter out falsy values and recursively compact remaining elements
16 if isinstance(obj, list):
17 # Filter out falsy values (None, 0, False, '', [], {}, etc.)
18 # and recursively compact nested objects/lists
19 return [compactObject(item) for item in obj if item]
20
21 # Handle dict case: iterate through items and keep only truthy values
22 result = {}
23 for key, value in obj.items():
24 # Only add the key-value pair if the value is truthy
25 if value:
26 # Recursively compact the value if it's an object/list
27 result[key] = compactObject(value)
28
29 return result
30
1import java.util.*;
2import java.util.stream.Collectors;
3
4public class Solution {
5 /**
6 * Recursively removes all falsy values from an object or array
7 * @param obj - The input object or array to compact
8 * @return A new object/array with all falsy values removed
9 */
10 public Object compactObject(Object obj) {
11 // Base case: if input is not a collection/map or is null, return as-is
12 if (obj == null || !(obj instanceof Map || obj instanceof List)) {
13 return obj;
14 }
15
16 // Handle List case: filter out falsy values and recursively compact remaining elements
17 if (obj instanceof List) {
18 List<?> list = (List<?>) obj;
19 return list.stream()
20 .filter(this::isTruthy) // Remove falsy values (null, 0, false, empty string)
21 .map(this::compactObject) // Recursively compact nested objects/arrays
22 .collect(Collectors.toList());
23 }
24
25 // Handle Map case: iterate through entries and keep only truthy values
26 if (obj instanceof Map) {
27 Map<?, ?> map = (Map<?, ?>) obj;
28 Map<Object, Object> result = new HashMap<>();
29
30 for (Map.Entry<?, ?> entry : map.entrySet()) {
31 Object key = entry.getKey();
32 Object value = entry.getValue();
33
34 // Only add the key-value pair if the value is truthy
35 if (isTruthy(value)) {
36 // Recursively compact the value if it's a Map or List
37 result.put(key, compactObject(value));
38 }
39 }
40
41 return result;
42 }
43
44 return obj;
45 }
46
47 /**
48 * Helper method to determine if a value is truthy
49 * In Java context: null, 0, false, and empty string are considered falsy
50 * @param value - The value to check
51 * @return true if the value is truthy, false otherwise
52 */
53 private boolean isTruthy(Object value) {
54 if (value == null) {
55 return false;
56 }
57 if (value instanceof Boolean) {
58 return (Boolean) value;
59 }
60 if (value instanceof Number) {
61 return ((Number) value).doubleValue() != 0;
62 }
63 if (value instanceof String) {
64 return !((String) value).isEmpty();
65 }
66 // All other objects (including Maps and Lists) are considered truthy
67 return true;
68 }
69}
70
1#include <variant>
2#include <unordered_map>
3#include <vector>
4#include <memory>
5#include <string>
6
7// Forward declaration for recursive type definition
8struct Obj;
9
10// Type definition for values that can be stored in the object
11// Supports null, bool, int, double, string, nested objects, and arrays
12using Value = std::variant<
13 std::nullptr_t, // null
14 bool, // boolean
15 int, // integer
16 double, // floating point
17 std::string, // string
18 std::shared_ptr<Obj>, // nested object
19 std::shared_ptr<std::vector<Value>> // array
20>;
21
22// Type definition for a generic object with any key-value pairs
23struct Obj {
24 std::unordered_map<std::string, Value> data;
25};
26
27/**
28 * Helper function to check if a value is considered "falsy"
29 * In JavaScript context: null, undefined, 0, false, "", NaN are falsy
30 * @param value - The value to check
31 * @returns true if the value is truthy, false if falsy
32 */
33bool isTruthy(const Value& value) {
34 return std::visit([](const auto& v) -> bool {
35 using T = std::decay_t<decltype(v)>;
36
37 // null is falsy
38 if constexpr (std::is_same_v<T, std::nullptr_t>) {
39 return false;
40 }
41 // false is falsy
42 else if constexpr (std::is_same_v<T, bool>) {
43 return v;
44 }
45 // 0 is falsy
46 else if constexpr (std::is_same_v<T, int>) {
47 return v != 0;
48 }
49 // 0.0 and NaN are falsy
50 else if constexpr (std::is_same_v<T, double>) {
51 return v != 0.0 && v == v; // v == v checks for NaN
52 }
53 // empty string is falsy
54 else if constexpr (std::is_same_v<T, std::string>) {
55 return !v.empty();
56 }
57 // objects and arrays are always truthy (even if empty)
58 else {
59 return true;
60 }
61 }, value);
62}
63
64/**
65 * Recursively removes all falsy values from an object or array
66 * @param obj - The input object or array to compact
67 * @returns A new object/array with all falsy values removed
68 */
69std::shared_ptr<Obj> compactObject(const std::shared_ptr<Obj>& obj) {
70 // Base case: if input is null, return null
71 if (!obj) {
72 return nullptr;
73 }
74
75 // Create a new object to store the compacted result
76 auto result = std::make_shared<Obj>();
77
78 // Handle object case: iterate through entries and keep only truthy values
79 for (const auto& [key, value] : obj->data) {
80 // Process the value based on its type
81 std::visit([&](const auto& v) {
82 using T = std::decay_t<decltype(v)>;
83
84 // Handle array case: filter out falsy values and recursively compact remaining elements
85 if constexpr (std::is_same_v<T, std::shared_ptr<std::vector<Value>>>) {
86 if (v) {
87 auto compactedArray = std::make_shared<std::vector<Value>>();
88
89 for (const auto& element : *v) {
90 // Only add truthy values to the compacted array
91 if (isTruthy(element)) {
92 // Recursively compact nested objects within the array
93 std::visit([&](const auto& elem) {
94 using ElemT = std::decay_t<decltype(elem)>;
95
96 if constexpr (std::is_same_v<ElemT, std::shared_ptr<Obj>>) {
97 // Recursively compact nested object
98 compactedArray->push_back(compactObject(elem));
99 } else if constexpr (std::is_same_v<ElemT, std::shared_ptr<std::vector<Value>>>) {
100 // Recursively compact nested array (wrap in object for processing)
101 auto tempObj = std::make_shared<Obj>();
102 tempObj->data["array"] = elem;
103 auto compacted = compactObject(tempObj);
104 if (compacted && compacted->data.count("array")) {
105 compactedArray->push_back(compacted->data.at("array"));
106 }
107 } else {
108 // For primitive types, add as-is if truthy
109 compactedArray->push_back(elem);
110 }
111 }, element);
112 }
113 }
114
115 // Add the compacted array to the result
116 result->data[key] = compactedArray;
117 }
118 }
119 // Handle nested object case
120 else if constexpr (std::is_same_v<T, std::shared_ptr<Obj>>) {
121 if (v) {
122 // Recursively compact the nested object
123 result->data[key] = compactObject(v);
124 }
125 }
126 // Handle primitive types
127 else {
128 // Only add the key-value pair if the value is truthy
129 if (isTruthy(value)) {
130 result->data[key] = v;
131 }
132 }
133 }, value);
134 }
135
136 return result;
137}
138
1// Type definition for a generic object with any key-value pairs
2type Obj = Record<any, any>;
3
4/**
5 * Recursively removes all falsy values from an object or array
6 * @param obj - The input object or array to compact
7 * @returns A new object/array with all falsy values removed
8 */
9function compactObject(obj: Obj): Obj {
10 // Base case: if input is not an object or is null/undefined, return as-is
11 if (!obj || typeof obj !== 'object') {
12 return obj;
13 }
14
15 // Handle array case: filter out falsy values and recursively compact remaining elements
16 if (Array.isArray(obj)) {
17 return obj
18 .filter(Boolean) // Remove falsy values (null, undefined, 0, false, '', NaN)
19 .map(compactObject); // Recursively compact nested objects/arrays
20 }
21
22 // Handle object case: iterate through entries and keep only truthy values
23 return Object.entries(obj).reduce((accumulator: Obj, [key, value]: [string, any]): Obj => {
24 // Only add the key-value pair if the value is truthy
25 if (value) {
26 // Recursively compact the value if it's an object/array
27 accumulator[key] = compactObject(value);
28 }
29 return accumulator;
30 }, {} as Obj);
31}
32
Time and Space Complexity
The time complexity is O(n)
, where n
is the total number of elements (including nested elements) in the input object structure.
The algorithm visits each element exactly once during the traversal. For objects, it iterates through all key-value pairs using Object.entries()
. For arrays, it processes each element with filter()
and map()
. Since each element is processed once regardless of the depth of nesting, the overall time complexity remains linear relative to the total number of elements.
The space complexity is O(n)
for two main reasons:
- Recursion stack depth: In the worst case, the recursion depth equals the maximum nesting level of the object structure, which could be
O(n)
for a deeply nested structure (like a linked list-shaped object). - Output space: The function creates a new compacted object structure. In the worst case where no elements are removed (all values are truthy), the output size equals the input size, requiring
O(n)
space.
The intermediate operations like Object.entries()
, filter()
, and map()
also create temporary arrays, but these don't exceed the overall O(n)
space bound since they operate on one level at a time.
Common Pitfalls
Pitfall 1: Incorrect Handling of Empty Containers
The Problem:
A common mistake is not recognizing that empty arrays []
and empty objects {}
are truthy in JavaScript/Python, even though they might seem "empty". The current solution correctly preserves them, but developers often mistakenly try to remove them.
Incorrect Implementation:
# Wrong: Trying to remove empty containers
if isinstance(obj, list):
result = [compactObject(item) for item in obj if item]
return result if result else None # DON'T do this!
# Wrong: Checking for empty dict
if value and (not isinstance(value, dict) or value): # Confusing logic
result[key] = compactObject(value)
Why It's Wrong:
Boolean([])
returnstrue
in JavaScript andbool([])
returnsTrue
in PythonBoolean({})
returnstrue
in JavaScript andbool({})
returnsTrue
in Python- These empty containers should be preserved in the output
Correct Approach: The original solution handles this correctly by only checking truthiness of the values themselves, not whether containers are empty after processing.
Pitfall 2: Modifying the Original Object
The Problem: Some implementations might accidentally modify the original object instead of creating a new one.
Incorrect Implementation:
def compactObject(obj):
if isinstance(obj, dict):
# Wrong: Modifying original object
for key in list(obj.keys()):
if not obj[key]:
del obj[key] # Mutating original!
else:
obj[key] = compactObject(obj[key])
return obj
Why It's Wrong:
- This mutates the input object, which can cause unexpected side effects
- If the same object is referenced elsewhere in the code, those references will see the changes
Correct Approach: Always create new objects/arrays during the compacting process:
# Correct: Creating new dict result = {} for key, value in obj.items(): if value: result[key] = compactObject(value) return result
Pitfall 3: Not Handling Special Falsy Values Correctly
The Problem: Developers might use explicit checks that miss certain falsy values or incorrectly classify truthy values as falsy.
Incorrect Implementation:
# Wrong: Using explicit checks that miss cases
if value is not None and value != "": # Misses 0, False, etc.
result[key] = compactObject(value)
# Wrong: Using len() which doesn't work for all types
if len(str(value)) > 0: # Converts 0 to "0" which has length > 0
result[key] = compactObject(value)
Why It's Wrong:
- Explicit checks often miss edge cases (like
NaN
,0
,False
) - Type conversions can change the truthiness evaluation
Correct Approach: Use Python's built-in truthiness evaluation:
if value: # Correctly identifies all falsy values result[key] = compactObject(value)
This naturally handles all falsy values: None
, False
, 0
, 0.0
, ""
, []
(for filtering), etc.
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___
in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
12
1public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
1function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!