JavaScript
Async/Await
Concurrency
Asynchronous Programming
Promises

Nested Async Await Does not Wait

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

When nested async and await code appears not to wait, the root issue is usually an unreturned promise or a function that starts async work without awaiting it. The syntax can look correct while control flow still escapes early. Reliable async code requires explicit promise chaining and consistent return behavior.

Core Sections

Common Failure Pattern

A frequent bug is awaiting an outer function that internally launches async work but does not return that promise.

javascript
1async function innerTask() {
2  await new Promise((r) => setTimeout(r, 100));
3  console.log("inner done");
4}
5
6async function outerBad() {
7  innerTask(); // missing await or return
8}
9
10(async () => {
11  await outerBad();
12  console.log("outer finished");
13})();

This can print outer finished before inner done.

Correct Patterns

Either await inner calls or return promise chains.

javascript
1async function outerGood() {
2  await innerTask();
3}
4
5async function outerAlsoGood() {
6  return innerTask();
7}

Now callers awaiting outer functions wait for inner completion correctly.

Avoid Mixing Callback and Promise Styles

Legacy callback APIs can silently break await expectations unless wrapped correctly.

javascript
1function waitMs(ms) {
2  return new Promise((resolve) => setTimeout(resolve, ms));
3}
4
5async function task() {
6  await waitMs(50);
7  return "ok";
8}

Standardizing on promise-based APIs improves flow consistency.

Handle Arrays of Async Work Explicitly

forEach does not await async callbacks. Use for...of or Promise.all.

javascript
1async function processAll(items) {
2  for (const item of items) {
3    await innerTask(item);
4  }
5}
6
7async function processParallel(items) {
8  await Promise.all(items.map((item) => innerTask(item)));
9}

Choose sequential or parallel intentionally based on dependency and rate limits.

Add Instrumentation for Async Order

Log start and end markers with identifiers to verify execution order.

javascript
1async function traced(name, fn) {
2  console.log("start", name);
3  const out = await fn();
4  console.log("end", name);
5  return out;
6}

This helps detect missing awaits quickly in larger systems.

Testing Async Control Flow

Unit tests should assert completion order and side effects after awaited calls. In Node test frameworks, always return or await async test functions so failures are detected.

Error Propagation in Nested Async Flows

Missing await also breaks error propagation. If an inner promise rejects after outer completion, errors can surface as unhandled rejections rather than normal catch flow.

javascript
1async function innerFail() {
2  await Promise.resolve();
3  throw new Error("boom");
4}
5
6async function outerBroken() {
7  innerFail();
8}
9
10outerBroken().catch((e) => console.log("caught", e.message));

In this example, catch may not behave as expected because rejection is detached from returned chain.

Correct version:

javascript
async function outerSafe() {
  return innerFail();
}

Concurrency Limits and Resource Control

When nested async code launches many tasks, also control concurrency. Awaiting everything at once can overload databases or APIs. Use batch processing or semaphores where needed. Clear concurrency design prevents timeout storms and improves reliability.

Code review checklists that include async return-path verification catch many of these issues before runtime. Treat async control flow as a correctness concern, not just style preference.

Static analysis and lint rules that flag floating promises can prevent many nested-await defects early.

Reliable async patterns reduce production race conditions and retry noise.

Well-defined async conventions in coding standards prevent repeat mistakes in large codebases.

Documentation plus linting is an effective long-term safeguard.

Small control-flow tests catch these regressions early.

Consistent async conventions reduce costly production bugs.

This makes async behavior easier to reason about in reviews.

Common Pitfalls

  • Calling async functions without await inside awaited outer wrappers.
  • Forgetting to return nested promises from non-async wrappers.
  • Mixing callbacks and promises without proper adaptation.
  • Using forEach with async callbacks and expecting serialized waiting.
  • Writing tests that finish before asynchronous assertions execute.

Summary

  • await only waits for promises that are returned through the call chain.
  • Missing awaits or missing returns cause early completion.
  • Standardize promise-based APIs for predictable async flow.
  • Use explicit sequential or parallel iteration patterns.
  • Add trace logs and tests to verify execution order.

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.