Before promises, Node.js code coordinated async work with error-first callbacks — every task calls back with (err, result). Libraries like async gave you runners on top of that: parallel fires all tasks at once and waits for them all; series runs them one after another. Rebuilding both teaches the two fundamental async shapes — fan-out and sequencing — in their rawest form.
Implement parallel(tasks, done) and series(tasks, done). Each task is a function taking a node-style callback: task((err, result) => {...}). When all tasks finish, call done(null, results) with results in task order. If any task errors, call done(err) once and stop.
function parallel(tasks, done) {} // start all at once; done(null, [r0, r1, ...])
function series(tasks, done) {} // one at a time; done(null, [r0, r1, ...])
// task: (cb) => cb(err, result)
parallel([
(cb) => setTimeout(() => cb(null, 'a'), 30),
(cb) => setTimeout(() => cb(null, 'b'), 5),
], (err, results) => {
// results === ['a', 'b'] — task order, even though 'b' finished first
});
series([step1, step2, step3], (err, results) => {
// step2 starts only after step1 calls back; results in order
});
cb(err, result); a non-null err means failure.parallel — starts all tasks immediately; done(null, results) fires once every task has completed.series — starts the next task only after the previous one calls back; strictly sequential.tasks order, not completion order.done(err) and never call done again (and, for series, don't start later tasks). An empty list calls done(null, []).Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.