JavaScript
Async Programming
Async/Await
Coding Challenges
Promises

Where does async and await end? Confusion

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

A lot of confusion around async and await comes from treating them as if they create a separate execution world. They do not. An async function is still ordinary JavaScript code that runs synchronously until it reaches an await, at which point it pauses and returns control to the event loop.

What async Actually Means

When you mark a function async, JavaScript guarantees one thing: the function returns a Promise. Even if you return 42, the caller receives a fulfilled promise whose value is 42.

javascript
1async function value() {
2  return 42;
3}
4
5value().then(console.log);

That means async changes the function's contract at the call site. The caller must use await or .then() to consume the result.

Where await Pauses Execution

Inside an async function, code runs normally until JavaScript hits an await expression. At that moment:

  1. the awaited value is converted to a promise if needed
  2. the current function pauses
  3. the rest of the function is scheduled to resume later
  4. other JavaScript can continue running

A useful mental model is that await splits one function into two phases: the part before suspension and the continuation after the promise settles.

javascript
1function delay(ms) {
2  return new Promise(resolve => setTimeout(resolve, ms));
3}
4
5async function demo() {
6  console.log("start");
7  await delay(100);
8  console.log("after await");
9}
10
11console.log("before call");
12demo();
13console.log("after call");

The output order is:

  • 'before call'
  • 'start'
  • 'after call'
  • 'after await'

That ordering explains most of the confusion. The function starts immediately, pauses at await, and finishes later.

Where The Async Work Ends

The async portion of an async function ends when the returned promise settles. That can happen in two ways:

  • the function finishes normally and resolves with a value
  • an uncaught error is thrown and the promise rejects
javascript
1async function parseJson(text) {
2  const obj = JSON.parse(text);
3  return obj.name;
4}
5
6parseJson('{"name":"Ada"}')
7  .then(console.log)
8  .catch(console.error);

Even though JSON.parse itself is synchronous, the outer function still returns a promise. So the boundary is about the function contract, not whether every line inside it is slow or asynchronous.

await Does Not Create Parallelism

Another common misconception is that multiple await calls automatically run concurrently. They do not if written sequentially.

javascript
1async function sequential() {
2  const a = await delay(1000);
3  const b = await delay(1000);
4  return [a, b];
5}

That waits twice in sequence. If you want overlap, start both promises first.

javascript
1async function parallel() {
2  const first = delay(1000);
3  const second = delay(1000);
4  await Promise.all([first, second]);
5  return "done";
6}

This is often where developers think async somehow "ended too early" or "blocked too long". In reality, the structure of the code decides whether work is serialized.

Error Boundaries

Errors also define where the async flow ends. A thrown error after an await rejects the promise just as surely as a thrown error before it.

javascript
1async function risky() {
2  await delay(10);
3  throw new Error("boom");
4}
5
6(async () => {
7  try {
8    await risky();
9  } catch (err) {
10    console.log(err.message);
11  }
12})();

The try and catch boundary is the practical end of the async chain for the caller.

How To Think About It Clearly

A reliable rule is:

  • 'async affects what the function returns'
  • 'await affects where that function pauses and resumes'

Nothing magical happens outside those boundaries. JavaScript still has one event loop thread for ordinary code execution, and await simply allows continuation after a promise settles.

Common Pitfalls

The first mistake is assuming that calling an async function means all of its code runs later. The function starts immediately and runs until the first await.

Another issue is forgetting that an async function always returns a promise. Using its return value like a plain synchronous value leads to bugs.

Developers also commonly write several await expressions in a row when the tasks could run concurrently. That is not a bug in await; it is a sequencing choice in the code.

Finally, be careful with error handling. A rejected promise and a thrown exception inside an async function are part of the same promise-based control flow, so callers must still handle rejection properly.

Summary

  • An async function returns a Promise immediately, even if part of its body runs synchronously first.
  • Code runs normally until an await is reached.
  • 'await pauses the current async function and resumes it after the promise settles.'
  • The async work ends when the function's returned promise resolves or rejects.
  • Sequential await calls serialize work unless you explicitly start promises first and coordinate them with Promise.all.

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.