Lodash Chain Map and Filter Order of Operations

When working with the Lodash JavaScript library, chaining sequence methods like map and filter via _.chain() alters how code is evaluated compared to standard native JavaScript arrays. Instead of eagerly executing each transformation and generating intermediate arrays, Lodash leverages lazy evaluation and shortcut fusion. This article explains the explicit order of operations that occurs behind the scenes when executing multiple map and filter operations within a Lodash chain.

Deferred Execution via .value()

Lodash chains use deferred execution. Calling methods such as _.chain(data).filter(...).map(...) does not immediately process any items. Instead, it builds an internal sequence pipeline wrapped in a lodash wrapper object.

Execution is completely deferred until the terminating .value() method is explicitly invoked. Only at this point does the computation begin.

Shortcut Fusion: Element-by-Element Traversal

Unlike native JavaScript array chaining—which executes collection-by-collection (running the first filter on all items, then the first map on all surviving items)—Lodash uses shortcut fusion.

Shortcut fusion reorganizes the pipeline to iterate through the source array element-by-element, rather than pass-by-pass:

  1. Item Selection: The engine takes the first item from the source array.
  2. Sequential Step Application: It passes that single item through the chain of filter and map operations in the exact order they were chained.
  3. Short-Circuiting (Branch Pruning): If a filter predicate returns false (or a falsy value) for that item, execution for that item halts immediately. Any subsequent map or filter steps in the chain are skipped, and the engine moves directly to the next item in the collection.
  4. Result Accumulation: If an item successfully passes all filters and maps, the final transformed value is appended to the output array.
  5. Next Element: The process repeats for the next element in the source array until the collection is exhausted.

Step-by-Step Execution Trace

Consider the following Lodash chain:

const result = _.chain([1, 2, 3, 4])
  .filter(x => {
    console.log(`filter1: ${x}`);
    return x % 2 === 0;
  })
  .map(x => {
    console.log(`map1: ${x}`);
    return x * 10;
  })
  .filter(x => {
    console.log(`filter2: ${x}`);
    return x > 20;
  })
  .map(x => {
    console.log(`map2: ${x}`);
    return x + 1;
  })
  .value();

When .value() executes, the explicit order of operations proceeds as follows:

The console output strictly reflects this interleaved, single-pass pipeline:

filter1: 1
filter1: 2
map1: 2
filter2: 20
filter1: 3
filter1: 4
map1: 4
filter2: 40
map2: 40

Exceptions to Fusion

While map and filter freely fuse together, certain operations act as execution boundaries that force intermediate arrays to be realized:

When intermediate operations require a full dataset, Lodash completes the fused map/filter pipeline up to that point, constructs the intermediate array, performs the required non-lazy operation, and resumes execution for the remainder of the chain.