Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. First check if the current value is itself an object (which includes arrays) - if so, we need to explore its children
  2. After exploring any nested structures, check if the current value is undefined and replace it with null

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:

  1. 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.

  2. 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 to obj[key]. This ensures we process all nested levels before continuing.

  3. Replace undefined with null: After handling any potential nested structure, we check if the current value is undefined. If it is, we replace it with null by assigning obj[key] = null.

  4. 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.

  5. 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 Evaluator

Example 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:

  1. First call - undefinedToNull({"a": undefined, "b": {"c": undefined, "d": 2}})

    • Start iterating with for...in loop
    • Process key "a":
      • Check: Is obj["a"] (which is undefined) an object? No, typeof undefined is "undefined"
      • Check: Is obj["a"] undefined? Yes!
      • Action: Set obj["a"] = null
    • Process key "b":
      • Check: Is obj["b"] (which is {"c": undefined, "d": 2}) an object? Yes!
      • Action: Recursively call undefinedToNull({"c": undefined, "d": 2})
  2. Second call (nested) - undefinedToNull({"c": undefined, "d": 2})

    • Start iterating with for...in loop
    • Process key "c":
      • Check: Is obj["c"] (which is undefined) an object? No
      • Check: Is obj["c"] undefined? Yes!
      • Action: Set obj["c"] = null
    • Process key "d":
      • Check: Is obj["d"] (which is 2) an object? No
      • Check: Is obj["d"] undefined? No
      • Action: No change, keep value as 2
    • Return {"c": null, "d": 2}
  3. Back to first call:

    • The recursive call returns {"c": null, "d": 2}, which gets assigned to obj["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}}

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;
}
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More