Skip to main content
Arthur Ha

JavaScript Fundamentals

How JavaScript Runs Many Async Tasks on One Thread

March 19, 2026

JavaScript has one main thread. That part is true.

But one thread does not mean one task at a time in the way most people imagine. The trick is simple: JavaScript executes your code, then the runtime handles waiting work in the background.

The easiest mental model

Think of JavaScript as a cashier.

  • The cashier takes orders quickly.
  • The kitchen cooks in parallel.
  • When food is ready, the cashier calls your number.

In this model:

  • Cashier = JavaScript thread
  • Kitchen = browser/Node runtime + OS
  • Calling your number = event loop picking ready callbacks

That is why Promise.all([taskA(), taskB(), taskC()]) feels parallel. All three tasks are started quickly, then the runtime handles the waiting.

What actually happens step by step

JavaScript
const [a, b, c] = await Promise.all([taskA(), taskB(), taskC()]);
  1. taskA() starts async work.
  2. taskB() starts async work.
  3. taskC() starts async work.
  4. JavaScript stack is now free.
  5. Runtime waits for each task to finish.
  6. When each task finishes, promise callbacks are queued.
  7. Event loop runs those callbacks on the same JS thread.
  8. Promise.all resolves when all are fulfilled (or rejects on first failure).

Important detail: tasks are not sitting in the callback queue the whole time. They are in the runtime first. They enter queues only when results are ready.

Promise.all vs Promise.allSettled

Both start tasks in the same way. The difference is how the final result is handled.

Promise.all

  • Good when all results are required
  • Rejects fast on first failure
  • You get one success array only if every task succeeds

Promise.allSettled

  • Good when partial success is acceptable
  • Waits for every task to finish
  • Returns per-task results with status: "fulfilled" or status: "rejected"
JavaScript
const results = await Promise.allSettled([taskA(), taskB(), taskC()]);

This is perfect for dashboards, bulk operations, or any UI where you want to show what succeeded and what failed.

Why this matters in real apps

If you run independent requests one by one, users wait longer. If you start them together, total waiting time usually drops to the slowest request, not the sum of all requests.

Use concurrent async calls when tasks do not depend on each other. Use sequential await only when order matters.

Common misunderstanding

"Single thread means no concurrency."

Not exactly. Single thread means JavaScript callbacks run one at a time. Concurrency still happens because network, timers, and I/O are managed outside the JS call stack.

Summary

JavaScript stays single-threaded for executing your code, but async work can still move concurrently in the runtime.

That is the core mechanism behind async/await, Promise.all, and Promise.allSettled. Once this clicks, async code becomes much easier to reason about.