Lodash omitBy vs Object.entries Performance
When processing large-scale JavaScript objects, choosing between
Lodash's _.omitBy and native methods using
Object.entries significantly affects runtime execution
speed and heap memory allocation. While Object.entries
benefits from native C++ engine optimizations, combining it with
functional array methods creates intermediate data structures that can
degrade performance during massive iterations compared to direct
iteration patterns like _.omitBy.
How Lodash
_.omitBy Operates Under the Hood
Lodash's _.omitBy is designed to construct a shallow
copy of an object while excluding properties that satisfy a predicate
function.
During iteration over a large object, _.omitBy:
- Traverses the object's enumerable own properties using internal iterator methods.
- Executes the predicate callback for every key-value pair.
- Assigns matching properties directly to a newly allocated result object.
The primary benefit of _.omitBy during massive
iterations is memory predictability. It does not construct temporary
collections of keys or values prior to filtering; it processes the
object linearly and populates the target object in a single pass.
However, it incurs the execution overhead of invoking a JavaScript
callback for every single property, which prevents V8 from inlining the
operation fully across millions of iterations.
The Cost of the
Object.entries Native Pipeline
A typical native equivalent to _.omitBy combines
Object.entries, Array.prototype.filter, and
Object.fromEntries:
Object.fromEntries(
Object.entries(sourceObject).filter(([key, value]) => !predicate(value, key))
);While native methods are highly optimized, this pipeline creates severe bottlenecks when handling objects with hundreds of thousands of keys:
- Massive Memory Allocation:
Object.entriesinstantly allocates an array containingNsub-arrays, each holding two elements ([key, value]). On a massive dataset, creating these millions of short-lived arrays triggers heavy memory usage. - Garbage Collection Pressure: After
Object.entriesruns,Array.prototype.filtercreates another array to hold the filtered tuples. OnceObject.fromEntriesreconstructs the final object, all intermediate arrays become unreachable, forcing immediate and frequent Garbage Collection (GC) pauses. - Multi-Pass Latency: The native pipeline requires three sequential passes over the dataset: one to create the entries array, one to filter the entries, and one to reconstruct the final object.
Performance and Memory Comparison
- Memory Footprint:
_.omitByrequires significantly less memory than theObject.entriespipeline._.omitByonly allocates the single output object, whereas theObject.entriesapproach allocates multiple intermediate arrays that scale directly with the size of the initial dataset. - Execution Time: For small-to-medium objects (under
10,000 keys),
Object.entriesis often faster due to native method execution. However, during massive iterations (100,000+ keys),_.omitByconsistently outperforms theObject.entriespipeline because it avoids the heavy GC churn caused by allocating millions of intermediate tuple arrays.
The Most Performant Alternative
If absolute performance on massive objects is critical, neither
_.omitBy nor Object.entries is optimal. A
direct for...in loop or a for...of loop over
Object.keys() eliminates both intermediate array
allocations and per-property callback overhead:
const result = {};
const keys = Object.keys(sourceObject);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = sourceObject[key];
if (!predicate(value, key)) {
result[key] = value;
}
}This imperative approach is considerably faster than
_.omitBy and uses a fraction of the memory consumed by
Object.entries.