Javascript · Async JS

Async JS Interview Questions
With Answers & Code Examples

6 carefully curated Async JS interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
6 questions0 beginner6 core0 advanced
Q1Core

Explain Promises — states, chaining, and error handling.

💡 Hint: pending → fulfilled/rejected; .then chains; .catch handles errors

A Promise is a value that may be available now or later. States: pending → fulfilled / rejected. Once settled, immutable.

fetch('/api/data')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err))
  .finally(() => setLoading(false));

Combinators: Promise.all (all must resolve), Promise.allSettled (wait all), Promise.race (first settles), Promise.any (first resolves).

Practice this question →
Q2Core

How does async/await work under the hood?

💡 Hint: Syntactic sugar over Promises; await pauses the function, not the thread

async/await is syntactic sugar over Promises. An async function always returns a Promise. await pauses only that function's execution.

async function fetchUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error('Not found');
    return await res.json();
  } catch (err) {
    console.error(err);
  }
}
💡 Parallel fetching: use Promise.all([fetch(a), fetch(b)]) instead of sequential awaits — ~2x faster.
Practice this question →
Q3Core

What is callback hell and how do you avoid it?

💡 Hint: Pyramid of doom — nested callbacks; solve with Promises/async-await

Deeply nested callbacks make code hard to read, debug, and maintain.

// ❌ Callback hell
getUser(id, (user) => {
  getPosts(user, (posts) => {
    getComments(posts[0], (comments) => { ... });
  });
});

// ✅ Async/await
const user = await getUser(id);
const posts = await getPosts(user);
const comments = await getComments(posts[0]);
Practice this question →
Q4Core

What are Promise combinators and when do you use each?

💡 Hint: all=all resolve, allSettled=wait all, race=first settles, any=first resolves
  • Promise.all() — waits for ALL to resolve. Rejects immediately if ANY rejects. Use when all must succeed.
  • Promise.allSettled() — waits for ALL to settle (resolve OR reject). Never rejects itself. Use when you need all results regardless of failure.
  • Promise.race() — settles with the FIRST settled promise (resolve or reject). Use for timeout patterns.
  • Promise.any() — resolves with the FIRST resolved promise. Rejects only if ALL reject. Use for fallback/redundancy.
const fast = fetch('/fast');   // 100ms
const slow = fetch('/slow');   // 500ms
const bad  = fetch('/broken'); // 200ms, rejects

await Promise.all([fast, slow]);    // ✅ 500ms (waits for both)
await Promise.all([fast, bad]);     // ❌ rejects at 200ms

const results = await Promise.allSettled([fast, slow, bad]);
// [{status:'fulfilled', value:...}, ..., {status:'rejected', reason:...}]

await Promise.race([fast, slow]);   // resolves at 100ms with fast result

await Promise.any([bad, fast]);     // resolves at 100ms (ignores bad)

// Timeout pattern with race:
const withTimeout = (p, ms) => Promise.race([
  p,
  new Promise((_, r) => setTimeout(() => r(new Error('Timeout')), ms))
]);
💡 allSettled is your safety net — it always resolves, making it great for "fire multiple requests, report all results" patterns.
Practice this question →
Q5Core

What is queueMicrotask() and when should you use it?

💡 Hint: Schedule a function in the microtask queue — runs after current sync, before next macrotask

queueMicrotask(fn) adds a callback directly to the microtask queue — the same queue that Promise callbacks use.

// Functionally equivalent:
Promise.resolve().then(() => console.log('A'));
queueMicrotask(() => console.log('B'));
// A, B — FIFO within the microtask queue

console.log('sync');
queueMicrotask(() => console.log('microtask'));
setTimeout(() => console.log('macrotask'), 0);
console.log('sync end');
// Order: sync → sync end → microtask → macrotask

Advantages over Promise.resolve().then():

  • No Promise overhead — slightly more performant
  • More explicit — clearly states "schedule as microtask"
  • Doesn't create a Promise chain
// Real use case: batch state updates
let pending = false;
function scheduleRender() {
  if (pending) return;
  pending = true;
  queueMicrotask(() => {
    pending = false;
    renderDOM(); // runs once after all sync mutations
  });
}
💡 Use queueMicrotask when you want something to run "ASAP but async" — after current sync code finishes, before any I/O or timers fire.
Practice this question →
Q6Core

How do you handle unhandled Promise rejections?

💡 Hint: They crash Node.js 15+ — always attach .catch() or try/catch; use global handlers as last resort

An unhandled rejection occurs when a Promise rejects with no .catch() or try/catch handler.

// ❌ Unhandled
const p = Promise.reject(new Error('oops'));
// Browser: warning in console; Node.js 15+: crashes process

// ✅ Always handle
async function fetchData() {
  try {
    return await fetch('/api');
  } catch (err) {
    if (err.status === 404) return null; // handle known
    throw err;                           // re-throw unknown
  }
}

// ✅ At call site
fetchData().catch(err => console.error(err));

// Global handlers (last resort / monitoring)
// Browser
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled:', event.reason);
  event.preventDefault(); // suppress browser logging
});

// Node.js
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection:', reason);
  process.exit(1); // recommended in production
});
💡 Never rely on global handlers for correctness — they're for logging/monitoring. Fix the root cause by ensuring every async operation is wrapped in try/catch or has .catch().
Practice this question →

Other Javascript Interview Topics

Rendering StrategiesCore JSType SystemReact FundamentalsFunctionsMicrofrontendsGenericsHooksObjectsMonorepoArrays'this' KeywordUtility TypesError HandlingModern JSBundle OptimizationPerformanceDOM & EventsState ManagementClasses & OOPCaching StrategiesComponent PatternsAdvanced TypesAuthenticationReact RouterFormsAdvanced PatternsFrontend SecurityConcurrent ReactServer ComponentsTestingEcosystemNetwork OptimizationCore Web VitalsBrowser APIs

Ready to practice Async JS?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →