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.