Asynchronous JavaScript — Interview Questions
🔴 Asynchronous JavaScript
Section titled “🔴 Asynchronous JavaScript”Q1. How does the JavaScript Event Loop work?
Section titled “Q1. How does the JavaScript Event Loop work?”Answer: The event loop allows JavaScript (single-threaded) to perform non-blocking operations.
Call Stack → Web APIs → Task Queue → Microtask QueueOrder of execution:
- Call Stack — synchronous code runs first
- Microtask Queue — Promises (
.then),queueMicrotask,MutationObserver - Macrotask Queue —
setTimeout,setInterval,setImmediate
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// Output: 1 → 4 → 3 → 2Q2. What are Promises in JavaScript?
Section titled “Q2. What are Promises in JavaScript?”Answer: A Promise represents a value that will be available now, in the future, or never.
States: pending → fulfilled or rejected
const fetchData = () => new Promise((resolve, reject) => { setTimeout(() => resolve("data"), 1000);});
fetchData() .then(data => console.log(data)) // "data" .catch(err => console.error(err)) .finally(() => console.log("done"));
// Promise combinatorsPromise.all([p1, p2, p3]) // all resolve or one rejectsPromise.allSettled([p1, p2, p3]) // waits for all, regardlessPromise.race([p1, p2, p3]) // first to settle winsPromise.any([p1, p2, p3]) // first to RESOLVE winsQ3. What is async/await?
Section titled “Q3. What is async/await?”Answer:
async/await is syntactic sugar over Promises, making async code look synchronous.
async function fetchUser(id) { try { const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error("User not found");
const user = await response.json(); return user; } catch (error) { console.error("Failed:", error.message); }}
// Parallel executionasync function loadAll() { const [users, posts] = await Promise.all([ fetchUsers(), fetchPosts() ]);}Q4. What is Promise.all vs Promise.allSettled vs Promise.race vs Promise.any?
Section titled “Q4. What is Promise.all vs Promise.allSettled vs Promise.race vs Promise.any?”Answer:
const p1 = Promise.resolve(1);const p2 = Promise.reject("error");const p3 = Promise.resolve(3);
// Promise.all — rejects if ANY rejectsPromise.all([p1, p2, p3]).catch(e => console.log(e)); // "error"Promise.all([p1, p3]).then(v => console.log(v)); // [1, 3]
// Promise.allSettled — waits for ALL, never rejectsPromise.allSettled([p1, p2, p3]).then(results => { results.forEach(r => console.log(r.status, r.value ?? r.reason)); // "fulfilled" 1 | "rejected" "error" | "fulfilled" 3});
// Promise.race — first to SETTLE (resolve or reject) winsPromise.race([p1, p2, p3]).then(v => console.log(v)); // 1
// Promise.any — first to RESOLVE wins (ignores rejections)Promise.any([p2, p1, p3]).then(v => console.log(v)); // 1Q5. Explain the difference between microtasks and macrotasks with execution order.
Section titled “Q5. Explain the difference between microtasks and macrotasks with execution order.”Answer:
// Macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering// Microtasks: Promise.then/catch/finally, queueMicrotask, MutationObserver
console.log("1"); // sync
setTimeout(() => console.log("2"), 0); // macrotask
Promise.resolve() .then(() => { console.log("3"); // microtask setTimeout(() => console.log("4"), 0); // macrotask (scheduled inside microtask) }) .then(() => console.log("5")); // microtask (chained)
queueMicrotask(() => console.log("6")); // microtask
console.log("7"); // sync
// Output: 1 → 7 → 3 → 6 → 5 → 2 → 4// Explanation:// Sync: 1, 7// Microtasks: 3, 6, 5 (3 adds setTimeout, 6 is queueMicrotask, 5 is chained)// Macrotasks: 2 (original), 4 (added during microtask)Q6. How does JavaScript’s async/await work under the hood?
Section titled “Q6. How does JavaScript’s async/await work under the hood?”Answer:
// async/await is syntactic sugar over Promises + generators
// This async function:async function fetchUser(id) { const response = await fetch(`/api/${id}`); const user = await response.json(); return user;}
// Is roughly equivalent to:function fetchUser(id) { return Promise.resolve() .then(() => fetch(`/api/${id}`)) .then(response => response.json()) .then(user => user);}
// Under the hood, V8 transforms async functions into state machines:// 1. Function runs synchronously until first await// 2. Suspends execution, returns Promise// 3. When awaited Promise resolves, execution resumes// 4. Repeat until function returns
async function demo() { console.log("A"); // sync await Promise.resolve(); console.log("B"); // after microtask}console.log("before");demo();console.log("after");// Output: before → A → after → BQ7. How do you implement Promise.all from scratch?
Section titled “Q7. How do you implement Promise.all from scratch?”Answer:
function myPromiseAll(promises) { return new Promise((resolve, reject) => { if (!promises.length) return resolve([]);
const results = new Array(promises.length); let resolved = 0;
promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = value; resolved++; if (resolved === promises.length) { resolve(results); } }) .catch(reject); // first rejection cancels all }); });}
// TestmyPromiseAll([ Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then(console.log); // [1, 2, 3]Q8. How do you implement async/await using generators?
Section titled “Q8. How do you implement async/await using generators?”Answer:
// async/await can be polyfilled using generators
function asyncToGenerator(generatorFn) { return function(...args) { const generator = generatorFn.apply(this, args);
return new Promise((resolve, reject) => { function step(key, arg) { let result; try { result = generator[key](arg); } catch (error) { return reject(error); }
const { value, done } = result;
if (done) { resolve(value); } else { Promise.resolve(value).then( val => step("next", val), err => step("throw", err) ); } }
step("next", undefined); }); };}
// Usageconst fetchUser = asyncToGenerator(function*(id) { const response = yield fetch(`/api/users/${id}`); const user = yield response.json(); return user;});Q9. Implement a pub/sub (event emitter) system.
Section titled “Q9. Implement a pub/sub (event emitter) system.”Answer:
class EventEmitter { #listeners = new Map();
on(event, listener) { if (!this.#listeners.has(event)) { this.#listeners.set(event, new Set()); } this.#listeners.get(event).add(listener); return () => this.off(event, listener); // return unsubscribe fn }
once(event, listener) { const wrapper = (...args) => { listener(...args); this.off(event, wrapper); }; return this.on(event, wrapper); }
off(event, listener) { this.#listeners.get(event)?.delete(listener); }
emit(event, ...args) { this.#listeners.get(event)?.forEach(listener => { listener(...args); }); }
removeAllListeners(event) { if (event) this.#listeners.delete(event); else this.#listeners.clear(); }}
const emitter = new EventEmitter();const unsub = emitter.on("data", data => console.log("Received:", data));emitter.emit("data", { id: 1 }); // "Received: {id:1}"unsub(); // unsubscribeQ10. How do you implement a rate limiter in JavaScript?
Section titled “Q10. How do you implement a rate limiter in JavaScript?”Answer:
class RateLimiter { #queue = []; #running = 0; #limit; #interval;
constructor(limit, interval) { this.#limit = limit; // max requests this.#interval = interval; // per interval (ms) }
async execute(fn) { return new Promise((resolve, reject) => { this.#queue.push({ fn, resolve, reject }); this.#process(); }); }
#process() { while (this.#queue.length && this.#running < this.#limit) { const { fn, resolve, reject } = this.#queue.shift(); this.#running++;
Promise.resolve() .then(() => fn()) .then(resolve) .catch(reject) .finally(() => { setTimeout(() => { this.#running--; this.#process(); }, this.#interval); }); } }}
// Max 3 requests per secondconst limiter = new RateLimiter(3, 1000);const fetchWithLimit = url => limiter.execute(() => fetch(url));Q11. What are async generators?
Section titled “Q11. What are async generators?”Answer:
async function* asyncRange(start, end) { for (let i = start; i <= end; i++) { await new Promise(resolve => setTimeout(resolve, 100)); yield i; }}
// Consume with for await...ofasync function main() { for await (const num of asyncRange(1, 5)) { console.log(num); // 1, 2, 3, 4, 5 — each delayed 100ms }}
// Real use: paginated API fetchingasync function* fetchPages(url) { let page = 1; while (true) { const data = await fetch(`${url}?page=${page}`).then(r => r.json()); if (!data.items.length) return; yield data.items; page++; }}Q12. What is the AbortController?
Section titled “Q12. What is the AbortController?”Answer:
// Cancel fetch requestsconst controller = new AbortController();const signal = controller.signal;
fetch("/api/data", { signal }) .then(res => res.json()) .then(data => console.log(data)) .catch(err => { if (err.name === "AbortError") { console.log("Request was cancelled"); } });
// Cancel after 5 secondssetTimeout(() => controller.abort(), 5000);
// Real use: cancel stale search requestslet currentController;input.addEventListener("input", async (e) => { currentController?.abort(); // cancel previous currentController = new AbortController(); const results = await fetch(`/search?q=${e.target.value}`, { signal: currentController.signal });});Q13. What is Promise.withResolvers (ES2024)?
Section titled “Q13. What is Promise.withResolvers (ES2024)?”Answer:
// Before ES2024 — needed to create resolve/reject outside promiselet resolve, reject;const promise = new Promise((res, rej) => { resolve = res; reject = rej;});
// ES2024 — cleaner APIconst { promise, resolve, reject } = Promise.withResolvers();
// Useful for callback-based APIsfunction waitForEvent(element, eventName) { const { promise, resolve } = Promise.withResolvers(); element.addEventListener(eventName, resolve, { once: true }); return promise;}Q14. Explain the JavaScript memory model and garbage collection.
Section titled “Q14. Explain the JavaScript memory model and garbage collection.”Answer:
JavaScript uses automatic garbage collection with the Mark-and-Sweep algorithm.
Memory lifecycle:
- Allocate — when declaring variables, objects, functions
- Use — reading/writing
- Release — GC reclaims unreachable memory
// Memory leak examples and fixes:
// 1. Forgotten timersconst id = setInterval(() => { /* uses reference */ }, 1000);clearInterval(id); // ✅ always clear
// 2. Detached DOM nodeslet div = document.createElement("div");document.body.appendChild(div);document.body.removeChild(div);div = null; // ✅ remove reference
// 3. Closures holding large datafunction process(largeData) { const summary = summarize(largeData); return function() { return summary; // ✅ only keep what's needed };}
// 4. Global variablesfunction oops() { leakedVar = "I am global!"; // ❌ no var/let/const}