When an interviewer asks you to explain promises and async/await, they want to see that you understand both the what and the why of modern JavaScript concurrency. Below is a compact framework you can use to structure your response, plus the deeper details you should be ready to discuss.

One‑sentence definition

A promise is a JavaScript object that represents the eventual completion (or failure) of an asynchronous operation and yields its result, while async/await are language keywords that let you write promise‑based code that looks like ordinary synchronous statements.

How promises work under the hood

  1. States – A promise starts as pending. It can transition to fulfilled (with a value) or rejected (with a reason). Once settled, the state never changes.
  2. Executor – When you create a promise with new Promise((resolve, reject) => { … }), the executor runs immediately and receives two callbacks.
  3. Chaining – promise.then(onFulfilled, onRejected) returns a new promise. The returned promise adopts the value returned by the handler, allowing you to build a chain of asynchronous steps.
  4. Microtasks – After the current call stack empties, the JavaScript engine processes the promise reaction queue (a microtask queue). This guarantees that .then callbacks run before the next macrotask (e.g., setTimeout).

async/await mechanics

  • The async keyword marks a function as returning a promise automatically.
  • Inside an async function, await expr pauses execution until expr settles. The expression must be a promise; non‑promise values are wrapped with Promise.resolve.
  • While the function is paused, the engine schedules the rest of the code as a microtask, preserving the same ordering guarantees as regular promise chains.
  • Errors thrown inside an async function are captured and cause the returned promise to reject, which you can handle with try/catch or a downstream .catch.

Trade‑offs: readability vs. control

AspectPromise chainsasync/await
ReadabilityCan become nested or hard to follow with many .then calls.Linear flow resembles synchronous code; easier to read for most developers.
Error handlingErrors must be propagated through each .catch or a final .catch.try/catch works naturally, but you must remember that await only catches the awaited promise.
ParallelismPromise.all is explicit; you can start many promises before awaiting.You can still use Promise.all, but a naïve await inside a loop runs sequentially, which may be slower.
DebuggingStack traces can be fragmented across then callbacks.Modern browsers preserve async stack traces, making debugging simpler.
CompatibilityWorks in any environment that supports ES6 promises.Requires ES2017 or a transpiler for older browsers.

When to prefer one over the other

  • Use async/await for straightforward, linear flows where readability is a priority.
  • Use promise methods (Promise.all, Promise.race, etc.) when you need to coordinate multiple concurrent operations or when you want to avoid the overhead of an extra async function.

Concrete example

// Fetch user data and their posts, then log a summary.
async function showUserSummary(userId) {
  try {
    // Start both requests in parallel.
    const [user, posts] = await Promise.all([
      fetch(`/api/users/${userId}`).then(r => r.json()),
      fetch(`/api/users/${userId}/posts`).then(r => r.json())
    ]);

    console.log(`${user.name} has ${posts.length} posts.`);
  } catch (e) {
    console.error('Failed to load data:', e);
  }
}

If you wrote the same logic with pure promises, you would need two separate fetch calls, two .then chains, and a final Promise.all to combine them. The async version is shorter and mirrors the mental model of “get both things, then do something.”

Typical interview follow‑up questions

  1. Why does await only pause the current async function and not the whole call stack? – Because await yields control back to the event loop, allowing other tasks to run while the promise settles.
  2. What happens if you forget to await a promise inside an async function? – The promise starts executing, but the function continues immediately; any errors become unhandled rejections unless you later attach a .catch.
  3. How do you run multiple async operations in parallel with await? – By creating the promises first (e.g., via fetch) and then awaiting Promise.all on the array of promises.
  4. Can you await a non‑promise value? – Yes; the value is automatically wrapped with Promise.resolve, so the await resolves immediately.
  5. What is the difference between Promise.resolve() and new Promise(resolve => resolve(value))? – Both produce a fulfilled promise, but Promise.resolve may return an existing promise unchanged, avoiding extra allocation.

60‑second spoken answer (sample)

"A promise is an object that stands for a value that will be available later, either as a result or an error. It starts pending, then settles to fulfilled or rejected, and you attach handlers with .then or .catch. async/await are just syntax sugar: marking a function async makes it return a promise, and await pauses that function until the awaited promise settles, letting you write asynchronous code that reads like normal sequential code. The main trade‑off is readability—await makes most flows easier to follow—but you have to be careful with parallelism; a naïve await inside a loop runs sequentially, so you often combine await with Promise.all to keep things concurrent. Error handling is also simpler with try/catch inside an async function. In practice, I use async/await for most business logic and fall back to raw promises when I need fine‑grained control over concurrency."

How to practice this

  1. Write the same logic twice – Implement a small feature (e.g., fetching data and combining results) once with pure promises and once with async/await. Compare length, readability, and error handling.
  2. Record yourself – Use Call Assistant to capture a 60‑second spoken answer, then replay it to check pacing and whether you stay within the time limit.
  3. Play the interview game – Pair with a colleague and take turns asking each other “Why would you use Promise.all here?” and “What happens if you forget an await?” Keep the conversation focused on the concepts, not on any specific company.

FAQ

  • What is the difference between a resolved promise and a fulfilled promise? A resolved promise is a generic term meaning the promise is no longer pending; it may be fulfilled with a value or rejected with a reason. "Fulfilled" specifically refers to the successful case.
  • Can I use await at the top level of a script? Modern ECMAScript modules allow top‑level await, but traditional scripts still require an async function wrapper.
  • Why do promise callbacks run before setTimeout callbacks? Promise reactions are placed in the microtask queue, which the engine processes after the current call stack but before the next macrotask queue (where setTimeout callbacks live).
  • Is it safe to mix async/await with .then chains? Yes; they interoperate because both ultimately produce promises. Just be consistent about error handling to avoid duplicated catches.

Frequently asked questions

What is a promise in JavaScript?

A promise is an object that represents a value that may become available later. It starts pending and can settle to either fulfilled (with a value) or rejected (with an error).

How does async/await simplify promise usage?

`async` marks a function to always return a promise, and `await` pauses that function until the awaited promise resolves, letting you write asynchronous code that looks like sequential statements.

When should I prefer raw promises over async/await?

Prefer raw promises when you need fine‑grained control over concurrency, such as building custom combinators or when you must start many operations without awaiting each immediately.

What common mistake leads to unhandled promise rejections?

Forgetting to `await` a promise inside an async function or not attaching a `.catch` to a promise chain can cause the rejection to go unhandled.

#concept#promises#async/await#javascript#interview#promises and async/await