Lodash Compact vs Native Map on Sparse Arrays
JavaScript arrays containing empty slots—commonly referred to as
sparse arrays—present unique indexing challenges across different
utility functions. While native methods like
Array.prototype.map respect the absence of explicit
elements by skipping unallocated indices and preserving empty slots,
Lodash’s _.compact processes array length uniformly,
evaluating missing slots as falsy values and re-indexing the retained
elements sequentially. This article examines the mechanical differences
between how _.compact handles sparse indices and how native
array mapping maintains sparse structures.
The Mechanics of Sparse Arrays in JavaScript
A sparse array is an array where certain indices have not been
assigned a value, creating "holes" rather than storing
undefined. For example, executing
const arr = [1, , 3] results in an array of length 3 where
index 0 and 2 exist, but index 1
does not exist as an own property of the object (1 in arr
returns false).
When JavaScript operations encounter these holes, behavior diverges
depending on whether the operation inspects property existence via the
internal [[HasProperty]] check or accesses values by
reading index positions directly up to the length
property.
How Native
Array.prototype.map Handles Missing Slots
Native Array.prototype.map conforms to ECMAScript
specifications that explicitly check for property existence before
invoking the callback. During iteration:
- The algorithm checks whether the current index exists as an own or
inherited property (
k in O). - If the index does not exist, the callback function is bypassed entirely for that slot.
- The resulting array retains the hole at the exact same index position, preserving the sparse architecture and length of the source array.
Because the callback is never executed for missing slots, native
mapping does not alter the index distribution of the existing elements,
nor does it normalize holes to undefined.
const sparse = [10, , 30];
const mapped = sparse.map(x => x * 2);
console.log(mapped); // [20, <1 empty item>, 60]
console.log(1 in mapped); // false
console.log(mapped.length); // 3How _.compact
Handles Sparse Arrays
Lodash’s _.compact is designed to create a new array
with all falsy values removed (false, null,
0, "", undefined, and
NaN). Its underlying implementation does not check whether
a property exists via the in operator; instead, it
processes the array using an index-based loop from 0 to
length - 1.
When _.compact reads an empty slot via direct property
access (array[index]), the JavaScript runtime evaluates the
non-existent property as undefined.
// Conceptual representation of Lodash's internal compact logic
function compact(array) {
let resIndex = 0;
const result = [];
if (array == null) {
return result;
}
for (const value of array) {
if (value) {
result[resIndex++] = value;
}
}
return result;
}Because undefined is falsy:
- Hole Conversion: The missing slot is accessed
directly, producing
undefined. - Falsy Exclusion: The
undefinedvalue fails the truthiness check. - Contiguous Re-indexing: The remaining truthy
elements are pushed into a new, contiguous collection indexed strictly
from
0onward via an internal pointer.
const sparse = [10, , 30];
const compacted = _.compact(sparse);
console.log(compacted); // [10, 30]
console.log(1 in compacted); // true (value is 30)
console.log(compacted.length); // 2Key Differences in Index Allocation
| Feature | Native
Array.prototype.map |
Lodash _.compact |
|---|---|---|
| Hole Detection | Checks property existence
(HasProperty) |
Direct lookup (returns
undefined) |
| Index Preservation | Retains original index offsets | Collapses empty slots; re-indexes to a dense array |
| Output Array Length | Equal to the input array's
length |
Decreased by the number of falsy and missing slots |
| Array Density | Remains sparse | Produces a strictly dense array |
Native mapping treats missing slots as structurally deliberate and
preserves the coordinate space of the array. Conversely,
_.compact normalizes sparse holes into runtime
undefined values and discards them, resulting in a strictly
dense, zero-indexed array where subsequent elements shift to fill the
omitted index positions.