When execution inside an `async` function reaches its first `await`, what does the caller of that function receive at that moment, and is anything blocked while the awaited value is still pending?
answer
- one function pauses, the thread does not
- the prefix already ran on the call
- the caller gets a placeholder immediately
- locals survive the pause intact
- only awaits yield, never busy loops
basics
~20 sThe caller receives a pending promise straight away. The async function's frame is suspended, not blocked: its locals are parked, control returns to the caller, and the rest of the program keeps running until the awaited value settles and the function resumes.
solid answer
~50 sCalling the function runs its body synchronously up to the first `await` — that prefix is not deferred. At the `await`, the function *suspends*: it hands control back to its caller, which immediately receives the already-created promise in pending state. Nothing is blocked. The single thread carries on with the caller's next statement and any other queued work; the suspended frame just sits there holding its local variables, `this`, and its position in the body. When the awaited value settles, the function is resumed from that exact point with everything intact, and it later fulfils or rejects its own returned promise. So `await` pauses one function, not the thread — which is why a caller that forgets to await sees a `Promise { <pending> }` instead of a value, and why CPU-heavy synchronous work inside an async function still freezes everything.
code
javascript · 15 linesfunction slowValue() {
return new Promise(resolve => setTimeout(() => resolve('done'), 50));
}
async function run() {
console.log('1 sync prefix');
const v = await slowValue(); // frame suspends, caller continues
console.log('4 resumed with', v);
return v.toUpperCase();
}
const promise = run();
console.log('2 caller got', promise instanceof Promise ? 'a pending promise' : promise);
console.log('3 caller keeps running');
promise.then(v => console.log('5 settled', v));go deeper
Know that the call gives you a promise immediately, that you need await or .then to reach the value, and that seeing Promise { <pending> } in a log means you forgot one.
Describe the three phases — synchronous prefix, suspension at the await, scheduled resumption — and use the word suspend rather than wait, explaining that the thread is released while the frame is parked.
Show the production consequence: only awaits yield, so synchronous hot spots between awaits still stall everything, and suspended frames pin their locals in memory for as long as they are parked.
Own the design view: async boundaries are where a system can interleave work, so choosing where a call suspends is a latency and fairness decision, not a syntax preference. Be ready to argue what belongs off the main thread altogether.
## Three phases of one call Calling an async function has three distinct phases, and mixing them up is the source of most confusion. 1. **Synchronous prefix.** The moment you call it, the body starts executing immediately, on the caller's stack, exactly like a normal function. Everything before the first `await` — argument validation, logging, kicking off a request — has already happened by the time the call expression evaluates. 2. **Suspension.** At the first `await`, the frame is parked. Control returns to the caller, which gets the pending promise that was created before the body started. 3. **Resumption.** Once the awaited value has settled, the frame is restored and execution continues on the line after the `await`, with all locals exactly as they were. Further awaits repeat phases 2 and 3. ```js function compute() { return Promise.resolve(21); } async function work() { console.log('A'); // phase 1: runs on the call const v = await compute(); // phase 2: suspends here console.log('C', v); // phase 3: resumes here return v * 2; } const p = work(); // logs 'A' console.log('B', p); // logs 'B' Promise { <pending> } p.then(x => console.log('D', x)); // A, B Promise { <pending> }, C 21, D 42 ``` ## Suspending is not blocking Blocking would mean the thread stops and does nothing until a result arrives — no other function runs, no other queued work is processed. That is not what happens. `await` removes *this function* from the stack and leaves the thread free. The caller's remaining statements run, other pending work runs, and in a browser the page keeps responding. The distinction has a sharp practical consequence: `await` cannot rescue you from slow **synchronous** code. A tight loop burning 400 ms inside an async function still occupies the thread for 400 ms, awaits or no awaits, because there is no suspension point inside it. Async functions give up the thread only at an `await`. ## The caller always gets the promise, never the value The returned promise exists before the body starts, so the caller receives it at the first suspension (or when the body finishes, if there is no await). It is pending until the function completes. A caller that logs the call result without awaiting sees `Promise { <pending> }` — the single most common beginner bug, and the reason async-ness is "contagious": to get a value, the caller must itself await, which means the caller must itself be async or must use `.then`. ## What survives the suspension The frame keeps everything: local `const`/`let` bindings, the parameter values, the `this` binding, closed-over variables, and the position inside any surrounding loop or `try` block. That is why you can write sequential-looking code that stops and starts, and why an object captured in a local variable stays reachable — and therefore not collectable — for the whole time the function is suspended. A long-lived suspended frame that holds a large buffer keeps that buffer alive. ## Resumption is scheduled, not immediate When the awaited value settles, the function does not resume inside the settling code. The continuation is queued as a job and runs later, once the currently executing code has finished. That is what guarantees the run-to-completion property: no other code interleaves halfway through a statement. The precise queue mechanics and the tick on which the resumption lands belong to the event loop rather than to the async-function sugar; what you must be able to state here is that resumption is always deferred, never synchronous — even if the awaited promise is already settled. ## Awaiting the same promise twice, and awaiting nothing Suspension happens per `await`, not per promise. Two awaits of the same already-settled promise suspend twice. And because suspension is unconditional, even `await` on a non-promise value defers the rest of the function. There is no fast path that keeps you on the current stack. ## How interviewers probe it Typically: "does `await` block the thread?", "what does the caller see while it is waiting?", or "why is my UI still frozen when everything is async?". A strong answer names the three phases, uses the word *suspend* rather than *wait*, and points out that only awaits yield — synchronous work never does.
- A page is still frozen even though every function in the handler is async. What does that tell you?That the cost is synchronous work, not waiting. `await` yields the thread only at a suspension point; a heavy parse, sort, or loop between awaits holds the single thread for its full duration. Fix it by breaking the work into chunks that yield, or by moving it off the main thread entirely — adding more `async` keywords changes nothing.
- If the awaited promise is already fulfilled, does the function continue immediately without suspending?No. Suspension is unconditional: the continuation is queued and runs after the current code finishes, even for an already-settled promise. That preserves run-to-completion — nothing interleaves halfway through the caller's statement — and it is why `await` on an already-resolved value still reorders your logs.
- What keeps a suspended frame's local variables alive while it waits?The suspended frame itself holds them, and it is reachable from the continuation registered on the awaited promise. So anything a local references stays alive for the whole suspension. A frame parked on a long request while holding a large buffer keeps that buffer out of the garbage collector's reach for the duration.
saying these in an interview costs you the question
- Says await blocks the thread until the value arrives
- Thinks the body does not start until you await the call
- Believes the caller receives the resolved value directly
- Says async runs the function on another thread
- Assumes nothing before the first await runs synchronously