Lodash Set Polyfill Garbage Collection and Security
This article examines how Lodash implements its internal Set
mechanisms—primarily through SetCache and
MapCache—to maintain memory safety, facilitate efficient
garbage collection, and prevent security vulnerabilities. By isolating
key-value storage, avoiding global state retention, and actively
neutralizing prototype pollution risks, Lodash ensures that temporary
collections are safely allocated and swiftly reclaimed by the JavaScript
runtime's garbage collector.
Architecture of Lodash's Internal Set
Rather than polyfilling the global window.Set or
global.Set, Lodash encapsulates unique-value collections
inside internal classes named SetCache and
MapCache. A SetCache wraps around
MapCache, which dynamically chooses between different
storage mechanisms depending on the data types and dataset size:
- ListCache: Used for smaller sets (typically under 200 elements), performing linear scans over an array of key-value pairs.
- Hash: Used for large sets of primitive keys, utilizing plain JavaScript objects as lookup tables.
- Map: Utilized directly if the native JavaScript
Mapis supported by the execution environment.
This tiered system ensures optimal lookup performance while managing the memory footprint dynamically.
Ephemeral Lifecycles and Scoped Garbage Collection
The primary mechanism Lodash uses to ensure clean garbage collection
is strict lifecycle scoping. Functions that require uniqueness
checks—such as _.uniq, _.difference, and
_.intersection—create a SetCache locally
within the execution frame of the function call.
Because the cache exists only within the function's local scope, it is dropped as soon as the function returns its transformed output. The JavaScript engine’s mark-and-sweep garbage collector quickly identifies that there are no remaining reference paths from the root context to the cache. Consequently, both the cache structure and the references it held become immediately eligible for memory reclamation.
Reference Severing via Explicit Clear Methods
In instances where caches persist across multiple operations (such as
in memoized functions), Lodash avoids memory leaks through built-in
cache-clearing mechanisms. Each cache abstraction implements a
clear() method that resets internal state:
ListCache.prototype.cleardrops its internal__data__array by assigning it to a fresh, empty array (this.__data__ = []).MapCache.prototype.clearreinitializes its sub-caches (hash,map, andstring).Hash.prototype.clearassigns a fresh dictionary created with no prototype (this.__data__ = Object.create ? Object.create(null) : {}).
Reassigning these internal properties unlinks previously stored object references. Severing these pointers prevents stale object graphs from remaining pinned in heap memory, allowing the garbage collector to reclaim unreachable instances.
Prototype Pollution Defense and Memory Isolation
A critical security vulnerability in JavaScript object-based sets is
prototype pollution. If user-controlled input contains keys such as
__proto__, constructor, or
prototype, writing them directly to a standard object
({}) can alter the global Object.prototype.
This can introduce security exploits and cause memory leaks, as modified
prototype chains permanently retain references in the global scope.
Lodash mitigates this within its Hash cache by creating
storage containers via Object.create(null) when supported.
Because these objects do not inherit from Object.prototype,
properties like toString or __proto__ are
treated as normal string keys rather than special object accessors.
For environments lacking Object.create(null), Lodash
uses a prefixing or separate handling mechanism for sensitive keys. By
preventing prototype pollution, Lodash ensures that keys are confined to
the instance and cannot leak into root-level objects, preserving both
runtime security and predictable garbage collection behavior.
Type-Segregated Key Storage
Lodash separates internal storage by type to avoid hash collision
issues and unexpected string coercion. Object references, primitive
strings, and other primitive types are routed to their respective
sub-caches inside MapCache.
Complex objects are retained as distinct references rather than
stringified representations. When native Map or
Set implementations are present, Lodash delegates to them
directly. This allows the host V8 or JavaScriptCore engine to handle
reference counts and collection passes at the native C++ layer, ensuring
that objects held by the cache do not outlive their utility.