How is async/await different from Promises?

Intermediate12 min interview
Skills tested:
async/await as syntactic sugar over PromisesError handling with try/catch vs .catch()Sequential vs parallel execution with awaitasync function return value behaviorCommon pitfalls of awaiting in loops

Advertisement

🧩 Scenario

In a real codebase, you will use async/await in every function that performs network requests, file reads, database queries, or any other operation that returns a Promise. The choice between Promise chains and async/await is mostly stylistic, but async/await wins on readability for sequential operations and produces more debuggable stack traces. The most common pitfalls involve error handling gaps, sequential awaiting when parallel execution is possible, and misusing await inside array iteration methods.

Architecture Walkthrough

async/await Is Built on Promises

async/await is syntactic sugar introduced in ES2017. An async function always returns a Promise. When the function returns a value, that value is wrapped in a resolved Promise. When the function throws, the thrown value becomes a rejected Promise. The await keyword pauses execution within the async function until the awaited Promise settles, then unwraps the resolved value.

Underneath, the JavaScript runtime translates await expressions into the equivalent Promise .then() chains. No new asynchronous mechanism is introduced. This means that everything you know about Promises, including microtask queue timing, rejection handling, and chaining, still applies when using async/await.

Error Handling: try/catch vs .catch()

With Promises, errors are handled by appending .catch() to the chain or providing a rejection handler as the second argument to .then(). With async/await, the natural equivalent is a try/catch block around the await call. Both approaches handle rejected Promises, but they differ in readability and in how errors from multiple operations are grouped.

A single try/catch block can cover multiple await calls, which is concise but means you lose granularity: any of the awaited operations could be the one that threw. For production code, it is often better to wrap each critical await individually or use a helper that wraps each call in a tuple pattern ([error, data]) to maintain per-operation error context.

Sequential vs Parallel Execution

await pauses the current function until a Promise settles. If you await two Promises sequentially, the second does not start until the first finishes. For independent operations (two separate API calls that do not depend on each other), this is wasteful.

Promise.all() accepts an array of Promises and returns a single Promise that resolves when all of them settle. Using Promise.all([fetchUser(), fetchPosts()]) with a single await allows both requests to run in parallel. The total time is determined by the slowest operation rather than the sum of all operations.


Key Code Explained

// Promise chain version
function loadUser(id) {
  return fetch(`/api/users/${id}`)
    .then((res) => res.json())
    .then((user) => {
      console.log(user);
      return user;
    })
    .catch((err) => console.error(err));
}

// Equivalent async/await version
async function loadUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    const user = await res.json();
    console.log(user);
    return user;
  } catch (err) {
    console.error(err);
  }
}

// Sequential vs parallel
async function loadDashboard(userId) {
  // Sequential: total time = time(user) + time(posts)
  const user = await fetchUser(userId);
  const posts = await fetchPosts(userId);

  // Parallel: total time = max(time(user), time(posts))
  const [user, posts] = await Promise.all([
    fetchUser(userId),
    fetchPosts(userId),
  ]);
}

// await inside forEach does NOT pause iteration
async function processItems(items) {
  items.forEach(async (item) => {
    await processItem(item); // does not pause the loop
  });
  // All items are "processing" simultaneously, forEach has already returned

  // Correct: use for...of to preserve sequential awaiting
  for (const item of items) {
    await processItem(item); // pauses loop for each item
  }
}

The forEach example is the most commonly misunderstood. The async callback passed to forEach returns a Promise, but forEach ignores that Promise and continues to the next iteration immediately. The outer function does not wait for any of the inner async operations to finish.


Tradeoffs

ApproachProCon
async/awaitReadable, debuggable stack traces, natural try/catchCan accidentally serialize parallel operations
Promise chainsExplicit data flow, composableVerbose for multi-step sequences, harder to debug
Promise.allParallel execution, single await pointAll Promises start regardless of earlier failures
Promise.allSettledRuns all even if some fail, gives per-result statusMore verbose to handle results, no short-circuit on failure

What Interviewers Actually Check

  • Whether you know async/await is syntactic sugar over Promises, not a separate mechanism
  • Whether you can correctly convert a Promise chain to async/await and back
  • Whether you know why await inside forEach does not work and what to use instead
  • Whether you understand when to use Promise.all for parallel execution
  • Whether you know an async function always returns a Promise even when it returns a primitive

Follow-Up Questions

  1. What is the difference between Promise.all and Promise.allSettled, and when would you choose each?
  2. If you await a value that is not a Promise (like a plain number), what happens?
  3. How do you handle a situation where you want to run multiple async operations in parallel but cancel all of them if one fails?
  4. What is Promise.race and what use case does it solve?
  5. A teammate proposes adding async to every function by default "just in case." What are the downsides?

Common Candidate Mistakes

  • Using await inside a forEach loop and expecting each iteration to pause before the next starts
  • Not wrapping await calls in try/catch and losing visibility into rejected Promises
  • Awaiting three independent API calls sequentially when they could run in parallel with Promise.all
  • Forgetting that an async function always returns a Promise, so calling .then() on it is valid
  • Using top-level await in a CommonJS file and getting a syntax error because it is only valid in ES modules

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you explain that async/await is syntactic sugar on top of Promises and does not introduce new runtime behavior?
  • Can you convert a .then()/.catch() chain into equivalent async/await code?
  • Can you explain why await inside forEach does not work and what to use instead?
  • Can you demonstrate running multiple async operations in parallel using Promise.all?
  • Can you explain what an async function returns when it throws an error?

Summary

async/await is syntactic sugar over Promises. An async function always returns a Promise, and await pauses execution within the function until a Promise settles, producing cleaner sequential-looking code for operations that were previously written as .then() chains.

The main readability advantage is error handling: try/catch is more natural than chained .catch() calls for most developers, and stack traces in async/await code are easier to read in debuggers. The main pitfall is accidentally serializing independent operations by awaiting them one at a time. Any two operations that do not depend on each other's results should be run with Promise.all instead of sequential awaits.

Understanding the relationship between async/await and Promises is essential because the underlying Promise semantics still govern timing, error propagation, and cancellation behavior. async/await does not change how JavaScript handles asynchronous operations; it only changes how you write them.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

Can I use await outside async functions?

No. await only works inside async functions (or top-level in ES2022+ modules).

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement