Node.js
async waterfall
parameter passing
asynchronous programming
JavaScript

passing parameters to Node.js async waterfall

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In async.waterfall, each task passes its results to the next task through the callback arguments. The first function is the only one that does not receive results from a previous step, so if you want to start the waterfall with initial parameters, you usually provide them through a wrapper, closure, or helper such as async.constant.

How waterfall Passes Values

The pattern is:

  • each task calls callback(err, value1, value2, ...)
  • the next task receives those values as positional arguments
  • if any task passes an error, the waterfall stops

Example:

javascript
1const async = require('async');
2
3async.waterfall([
4  function (callback) {
5    callback(null, 2, 3);
6  },
7  function (a, b, callback) {
8    callback(null, a + b);
9  },
10  function (sum, callback) {
11    callback(null, sum * 10);
12  }
13], function (err, result) {
14  if (err) throw err;
15  console.log(result); // 50
16});

Each step receives what the previous step emitted.

Passing Initial Parameters

Since the first function has no incoming task results, the normal pattern is to capture outside values in a closure.

javascript
1const async = require('async');
2
3function runJob(userId, limit) {
4  async.waterfall([
5    function (callback) {
6      callback(null, userId, limit);
7    },
8    function (userId, limit, callback) {
9      callback(null, `user=${userId}`, limit * 2);
10    },
11    function (label, doubledLimit, callback) {
12      callback(null, `${label}, limit=${doubledLimit}`);
13    }
14  ], function (err, result) {
15    if (err) throw err;
16    console.log(result);
17  });
18}
19
20runJob(42, 5);

This is often the clearest option.

Using async.constant

The async library also provides a neat helper for fixed initial values.

javascript
1const async = require('async');
2
3async.waterfall([
4  async.constant(42, 5),
5  function (userId, limit, callback) {
6    callback(null, userId + 1, limit * 2);
7  },
8  function (nextUserId, nextLimit, callback) {
9    callback(null, { nextUserId, nextLimit });
10  }
11], function (err, result) {
12  if (err) throw err;
13  console.log(result);
14});

This avoids writing a manual first function whose only job is to seed the pipeline.

Returning Multiple Values

waterfall can pass multiple values, not just one. That is why the callback signature matters.

javascript
callback(null, value1, value2, value3)

Then the next function must accept those parameters in the same order.

That positional style is powerful, but it also becomes fragile if too many values are threaded through the chain.

When to Pass One Object Instead

If the flow needs many related values, a single object is often easier to maintain.

javascript
1async.waterfall([
2  function (callback) {
3    callback(null, { userId: 42, limit: 5 });
4  },
5  function (ctx, callback) {
6    ctx.limit *= 2;
7    callback(null, ctx);
8  }
9], function (err, result) {
10  console.log(result);
11});

This reduces positional mistakes and makes later refactors simpler.

Modern JavaScript Note

In newer Node.js code, async.waterfall is less common than async or await. But if you are maintaining legacy async library code, understanding parameter flow is still useful.

Common Pitfalls

A common mistake is expecting the first waterfall function to receive outside parameters automatically. It does not.

Another mistake is mismatching callback output and next-function arguments, which silently shifts values into the wrong positions.

Developers also often pass too many separate values through the chain. A context object is usually easier to read and maintain once the flow grows beyond a couple of fields.

Summary

  • In async.waterfall, each task passes results to the next through callback arguments.
  • Initial parameters are usually introduced with a wrapper function, closure, or async.constant.
  • Multiple values can be passed positionally, but a single object is often easier to maintain.
  • The first task seeds the pipeline; later tasks transform the results.
  • For new code, consider async or await, but legacy waterfall code still follows these rules.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.