Understanding Array Iteration Choices in Lodash's compact Implementation

The compact functon in Lodash filters out falsy values (false, null, 0, "", undefined, NaN) from an array and returns a new array containing only truthy elements. Its implementation is minimal yet deliberate—especially in how it iterates.

Core Implementation

Here's a refactored version of the logic, preserving correctness while renaming identifiers and adjusting structure for clarity:

function filterTruthy(input) {
  if (input == null) return [];

  const output = [];
  let writePosition = 0;

  for (const item of input) {
    if (item) {
      output[writePosition++] = item;
    }
  }

  return output;
}

This version avoids mutation of external state, uses descriptive names (input, output, writePosition), and retains the same behavior as the original compact.

Why for...of Over Alternatives?

Traditional for Loop

A C-style loop works but introduces index management overhead:

for (let idx = 0; idx < input.length; idx++) {
  const val = input[idx];
  if (val) output[writePosition++] = val;
}

While functional, it’s more verbose and error-prone (e.g., off-by-one bugs, accidental mutations to idx).

for...in: Unsafe for Arrays

for...in enumerates all enumerable properties—including inherited ones and non-numeric keys:

const data = [1, 2, 3];
data.extra = 'unintended';
for (const key in data) {
  console.log(key); // May log "0", "1", "2", "extra"
}

It also provides no guarantee of iteration order and treats sparse arrays inconsistently—making it unsuitable for reliable array traversal.

for...of: Purpose-Built for Iterable Collections

for...of relies on the iterable protocol via Symbol.iterator. For arrays, this guarantees:

  • Order-preserving traversal (index order, left-to-right)
  • Exclusion of non-element properties (no inherited or custom keys)
  • Native support without manual iterator construction

You can inspect the underlying iterator:

const seq = [10, 20, 30];
const iter = seq[Symbol.iterator]();
console.log(iter.next()); // { value: 10, done: false }
console.log(iter.next()); // { value: 20, done: false }
console.log(iter.next()); // { value: 30, done: false }
console.log(iter.next()); // { value: undefined, done: true }

This protocol-based approach enables composability—for example, custom iterators that transform values on-the-fly:

Array.prototype[Symbol.iterator] = function* () {
  for (let i = 0; i < this.length; i++) {
    yield this[i] * 3;
  }
};

However, such overrides are discouraged in production code due to global side effects. The default for...of behavior remains safe, predictable, and aligned with ES specification expectations.

Tags: javascript ecmascript iterable for-of Lodash

Posted on Tue, 19 May 2026 18:35:20 +0000 by michaeru