How Lodash mergeWith Prevents Infinite Recursion

In the Lodash library, _.mergeWith safely handles circularly referenced objects by delegating operations to an internal traversal function that maintains an operational state stack. Instead of blindly traversing object trees, Lodash uses an internal caching mechanism to track object references encountered during the recursive walk. When an already-visited object is encountered within the source hierarchy, Lodash resolves the reference from the stack rather than initiating a new recursive call, completely mitigating the risk of RangeError: Maximum call stack size exceeded errors and denial-of-service vulnerabilities.

At the core of this safety mechanism is Lodash's internal baseMerge function, which accepts an optional hidden argument: stack. When _.mergeWith or _.merge is invoked, Lodash initializes an instance of its internal Stack class if one has not already been provided. This stack object acts as an associative lookup structure that persists across all nested invocations during that single merge operation.

As the merge algorithm descends into nested objects or arrays via baseMergeDeep, it executes the following sequence:

  1. Pre-Traversal Lookup: Before recursing into a non-primitive value, the algorithm checks stack.get(srcValue). If this source reference already exists in the stack, Lodash immediately assigns the retrieved cached target reference to the destination key and halts further recursion along that branch.
  2. Stack Registration: If the object has not been visited, Lodash creates or determines the corresponding target structure (array or plain object), then calls stack.set(srcValue, assigned). This step pairs the original source reference with the newly generated or modified destination reference before iterating over its keys.
  3. Recursive Descent: The function iterates over the object’s enumerable properties, passing the customizer callback (if supplied to _.mergeWith) and the ongoing stack to subsequent recursive calls.
  4. Reference Preservation: If an inner property points back to an ancestor object, the pre-traversal check detects the ancestor in the stack, returns the matching assigned target, and exits the branch safely.

Lodash’s Stack implementation is optimized for both performance and memory efficiency. For small numbers of keys (fewer than 200 elements), it uses a lightweight array of key-value pairs called ListCache. When the number of tracked objects exceeds this threshold, it automatically upgrades itself to a MapCache, which utilizes JavaScript’s native Map whenever available.

Because the stack exists only for the duration of the top-level merge execution, all tracked references become eligible for garbage collection once _.mergeWith returns its final output, avoiding long-term memory leaks while maintaining robust protection against circular recursion.