Lodash Prototype Reverse and Array Mutation
This article examines how the Lodash JavaScript library handles
sequence reversals, detailing the fundamental structural mutation caused
by native prototype.reverse during chained and mapped
transformations. It covers the internal differences between eager
mutation using native Array.prototype.reverse and deferred
traversal via lazy evaluation wrappers, illustrating how mapped data
structures are reorganized in memory.
The Underlying Mutation: In-Place Index Reversal
In standard JavaScript and Lodash execution pipelines, the
fundamental structural mutation that reorganizes mapped values is
in-place buffer mutation executed via
Array.prototype.reverse. When invoked on a standard Lodash
sequence wrapper (LodashWrapper), Lodash delegates the
operation directly to native array reversal mechanisms rather than
creating an immutable clone.
Native Array.prototype.reverse mutates the target array
directly in memory by swapping indices from the outer boundaries inward
toward the center:
- The element at index
iis swapped with the element at indexlength - 1 - i. - The references stored at each slot are preserved, but their positional keys are transposed.
- The underlying array reference (
this.__wrapped__) is modified permanently in place.
When operations such as .map() produce an intermediate
mapped collection in an eager sequence, calling .reverse()
fundamentally rearranges those mapped elements by mutating that
intermediate array instance rather than allocating a newly indexed
container.
Eager Chains vs. Lazy Evaluation
Lodash employs two distinct structural wrappers depending on how
methods are chained: LodashWrapper (eager/shortcut-capable
chaining) and LazyWrapper (true deferred evaluation).
1. Eager Chains
(LodashWrapper)
In standard chaining (e.g.,
_(array).map(fn).reverse().value()), if the chain cannot be
lazily compiled, Lodash executes operations sequentially. The mapping
pass yields a new array of transformed values, and
.reverse() applies Array.prototype.reverse
natively to that collection. The mutation directly targets the physical
array pointer held inside the wrapper instance.
2. Deferred Direction
Flipping (LazyWrapper)
When Lodash activates lazy evaluation (often triggered when chaining
arrays of length greater than 200 with methods like filter,
map, and take), calling
.reverse() avoids immediate in-place mutation.
Instead of mutating memory positions,
LazyWrapper.prototype.reverse performs a structural
configuration change on the wrapper instance itself:
- It flips an internal direction property:
this.__dir__ = this.__dir__ * -1. - It records the reversal step in the wrapper's pipeline
(
__actions__).
When the chain is resolved via .value(), iteration
begins from the tail index instead of the head index. However, if
unshift/splice or strict native calls force synchronization, Lodash
falls back to flushing pending actions through
Array.prototype.reverse.
Structural Effects on Deep and Nested Data
While prototype.reverse fundamentally mutates the index
positions of the array structure, its effect on deeper nested structures
depends on reference handling:
- Shallow Index Transposition: The physical array
housing the mapped values has its top-level pointers inverted. Index
0points to the object previously at indexn - 1. - Reference Preservation: Nested objects, arrays, or functions mapped inside the parent collection are not cloned. Their internal structures remain unchanged, but their parent lookup path in the mapped sequence is reversed.
- Side Effects on Shared Wrapped Buffers: Because
eager
.reverse()operates destructively, any external variable holding a direct reference to the intermediate evaluated array will observe the swapped indices immediately without subsequent reassignment.