Skip to content
GeeksSmith
intermediate 7 min read

Promises, Async/Await & Race Conditions

A Promise is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value, preventing callback hell via chainable state transitions (pending -> fulfilled/rejected).

Why it matters in interviews

Handling stale async responses, race conditions, AbortController cancellations, and error boundaries in async operations is fundamental to robust real-world frontend apps.

Visual & Interactive Explanation

Promise Combinators Comparison Matrix

Interactive Comparison

Promise.all

Fail-fast parallel execution

All or Nothing
ResolvesWhen ALL resolve
RejectsOn FIRST rejection
Use CaseCoupled data dependencies
Strengths
  • Maximum concurrency
  • Fails quickly without waiting
Trade-offs
  • One failure discards all successful data

Promise.allSettled

Resilient parallel execution

Fault Tolerant
ResolvesWhen ALL settle (resolve or reject)
RejectsNever rejects
Use CaseIndependent widgets / dashboards
Strengths
  • No data loss from partial failures
  • Provides status per promise
Trade-offs
  • Requires post-processing status array

Promise.race

First settled wins

Speed / Timeout
ResolvesOn FIRST resolved promise
RejectsOn FIRST rejected promise
Use CaseNetwork timeout racing
Strengths
  • Fastest response
  • Great for timeout boundaries
Trade-offs
  • Ignores slower valid responses

Code Examples & Implementation

Fixing Race Conditions with AbortController
let currentController: AbortController | null = null;

async function searchUsers(query: string) {
  // Cancel the previous pending request
  if (currentController) {
    currentController.abort();
  }
  
  currentController = new AbortController();
  
  try {
    const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
      signal: currentController.signal,
    });
    return await res.json();
  } catch (err: any) {
    if (err.name === 'AbortError') {
      console.log('Previous search cancelled cleanly');
      return null;
    }
    throw err;
  }
}

Interview-Ready Answers

Promises handle async operations cleanly. async/await is syntactic sugar over Promises. Combinators like Promise.all fail fast on first error, while Promise.allSettled waits for all outcomes.

Official Documentation & Specifications

Follow-up Questions & Deep Dives

🎯 Code Output — What Does This Print?

Question 1medium
const p = new Promise((resolve, reject) => {
  console.log("executor");
  resolve("done");
});

p.then((val) => console.log(val));
console.log("after");
Question 2hard
Promise.resolve(1)
  .then((x) => x + 1)
  .then((x) => { throw new Error("fail"); })
  .then((x) => console.log("success:", x))
  .catch((e) => console.log("caught:", e.message))
  .then(() => console.log("after catch"));
Question 3tricky
async function bar() {
  console.log("bar1");
  await new Promise((r) => setTimeout(r, 0));
  console.log("bar2");
}

async function foo() {
  console.log("foo1");
  await bar();
  console.log("foo2");
}

foo();
console.log("end");
Question 4hard
console.log("start");

Promise.all([
  Promise.resolve("a"),
  Promise.reject("b"),
  Promise.resolve("c"),
])
  .then((results) => console.log("results:", results))
  .catch((err) => console.log("error:", err));

console.log("end");

Common Mistakes & Anti-Patterns

  • Forgetting to handle .catch() or try/catch resulting in unhandled promise rejections
  • Using forEach with async callbacks (it executes concurrently without awaiting)
  • Not cancelling obsolete HTTP requests in autocomplete/search inputs

Real-World Architectural Scenario

A user types 'apple' then rapidly changes to 'banana'. The 'apple' request takes 800ms and 'banana' takes 200ms. Without cancellation, 'apple' overwrites 'banana'. How do you resolve this?

Rate Your Readiness

Rate how comfortably you can explain this in an interview to update your global readiness gauge:

Rate your confidence: