search

Sequential vs Parallel JavaScript: The Performance Mistake

I see this mistake in code reviews all the time. The code looks clean, the indentation is calm, and every line has an await in the right place. Then you open the Network panel and realize the page is waiting three times longer than it needs to.

The problem is not async/await. The problem is using it too literally. If three async operations do not depend on each other, waiting for them one by one is just a slow waterfall with nicer syntax.

The Mistake

This is the pattern that quietly makes pages feel slow:

// Slow: ~3 seconds total
async function loadAllSlow() {
    const a = await fetchA(); // wait 1s
    const b = await fetchB(); // wait 1s
    const c = await fetchC(); // wait 1s
    return [a, b, c];
}

Nothing is technically broken. The result is correct. But the timing is wrong. fetchB() does not start until fetchA() finishes, and fetchC() does not start until fetchB() finishes.

If those requests are independent, this is the version you usually want:

// Fast: ~1 second total
async function loadAllFast() {
    const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);
    return [a, b, c];
}

The difference is simple: the slow version starts one job at a time. The fast version starts all jobs immediately, then waits for the group.

Promise.all() fulfills when all input promises fulfill, and rejects when any input promise rejects. MDN Web Docs

The Question I Ask Before Every await

Before I write the next await, I ask one question: does this line need the result from the previous line?

  • If yes, keep it sequential.
  • If no, start the work together with Promise.all().

That one question catches most performance bugs in async code. It also keeps the code honest. Sequential code is not bad. Accidental sequential code is.

Do not parallelize dependencies. If fetchOrders(user.id) needs the user returned from fetchUser(), it must wait. Use parallel execution only when the operations are truly independent.

Live Runner: Same Work, Different Timing

Run the demo below. Both columns do the same three fake requests: 600ms, 700ms, and 800ms. The only difference is whether they start one after another or all at once.

Sequential vs Parallel: Live Runner
Sequential0.000s
  • await fetchA()
  • await fetchB()
  • await fetchC()
Parallel0.000s
  • fetchA()
  • fetchB()
  • fetchC()
Three fake fetches: 600ms + 700ms + 800ms.
const slow = await fetchA();
const slower = await fetchB();
const slowest = await fetchC();

const fast = await Promise.all([fetchA(), fetchB(), fetchC()]);

That gap is not theoretical. It is the difference between a page that feels instant and a page that makes people wait for no reason.

Another Example: Dashboard Widgets

A dashboard is where this mistake becomes obvious. The profile card, notifications card, and stats card often come from different endpoints. If they do not depend on each other, there is no reason to load them in a line.

Dashboard Loader: Waterfall vs Parallel
Profile500ms
Stats900ms
Notices700ms
Elapsed: 0.000s
const profile = await fetchProfile();
const stats = await fetchStats();
const notices = await fetchNotices();

Switch between Waterfall and Parallel and run it again. The work is identical. The user experience is not.

When Sequential Is Actually Correct

Parallel is not a religion. Some operations really are sequential:

const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);

Here, fetchOrders() needs user.id. Starting both together would be impossible or wrong. This is the part I care about most: make the dependency explicit. If the second line needs the first, await it. If it does not, start them together.

For a broader look at the syntax and error handling, see the full JavaScript async/await guide.

What I Check in Code Review

When I review async code, I look for one smell first: multiple await lines in a row.

Then I ask:

  • Does each request need data from the previous request?
  • Would Promise.all() make this faster?
  • Should one failure cancel everything, or should partial results still render?

If one failure should not kill the whole screen, Promise.allSettled() may be a better fit. For example, a broken analytics widget should not stop a profile card from rendering.

FAQs

Common questions about sequential and parallel execution with async/await.

Is sequential await always bad?
No. Sequential await is correct when each step depends on the result of the previous step. It becomes a performance bug when independent work is forced to wait in line.
When should I use Promise.all?
Use Promise.all() when several async operations can start at the same time and you need all of them to succeed before continuing.
Does Promise.all run JavaScript on multiple threads?
No. Promise.all() starts async work together, but JavaScript still runs on the same main thread. The speedup usually comes from overlapping waiting time, such as network requests.
What happens if one Promise in Promise.all fails?
Promise.all() rejects as soon as one input rejects. If you want every operation to finish and then inspect successes and failures separately, use Promise.allSettled().
Can too much parallel work be a problem?
Yes. Starting hundreds of requests at once can overload the browser, the network, or your server. Use Promise.all() for small independent groups. For large batches, add a concurrency limit.

Summary

The biggest async/await performance mistake is not using await. It is waiting in sequence when the work could have started together.

Ask one question before every await: does this line need the result from the previous line? If it does, keep it sequential. If it does not, start the work together and wait with Promise.all().

Join the Discussion
0 Comments  ]

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!

Savvy WordPress Development official logo