Master JavaScript Promise interview questions with detailed answers. Covers Promise states, chaining, Promise.all/race/allSettled/any, async/await, error handling, and output questions.
Promises are the backbone of async JavaScript. Every interview from mid-level to senior level includes at least 3-5 Promise questions. This guide covers all of them with detailed answers.
Tips for Interview 💡
- Always mention the 3 states: pending, fulfilled, rejected.
- Know the difference between
Promise.all, Promise.allSettled, Promise.race, Promise.any. - Explain that
async/await is syntactic sugar over Promises. - Promise executor runs synchronously;
.then callbacks run as microtasks. - Unhandled promise rejections cause process crashes in Node.js.
Q1: Create a Basic Promise
function fetchData(shouldFail) {
return new Promise((resolve, reject) => {
console.log("Executing..."); // SYNC
setTimeout(() => {
if (!shouldFail) {
resolve({ data: "Success!", status: 200 });
} else {
reject(new Error("Network failed!"));
}
}, 1000);
});
}
fetchData(false)
.then(result => console.log("Resolved:", result.data))
.catch(err => console.log("Error:", err.message));
fetchData(true)
.then(result => console.log("Resolved:", result.data))
.catch(err => console.log("Error:", err.message));
Executing...
Executing...
Resolved: Success!
Error: Network failed!
Q2: Promise Chaining
function step(name, delay) {
return new Promise(resolve => {
setTimeout(() => {
console.log(name, "done");
resolve(name);
}, delay);
});
}
step("Step 1", 100)
.then(val => {
console.log("Got:", val);
return step("Step 2", 50);
})
.then(val => {
console.log("Got:", val);
return "Final";
})
.then(val => console.log("Final:", val));
Step 1 done
Got: Step 1
Step 2 done
Got: Step 2
Final: Final
Q3: What is the Output? (Tricky)
const p = new Promise((resolve, reject) => {
console.log("A");
resolve("B");
reject("C"); // ignored — promise already settled
console.log("D");
});
p.then(val => console.log("Then:", val))
.catch(err => console.log("Catch:", err));
console.log("E");
Key Points:
- Executor runs synchronously → A, D print immediately.
- Once
resolve("B") is called, promise is settled → reject("C") is ignored. "E" prints before .then (microtask).
Q4: Promise.all — All must succeed
const p1 = Promise.resolve("User data");
const p2 = Promise.resolve("Product data");
const p3 = Promise.resolve("Order data");
Promise.all([p1, p2, p3])
.then(results => {
console.log("All resolved:", results);
})
.catch(err => console.log("One failed:", err));
All resolved: ["User data", "Product data", "Order data"]
With failure:
Promise.all([
Promise.resolve("A"),
Promise.reject("Error!"),
Promise.resolve("C")
])
.then(vals => console.log(vals))
.catch(err => console.log("Failed:", err));
Q5: Promise.allSettled — Don't fail on rejection
Promise.allSettled([
Promise.resolve("Success"),
Promise.reject("Failure"),
Promise.resolve("Another success")
]).then(results => {
results.forEach(r => {
if (r.status === "fulfilled") {
console.log("✅", r.value);
} else {
console.log("❌", r.reason);
}
});
});
✅ Success
❌ Failure
✅ Another success
Use case: When you want results from ALL promises, regardless of failure.
Q6: Promise.race — First to settle wins
const slow = new Promise(resolve => setTimeout(() => resolve("slow"), 500));
const fast = new Promise(resolve => setTimeout(() => resolve("fast"), 100));
const error = new Promise((_, reject) => setTimeout(() => reject("error"), 200));
Promise.race([slow, fast, error])
.then(val => console.log("Winner:", val))
.catch(err => console.log("Error:", err));
Use case: Timeout patterns — race a fetch with a timeout promise.
Q7: Promise.any — First to FULFILL wins (ignores rejections)
Promise.any([
Promise.reject("Error 1"),
Promise.resolve("First success"),
Promise.resolve("Second success")
]).then(val => console.log("First success:", val))
.catch(err => console.log("AggregateError:", err.errors));
First success: First success
If ALL reject:
Promise.any([
Promise.reject("E1"),
Promise.reject("E2"),
Promise.reject("E3")
]).catch(err => {
console.log(err instanceof AggregateError); // true
console.log(err.errors);
});
Q8: Comparison Table
| Method | Behavior | Short-circuit |
|---|
Promise.all | All must fulfill | On first rejection |
Promise.allSettled | Wait for all to settle | Never |
Promise.race | First to settle (fulfill or reject) | On first settlement |
Promise.any | First to fulfill | On first fulfillment |
Q9: Error Handling Chain
Promise.resolve("start")
.then(val => {
console.log(val);
throw new Error("Something broke");
})
.then(val => {
console.log("This won't run");
})
.catch(err => {
console.log("Caught:", err.message);
return "recovered";
})
.then(val => {
console.log("After recovery:", val);
});
start
Caught: Something broke
After recovery: recovered
Rule: Once an error is thrown in a chain, it skips all .then until the nearest .catch. After .catch returns, the chain continues normally.
Q10: .finally()
function loadData() {
let loading = true;
return Promise.resolve("data")
.then(data => {
console.log("Data:", data);
return data;
})
.finally(() => {
loading = false;
console.log("Loading done:", loading);
});
}
loadData().then(val => console.log("Final:", val));
Data: data
Loading done: false
Final: data
Note: .finally() does NOT modify the resolved value — it passes through.
Q11: async/await Syntax
async function getUser(id) {
const user = await fetch(`/api/users/${id}`).then(r => r.json());
return user;
}
// Equivalent Promise version:
function getUser(id) {
return fetch(`/api/users/${id}`)
.then(r => r.json());
}
Q12: async/await Error Handling
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (err) {
console.log(`Attempt ${i + 1} failed: ${err.message}`);
if (i === retries - 1) throw err;
}
}
}
Q13: Sequential vs Parallel Promises
// SEQUENTIAL (slow — waits for each)
async function sequential() {
console.time("sequential");
const a = await delay(100, "A");
const b = await delay(100, "B");
const c = await delay(100, "C");
console.timeEnd("sequential"); // ~300ms
return [a, b, c];
}
// PARALLEL (fast — all at once)
async function parallel() {
console.time("parallel");
const [a, b, c] = await Promise.all([
delay(100, "A"),
delay(100, "B"),
delay(100, "C")
]);
console.timeEnd("parallel"); // ~100ms
return [a, b, c];
}
function delay(ms, val) {
return new Promise(resolve => setTimeout(() => resolve(val), ms));
}
sequential: ~300ms
parallel: ~100ms
Q14: Promisify Callback
function readFile(path, callback) {
setTimeout(() => callback(null, "file contents"), 100);
}
// Promisify manually
function readFilePromise(path) {
return new Promise((resolve, reject) => {
readFile(path, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
readFilePromise("./file.txt").then(content => console.log(content));
Q15: Output — Promise in if/else
let p;
if (Math.random() > 0.5) {
p = Promise.resolve("heads");
} else {
p = Promise.resolve("tails");
}
p.then(val => console.log("Result:", val));
console.log("After if/else");
After if/else
Result: heads (or tails — random)
Q16: Async Function Always Returns Promise
async function getValue() {
return 42;
}
const result = getValue();
console.log(result); // Promise {<fulfilled>: 42}
result.then(val => console.log(val)); // 42
Promise {<fulfilled>: 42}
42
Q17: await with non-Promise values
async function test() {
const a = await 42; // Wraps in Promise.resolve(42)
const b = await "hello"; // Wraps in Promise.resolve("hello")
console.log(a, b);
}
test();
Q18: Chained async/await
async function step1() { return "Step 1"; }
async function step2(val) { return val + " → Step 2"; }
async function step3(val) { return val + " → Step 3"; }
async function run() {
const result = await step3(await step2(await step1()));
console.log(result);
}
run();
Common Traps 🪤
| Trap | Issue | Fix |
|---|
Missing await | Promise returned, not value | Always await async calls |
await in .forEach | Doesn't wait | Use for...of with await |
| Unhandled rejection | Silent crash in Node.js | Always .catch() or try/catch |
Promise.all short-circuits | One rejection kills all | Use Promise.allSettled instead |
| Not returning in chain | Returns undefined | Always return from .then |
The forEach Trap:
// ❌ Wrong — doesn't await
async function processAll(items) {
items.forEach(async (item) => {
await processItem(item); // IGNORED by forEach
});
}
// ✅ Correct — sequential
async function processAll(items) {
for (const item of items) {
await processItem(item);
}
}
// ✅ Correct — parallel
async function processAll(items) {
await Promise.all(items.map(item => processItem(item)));
}
Interview Quick Reference
Promise.resolve(val) → Already fulfilled
Promise.reject(err) → Already rejected
new Promise(fn) → fn runs synchronously
.then(onFulfilled) → Runs as microtask
.catch(onRejected) → Same as .then(null, onRejected)
.finally(fn) → Runs always, passes through value
async fn() { ... } → Returns a Promise
await expr → Pauses async fn, schedules resume as microtask
Practice Questions
- Implement
sleep(ms) that returns a Promise resolving after ms milliseconds. - Write
timeout(promise, ms) that rejects if promise takes longer than ms. - Build
promiseQueue(tasks, concurrency) — run N tasks at a time. - Implement a simple
Promise class from scratch (constructor, then, catch). - Refactor callback hell to async/await with proper error handling.