All Articles

RxJS Transducers vs Method Chaining Performance

After reading about transducers and why they’re awesome, I played around with some large arrays and measured their timings.

If you’re new to transducers I’ll quickly explain here, but highly recommend you read this intro.

Max 10 Million Salaries

Let’s make an array of 10 million users, each with a six-figure salary:

users = Array(10000000)
  .fill()
  .map((_, index) => ({
    name: `User ${index + 1}`,
    salary: Math.floor(Math.random() * 999999) + 100000
  }));

Now we want to:

  • Get the even-numbered salaries using filter().
  • Calculate the weekly paycheck using map().
  • Get the highest paycheck using reduce().

We’ll focus on two implementations…

Method Chaining

As of this writing, method chaining’s more familiar to me.

map, filter, and reduce are array methods. They also return an array, so we can chain them.

Let’s use console.time and console.timeEnd to measure how long the operation takes.

getMaxEvenPaycheck = (users) => {
  console.time('Method chaining');

  const max = users
    .filter((user) => user.salary % 2 === 0)
    .map((user) => user.salary / 52)
    .reduce((a, b) => Math.max(a, b));

  console.timeEnd('Method chaining');

  return max;
};

Here’s our result:

This works just fine! The operation took about 3.56 seconds.

Our speed, however, can improve.

Since methods like map, reduce, and filter return arrays, chaining them means you’re wasting time by creating intermediate arrays.

Transducers

Instead of intermediate arrays, a transducer processes one element at a time, like an assembly line.

We get the same result in less time.

My favorite library for this is RxJS. The concept is similar, but we’re operating on one element at a time.

getMaxEvenPaycheck = (users) => {
  const { filter, map, max } = Rx.operators;

  console.time('Transducer');

  return Rx.Observable.from(users).pipe(
    filter((user) => user.salary % 2 === 0),
    map((user) => user.salary / 52),
    max()
  );
};

getMaxEvenPaycheck(users).subscribe((max) => {
  console.log('max:', max);
  console.timeEnd('Transducer');
});

By streaming users and manipulating elements one-by-one with RxJS operators, our performance increased by about 75%!

Here’s the screenshots side by side, for reference:

I’m exploring this topic more, and would love any resources you could throw at me!