2775. Undefined to Null 🔒
Problem Description
This problem asks you to transform a deeply nested object or array by replacing all undefined
values with null
.
The input obj
can be any JavaScript object or array that may contain nested structures at multiple levels. Your task is to traverse through the entire structure and convert every undefined
value to null
, while keeping all other values unchanged.
The motivation behind this problem relates to JSON serialization. When using JSON.stringify()
to convert JavaScript objects to JSON strings, undefined
values are handled inconsistently - they are omitted from objects but converted to null
in arrays. This inconsistency can lead to unexpected behavior. By converting all undefined
values to null
beforehand, you ensure consistent and predictable serialization.
For example:
- Input:
{"a": undefined, "b": 3}
should return{"a": null, "b": 3}
- Input:
[undefined, undefined]
should return[null, null]
- For nested structures like
{"a": {"b": undefined}, "c": 1}
, the result should be{"a": {"b": null}, "c": 1}
The solution needs to recursively process all nested objects and arrays to ensure every undefined
value at any depth is replaced with null
.
Intuition
The key insight is that we need to visit every value in a potentially nested structure. This immediately suggests a recursive approach - when we encounter an object or array, we need to dive deeper into it before processing the current level.
Think of it like exploring a tree structure. At each node, we need to:
- First check if the current value is itself an object (which includes arrays) - if so, we need to explore its children
- After exploring any nested structures, check if the current value is
undefined
and replace it withnull
The order matters here. We process nested objects first (depth-first traversal) to ensure we handle all levels of nesting. This way, when we return from a recursive call, we've already processed all the deeper levels.
For the iteration through the object, we use a for...in
loop which works for both regular objects and arrays. In JavaScript, arrays are objects with numeric keys, so for...in
will iterate through array indices just as it iterates through object properties.
The type check typeof obj[key] === 'object'
is interesting because in JavaScript, typeof null
also returns 'object'
. However, this actually works in our favor - if a value is already null
, we'll recursively call the function on it, but since null
has no enumerable properties, the for...in
loop won't execute, and we'll just return the null
value unchanged.
This recursive pattern naturally handles arbitrary nesting depth - each recursive call handles one level deeper, and the recursion naturally terminates when we reach primitive values (numbers, strings, booleans) or null
values that have no properties to iterate over.
Solution Approach
The implementation uses a recursive depth-first traversal to process the nested structure:
function undefinedToNull(obj: Record<any, any>): Record<any, any> {
for (const key in obj) {
if (typeof obj[key] === 'object') {
obj[key] = undefinedToNull(obj[key]);
}
if (obj[key] === undefined) {
obj[key] = null;
}
}
return obj;
}
Let's walk through the implementation step by step:
-
Iterate through all properties: The
for...in
loop iterates through all enumerable properties of the object. This works for both objects and arrays since arrays in JavaScript are objects with numeric indices as keys. -
Check for nested objects: For each property, we first check if its value is an object using
typeof obj[key] === 'object'
. This condition will be true for:- Nested objects
{...}
- Arrays
[...]
null
values (though they have no properties to iterate)
When we find a nested object, we recursively call
undefinedToNull(obj[key])
and assign the result back toobj[key]
. This ensures we process all nested levels before continuing. - Nested objects
-
Replace undefined with null: After handling any potential nested structure, we check if the current value is
undefined
. If it is, we replace it withnull
by assigningobj[key] = null
. -
In-place modification: The function modifies the object in place rather than creating a new object. Each recursive call operates on the same object reference, directly updating values as it traverses.
-
Return the modified object: Finally, we return the modified object. Since we've been modifying it in place, this is the same object reference that was passed in, but with all
undefined
values replaced.
The algorithm handles edge cases naturally:
- Empty objects or arrays simply return after the loop completes with no iterations
- Primitive values at any level are left unchanged (except
undefined
) null
values are preserved since the recursive call on them does nothing- The depth of recursion is limited only by the JavaScript call stack
Time complexity: O(n)
where n is the total number of properties across all nested levels.
Space complexity: O(d)
where d is the maximum depth of nesting (for the recursion call stack).
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 small example to understand how the solution works:
Input: {"a": undefined, "b": {"c": undefined, "d": 2}}
Step-by-step execution:
-
First call -
undefinedToNull({"a": undefined, "b": {"c": undefined, "d": 2}})
- Start iterating with
for...in
loop - Process key "a":
- Check: Is
obj["a"]
(which isundefined
) an object? No,typeof undefined
is"undefined"
- Check: Is
obj["a"]
undefined? Yes! - Action: Set
obj["a"] = null
- Check: Is
- Process key "b":
- Check: Is
obj["b"]
(which is{"c": undefined, "d": 2}
) an object? Yes! - Action: Recursively call
undefinedToNull({"c": undefined, "d": 2})
- Check: Is
- Start iterating with
-
Second call (nested) -
undefinedToNull({"c": undefined, "d": 2})
- Start iterating with
for...in
loop - Process key "c":
- Check: Is
obj["c"]
(which isundefined
) an object? No - Check: Is
obj["c"]
undefined? Yes! - Action: Set
obj["c"] = null
- Check: Is
- Process key "d":
- Check: Is
obj["d"]
(which is2
) an object? No - Check: Is
obj["d"]
undefined? No - Action: No change, keep value as
2
- Check: Is
- Return
{"c": null, "d": 2}
- Start iterating with
-
Back to first call:
- The recursive call returns
{"c": null, "d": 2}
, which gets assigned toobj["b"]
- Check: Is
obj["b"]
undefined? No (it's now{"c": null, "d": 2}
) - Continue to next iteration (no more keys)
- Return
{"a": null, "b": {"c": null, "d": 2}}
- The recursive call returns
Final output: {"a": null, "b": {"c": null, "d": 2}}
The algorithm successfully replaced both undefined
values at different nesting levels with null
, while preserving the number value 2
.
Solution Implementation
1def undefinedToNull(obj):
2 """
3 Recursively converts all None values to None in an object (dict) or array (list)
4
5 Args:
6 obj: The input dict or list to process
7
8 Returns:
9 The modified object with None values explicitly set (no actual change in Python)
10 """
11 # Check if input is a dictionary
12 if isinstance(obj, dict):
13 # Iterate through all key-value pairs in the dictionary
14 for key in obj:
15 # Check if the current value is a dict or list (nested structure)
16 if isinstance(obj[key], (dict, list)):
17 # Recursively process nested dictionaries and lists
18 obj[key] = undefinedToNull(obj[key])
19
20 # In Python, None is already the equivalent of undefined/null
21 # This condition is kept for consistency with original logic
22 if obj[key] is None:
23 obj[key] = None
24
25 # Check if input is a list
26 elif isinstance(obj, list):
27 # Iterate through all elements in the list using index
28 for i in range(len(obj)):
29 # Check if the current element is a dict or list (nested structure)
30 if isinstance(obj[i], (dict, list)):
31 # Recursively process nested dictionaries and lists
32 obj[i] = undefinedToNull(obj[i])
33
34 # In Python, None is already the equivalent of undefined/null
35 # This condition is kept for consistency with original logic
36 if obj[i] is None:
37 obj[i] = None
38
39 return obj
40
41
42# Example usage:
43# undefinedToNull({"a": None, "b": 3}) # {"a": None, "b": 3}
44# undefinedToNull([None, None]) # [None, None]
45
1import java.util.Map;
2import java.util.List;
3import java.util.HashMap;
4import java.util.ArrayList;
5
6public class UndefinedToNullConverter {
7
8 /**
9 * Recursively converts all null values (representing undefined in Java context) to a specific null marker
10 * Since Java doesn't have undefined, we treat null as the value to be processed
11 * @param obj - The input object (Map or List) to process
12 * @return The modified object with appropriate null handling
13 */
14 public static Object undefinedToNull(Object obj) {
15 // Handle Map objects (equivalent to JavaScript objects)
16 if (obj instanceof Map) {
17 Map<String, Object> map = (Map<String, Object>) obj;
18
19 // Iterate through all entries in the map
20 for (Map.Entry<String, Object> entry : map.entrySet()) {
21 String key = entry.getKey();
22 Object value = entry.getValue();
23
24 // Check if the current value is a Map or List (nested structure)
25 if (value instanceof Map || value instanceof List) {
26 // Recursively process nested objects and arrays
27 map.put(key, undefinedToNull(value));
28 }
29 // Note: In Java, null values remain as null since there's no undefined
30 // This maintains consistency with the original logic
31 }
32 return map;
33 }
34
35 // Handle List objects (equivalent to JavaScript arrays)
36 if (obj instanceof List) {
37 List<Object> list = (List<Object>) obj;
38
39 // Iterate through all elements in the list
40 for (int i = 0; i < list.size(); i++) {
41 Object value = list.get(i);
42
43 // Check if the current value is a Map or List (nested structure)
44 if (value instanceof Map || value instanceof List) {
45 // Recursively process nested objects and arrays
46 list.set(i, undefinedToNull(value));
47 }
48 // Note: In Java, null values remain as null since there's no undefined
49 }
50 return list;
51 }
52
53 // Return the object unchanged if it's neither Map nor List
54 return obj;
55 }
56
57 /**
58 * Example usage:
59 * Map<String, Object> map = new HashMap<>();
60 * map.put("a", null);
61 * map.put("b", 3);
62 * undefinedToNull(map); // {"a": null, "b": 3}
63 *
64 * List<Object> list = new ArrayList<>();
65 * list.add(null);
66 * list.add(null);
67 * undefinedToNull(list); // [null, null]
68 */
69}
70
1#include <variant>
2#include <unordered_map>
3#include <vector>
4#include <memory>
5#include <any>
6
7// Define a recursive variant type to handle nested structures
8struct Value;
9using ValuePtr = std::shared_ptr<Value>;
10using Object = std::unordered_map<std::string, ValuePtr>;
11using Array = std::vector<ValuePtr>;
12
13struct Value {
14 std::variant<std::nullptr_t, int, double, std::string, bool, Object, Array> data;
15
16 // Constructor for undefined (represented as monostate initially)
17 Value() : data(nullptr) {}
18
19 // Template constructor for various types
20 template<typename T>
21 Value(T&& val) : data(std::forward<T>(val)) {}
22};
23
24/**
25 * Recursively converts all undefined values to null in an object or array
26 * @param obj - The input object (map structure) to process
27 * @returns The modified object with undefined values replaced by null
28 */
29Object undefinedToNull(Object obj) {
30 // Iterate through all key-value pairs in the object
31 for (auto& [key, value] : obj) {
32 // Check if value is null or uninitialized
33 if (!value) {
34 // Replace undefined (nullptr shared_ptr) with null
35 value = std::make_shared<Value>(nullptr);
36 continue;
37 }
38
39 // Check if the current value is an object (map structure)
40 if (std::holds_alternative<Object>(value->data)) {
41 // Recursively process nested objects
42 Object nestedObj = std::get<Object>(value->data);
43 value->data = undefinedToNull(nestedObj);
44 }
45 // Check if the current value is an array
46 else if (std::holds_alternative<Array>(value->data)) {
47 // Process array elements
48 Array& arr = std::get<Array>(value->data);
49 for (auto& element : arr) {
50 if (!element) {
51 // Replace undefined elements with null
52 element = std::make_shared<Value>(nullptr);
53 } else if (std::holds_alternative<Object>(element->data)) {
54 // Recursively process nested objects in array
55 Object nestedObj = std::get<Object>(element->data);
56 element->data = undefinedToNull(nestedObj);
57 }
58 }
59 }
60 }
61
62 return obj;
63}
64
65/**
66 * Overloaded version for processing arrays
67 * @param arr - The input array to process
68 * @returns The modified array with undefined values replaced by null
69 */
70Array undefinedToNull(Array arr) {
71 // Iterate through all elements in the array
72 for (auto& element : arr) {
73 if (!element) {
74 // Replace undefined elements with null
75 element = std::make_shared<Value>(nullptr);
76 } else if (std::holds_alternative<Object>(element->data)) {
77 // Recursively process nested objects
78 Object nestedObj = std::get<Object>(element->data);
79 element->data = undefinedToNull(nestedObj);
80 } else if (std::holds_alternative<Array>(element->data)) {
81 // Recursively process nested arrays
82 Array nestedArr = std::get<Array>(element->data);
83 element->data = undefinedToNull(nestedArr);
84 }
85 }
86
87 return arr;
88}
89
90/**
91 * Example usage:
92 * Object obj1 = {{"a", nullptr}, {"b", std::make_shared<Value>(3)}};
93 * undefinedToNull(obj1); // {"a": null, "b": 3}
94 *
95 * Array arr1 = {nullptr, nullptr};
96 * undefinedToNull(arr1); // [null, null]
97 */
98
1/**
2 * Recursively converts all undefined values to null in an object or array
3 * @param obj - The input object or array to process
4 * @returns The modified object with undefined values replaced by null
5 */
6function undefinedToNull(obj: Record<any, any>): Record<any, any> {
7 // Iterate through all enumerable properties of the object
8 for (const key in obj) {
9 // Check if the current value is an object (including arrays)
10 // Note: typeof null also returns 'object', but null values should remain unchanged
11 if (typeof obj[key] === 'object' && obj[key] !== null) {
12 // Recursively process nested objects and arrays
13 obj[key] = undefinedToNull(obj[key]);
14 }
15
16 // Replace undefined values with null
17 if (obj[key] === undefined) {
18 obj[key] = null;
19 }
20 }
21
22 return obj;
23}
24
25/**
26 * Example usage:
27 * undefinedToNull({"a": undefined, "b": 3}) // {"a": null, "b": 3}
28 * undefinedToNull([undefined, undefined]) // [null, null]
29 */
30
Time and Space Complexity
Time Complexity: O(n)
where n
is the total number of properties/elements in the object including all nested objects and arrays.
The function visits each property in the object exactly once. For nested objects, it recursively processes them, but each property is still only visited once during the entire traversal. Therefore, if there are n
total properties across all levels of nesting, the time complexity is linear.
Space Complexity: O(d)
where d
is the maximum depth of nesting in the object structure.
The space complexity comes from the recursive call stack. In the worst case, if we have a deeply nested object, the recursion will go d
levels deep, using O(d)
space for the call stack. The function modifies the object in-place rather than creating new objects, so no additional space is used for storing data beyond the recursion stack.
Note: If we consider the input size, the space complexity could also be expressed as O(n)
in the worst case when the object is structured as a deeply nested chain (like {a: {b: {c: {d: ...}}}}
) where the depth equals the number of properties.
Common Pitfalls
1. Incorrect Type Checking for Objects
The original TypeScript solution uses typeof obj[key] === 'object'
to check for nested structures. This creates a critical bug because typeof null === 'object'
in JavaScript, causing the function to attempt recursive calls on null
values.
Problem Example:
const input = {"a": null, "b": undefined}; // typeof null === 'object' returns true // Attempts to call undefinedToNull(null) // Results in attempting to iterate over null's properties
Solution: Add an explicit null check before the recursive call:
function undefinedToNull(obj: Record<any, any>): Record<any, any> {
for (const key in obj) {
// Check for non-null objects
if (obj[key] !== null && typeof obj[key] === 'object') {
obj[key] = undefinedToNull(obj[key]);
}
if (obj[key] === undefined) {
obj[key] = null;
}
}
return obj;
}
2. Mutating the Original Object
The function modifies the input object in-place, which can cause unexpected side effects if the original object is used elsewhere in the code.
Problem Example:
const original = {"a": undefined, "b": {"c": undefined}};
const result = undefinedToNull(original);
console.log(original === result); // true - same object reference
// original is now modified: {"a": null, "b": {"c": null}}
Solution: Create a deep copy before modification or build a new object during traversal:
function undefinedToNull(obj: Record<any, any>): Record<any, any> {
if (obj === null || obj === undefined) return obj;
if (Array.isArray(obj)) {
return obj.map(item =>
item === undefined ? null :
(typeof item === 'object' && item !== null) ? undefinedToNull(item) : item
);
}
if (typeof obj === 'object') {
const result: Record<any, any> = {};
for (const key in obj) {
const value = obj[key];
result[key] = value === undefined ? null :
(typeof value === 'object' && value !== null) ? undefinedToNull(value) : value;
}
return result;
}
return obj;
}
3. Not Handling Arrays Properly
While for...in
works for arrays, it iterates over all enumerable properties including non-numeric indices and inherited properties, which may not be desired behavior.
Problem Example:
const arr = [undefined, 1, 2]; arr.customProp = undefined; // Adding a non-numeric property // for...in will iterate over '0', '1', '2', and 'customProp'
Solution: Explicitly handle arrays differently using Array methods or numeric iteration:
function undefinedToNull(obj: any): any {
if (obj === null || obj === undefined) return obj;
// Handle arrays specifically
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
if (typeof obj[i] === 'object' && obj[i] !== null) {
obj[i] = undefinedToNull(obj[i]);
}
if (obj[i] === undefined) {
obj[i] = null;
}
}
return obj;
}
// Handle objects
if (typeof obj === 'object') {
for (const key in obj) {
if (obj.hasOwnProperty(key)) { // Only process own properties
if (typeof obj[key] === 'object' && obj[key] !== null) {
obj[key] = undefinedToNull(obj[key]);
}
if (obj[key] === undefined) {
obj[key] = null;
}
}
}
}
return obj;
}
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
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!