How Lodash isEqual Prevents Recursion Limits

Lodash's _.isEqual performs deep comparisons between values while actively guarding against infinite recursion and call stack exhaustion. To prevent recursion limits from being reached, Lodash relies on an internal stack-based cache to detect circular references, fast-path reference checks, structural early-exit validations, and an adaptive memoization structure that keeps deep object traversals performant and safe.

Circular Reference Detection via an Internal Stack

The primary mechanism for preventing infinite recursive loops in _.isEqual is the internal Stack class (found in _baseIsEqualDeep.js and _Stack.js). When evaluating nested objects, Lodash maintains an execution stack representing the ancestry of currently traversed structures:

  1. Stack Association: Before recursing into nested properties of two objects, baseIsEqualDeep records the current pair in the stack via stack.set(objA, objB).
  2. Lookup Prior to Traversal: During recursive steps, Lodash queries the stack using stack.get(objA). If objA has already been recorded and matches objB, it indicates a circular reference that has already been resolved or is currently in progress.
  3. Loop Short-Circuiting: Upon finding a matching pair in the stack, Lodash bypasses further recursion and returns true, effectively breaking reference loops that would otherwise trigger a RangeError: Maximum call stack size exceeded.

Fast-Path Identity Checks

Before invoking deep traversal logic, Lodash executes strict identity checks via baseIsEqual. If value === other, Lodash immediately returns true (with a special case adjustment for +0 and -0, as well as NaN).

For objects sharing the same memory reference, this check returns immediately, preventing the engine from iterating over child keys or allocating comparison stacks.

Structural Pre-Checks and Early Exits

Recursion depth is significantly reduced through heuristics executed before traversing nested properties:

Adaptive Stack Architecture (ListCache to MapCache)

To ensure tracking overhead does not degrade performance or exhaust memory during deep evaluations, the internal Stack dynamically scales its internal data structure: