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:

  1. The algorithm checks whether the current index exists as an own or inherited property (k in O).
  2. If the index does not exist, the callback function is bypassed entirely for that slot.
  3. 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); // 3

How _.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:

  1. Hole Conversion: The missing slot is accessed directly, producing undefined.
  2. Falsy Exclusion: The undefined value fails the truthiness check.
  3. Contiguous Re-indexing: The remaining truthy elements are pushed into a new, contiguous collection indexed strictly from 0 onward 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); // 2

Key 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.