JavaScript Promises
JavaScript Promises Interview with follow-up questions
1. What is a Promise in JavaScript?
A Promise is an object representing the eventual result of an asynchronous operation — a placeholder for a value that isn't available yet. It can be in one of three states:
- pending — the initial state, not yet settled
- fulfilled — completed successfully, with a value
- rejected — failed, with a reason (usually an
Error)
Once it settles (fulfilled or rejected) it's immutable — it can't change state or value again. You consume it with .then() (success), .catch() (failure), and .finally() (cleanup either way).
const promise = fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
What interviewers want you to add:
- Promises solve callback hell by making async flows chainable and giving a single, propagating error path.
- Each
.then()returns a new Promise, which is what makes chaining work (return values flow to the next handler). - Their handlers run as microtasks, which resolve before
setTimeoutmacrotasks — a classic ordering gotcha. async/awaitis syntactic sugar over this same machinery, and combinators likePromise.all/allSettled/race/anycoordinate multiple Promises.
Follow-up 1
What are the states of a Promise?
A Promise can be in one of three states:
- Pending: The initial state of a Promise. It is neither fulfilled nor rejected.
- Fulfilled: The state of a Promise when it is successfully resolved with a value.
- Rejected: The state of a Promise when it encounters an error or fails to fulfill its intended operation.
Follow-up 2
How do you create a Promise?
You can create a Promise using the Promise constructor. The Promise constructor takes a single argument, a callback function, which is called with two arguments: resolve and reject. Inside the callback function, you can perform asynchronous operations and then call resolve when the operation is successful or reject when it encounters an error.
Here's an example of creating a Promise that resolves after a delay of 1 second:
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise resolved!');
}, 1000);
});
Follow-up 3
What is the difference between synchronous and asynchronous programming in JavaScript?
In synchronous programming, code is executed sequentially from top to bottom, and each line of code waits for the previous line to finish executing before it can run. This means that if there is a long-running operation, it will block the execution of subsequent code until it completes.
In asynchronous programming, code does not wait for long-running operations to complete. Instead, it continues to execute, and the result of the long-running operation is handled later when it becomes available. Asynchronous programming is commonly used for tasks such as making API calls, reading files, or handling user input.
JavaScript uses Promises, callbacks, and async/await to handle asynchronous operations.
Follow-up 4
Can you provide an example of a Promise?
Sure! Here's an example of a Promise that fetches data from an API:
const fetchData = () => {
return new Promise((resolve, reject) => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => resolve(data))
.catch(error => reject(error));
});
};
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
2. How do you handle errors in Promises?
Errors in Promises are handled with .catch() (or the second argument to .then()). A rejection — whether from an explicit reject, a thrown error inside a handler, or a failed async op — propagates down the chain until a .catch() catches it.
fetch('/api/data')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => process(data))
.catch(err => console.error('Failed:', err)) // catches any error above
.finally(() => hideSpinner()); // runs regardless
Points interviewers expect:
- One
.catch()at the end handles errors from every preceding step — you don't need one per.then(). - A
.catch()can recover: if it returns a value (instead of rethrowing), the chain continues fulfilled. - With
async/await, the equivalent istry/catch, since a rejected awaited Promise throws. - Always handle rejections. An unhandled rejection triggers an
unhandledRejectionevent (and crashes Node by default in current versions). .then(onFulfilled, onRejected)'s second arg won't catch an error thrown byonFulfilledin the same call — a trailing.catch()will, which is why it's preferred.
Follow-up 1
What is the 'catch' method in Promises?
The 'catch' method in Promises is used to handle errors that occur during the promise chain. It takes a callback function as an argument, which will be called when an error occurs. The callback function receives the error object as its argument. The 'catch' method returns a new promise that is resolved with the value returned by the callback function. This allows you to handle the error and continue the promise chain.
Follow-up 2
What is the 'finally' method in Promises?
The 'finally' method in Promises is used to specify a callback function that will be called regardless of whether the promise is resolved or rejected. This method is useful for performing cleanup operations, such as closing resources or releasing memory, that need to be done regardless of the outcome of the promise. The 'finally' method returns a new promise that is resolved with the same value or error as the original promise.
Follow-up 3
Can you provide an example of error handling in Promises?
Sure! Here's an example of error handling in Promises:
function fetchData() {
return new Promise((resolve, reject) => {
// Simulating an asynchronous operation
setTimeout(() => {
const error = new Error('Failed to fetch data');
reject(error);
}, 1000);
});
}
fetchData()
.then(data => {
// Handle successful data retrieval
console.log(data);
})
.catch(error => {
// Handle error
console.error(error);
});
3. What is Promise chaining?
Promise chaining is linking asynchronous steps in sequence by returning a value (or another Promise) from each .then(). The key mechanic: every .then() returns a new Promise, and whatever you return from its callback becomes the fulfillment value of that new Promise — so it flows into the next .then().
fetch('/api/user')
.then(res => res.json()) // returns a Promise → awaited
.then(user => fetch(`/api/orders/${user.id}`))
.then(res => res.json())
.then(orders => console.log(orders))
.catch(err => console.error(err)); // one catch for the whole chain
What interviewers want highlighted:
- Returning a Promise from a
.then()makes the chain wait for it to settle before continuing — that's how you sequence dependent async calls flatly instead of nesting. - Returning a plain value passes it straight to the next handler.
- Forgetting to
returnis the classic bug: the next.then()receivesundefinedand the chain doesn't wait. - A single trailing
.catch()handles errors from any step, since rejections propagate down. async/awaitexpresses the same sequence more linearly, but chaining still shines for short, fluent transformations.
Follow-up 1
How does Promise chaining work?
In Promise chaining, each promise has a then() method that takes two optional arguments: onFulfilled and onRejected. When a promise is fulfilled, the onFulfilled callback is executed, and its return value becomes the fulfillment value of the chained promise. If an error occurs, the onRejected callback is executed, and the error is propagated down the chain. This allows you to perform a series of asynchronous operations in a sequential manner.
Follow-up 2
Can you provide an example of Promise chaining?
Sure! Here's an example of Promise chaining in JavaScript:
function getUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const user = {
id: id,
name: 'John Doe'
};
resolve(user);
}, 1000);
});
}
function getUserPosts(user) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const posts = ['Post 1', 'Post 2', 'Post 3'];
resolve({
user: user,
posts: posts
});
}, 1000);
});
}
getUser(1)
.then(user => getUserPosts(user))
.then(data => console.log(data));
Follow-up 3
What is the difference between Promise chaining and callback hell?
Promise chaining is a more elegant and readable way of handling asynchronous operations compared to callback hell. In callback hell, callbacks are nested within each other, leading to deeply nested and hard-to-read code. On the other hand, Promise chaining allows you to write asynchronous code in a sequential manner, making it easier to understand and maintain. Additionally, Promise chaining provides built-in error handling through the catch() method, whereas in callback hell, error handling can become complex and error-prone.
4. What are Promise.all and Promise.race?
Both coordinate multiple Promises, but they settle on different conditions. In 2026 interviewers usually expect you to know all four combinators, so here's the full picture:
Promise.all([...])— waits for all to fulfill, resolving to an array of values in order. Fail-fast: if any one rejects, the whole thing rejects immediately with that reason.
const [user, posts] = await Promise.all([getUser(), getPosts()]);
Promise.race([...])— settles as soon as the first Promise settles, whether it fulfills or rejects. Useful for timeouts.Promise.allSettled([...])— waits for every Promise and never short-circuits; resolves to an array of{ status: 'fulfilled', value }/{ status: 'rejected', reason }. Use it when you want all results regardless of individual failures.Promise.any([...])— resolves with the first fulfilled value, ignoring rejections; only rejects (with anAggregateError) if all reject.
const fastest = await Promise.any([mirror1(), mirror2(), mirror3()]);
Quick contrast: all = all-or-nothing, allSettled = collect everything, race = first to settle (success or failure), any = first success. All accept any iterable of values/Promises, and non-Promise values are treated as already-fulfilled.
Follow-up 1
How do Promise.all and Promise.race work?
When Promise.all is called with an array of promises, it waits for all the promises to be fulfilled or rejected. If all promises are fulfilled, Promise.all returns a new promise that is fulfilled with an array of the fulfilled values of the input promises. If any of the promises are rejected, the returned promise is immediately rejected with the reason of the first rejected promise.
When Promise.race is called with an array of promises, it returns a new promise that is settled with the value or reason of the first promise in the input array to be settled. This means that if the first promise to be settled is fulfilled, the returned promise is fulfilled with the same value. If the first promise to be settled is rejected, the returned promise is rejected with the same reason.
Follow-up 2
Can you provide an example of Promise.all and Promise.race?
Sure! Here's an example of Promise.all:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 1'), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 2'), 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 3'), 3000);
});
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // Output: ['Promise 1', 'Promise 2', 'Promise 3']
})
.catch(error => {
console.error(error);
});
And here's an example of Promise.race:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 1'), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 2'), 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 3'), 3000);
});
Promise.race([promise1, promise2, promise3])
.then(value => {
console.log(value); // Output: 'Promise 1'
})
.catch(error => {
console.error(error);
});
Follow-up 3
What happens if one of the Promises in Promise.all fails?
If one of the promises in Promise.all fails (i.e., is rejected), the returned promise is immediately rejected with the reason of the first rejected promise. The other promises in the input array that have not yet settled will continue to run, but their results will be ignored.
5. What is async/await in JavaScript and how does it relate to Promises?
Async/await is syntactic sugar over Promises — it doesn't replace them, it gives a cleaner way to consume them. An async function always returns a Promise, and await unwraps a Promise's fulfilled value (or throws its rejection) while pausing only that function.
The relationship, made concrete — these two are equivalent:
// Promise style
function load() {
return fetch('/api/data')
.then(res => res.json())
.then(data => data.items)
.catch(err => { throw err; });
}
// async/await style
async function load() {
try {
const res = await fetch('/api/data');
const data = await res.json();
return data.items;
} catch (err) {
throw err;
}
}
What interviewers want you to connect:
await xis roughlyx.then(...);try/catchis the.catch();returnis the resolved value.- Because it's Promises underneath, you mix them freely —
await Promise.all([...])to run things in parallel, and an async function's return value is itself awaitable. - They share the microtask queue, so ordering relative to
setTimeoutis the same gotcha as with raw Promises. - Use
awaitfor readable sequential logic; reach for Promise combinators when coordinating many operations.
Follow-up 1
How does async/await work?
Async/await works by using two keywords: async and await.
When you define a function with the async keyword, it becomes an asynchronous function. Inside an asynchronous function, you can use the await keyword to pause the execution of the function until a promise is resolved or rejected.
Here's how it works:
- When an asynchronous function encounters an
awaitexpression, it pauses the execution of the function and waits for the promise to settle (either resolve or reject). - While waiting for the promise to settle, the function is suspended, and the control is returned to the caller. This allows other code to run in the meantime.
- Once the promise settles, the function resumes its execution from where it left off, and the value of the
awaitexpression is the resolved value of the promise.
It's important to note that you can only use await inside an asynchronous function.
Follow-up 2
Can you provide an example of async/await?
Sure! Here's an example that demonstrates the use of async/await:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchData() {
console.log('Fetching data...');
await delay(2000);
console.log('Data fetched!');
}
fetchData();
console.log('After calling fetchData');
In this example, the fetchData function is defined as an asynchronous function using the async keyword. Inside the function, we use the await keyword to pause the execution of the function until the delay promise is resolved.
When we run this code, it will output:
Fetching data...
After calling fetchData
Data fetched!
As you can see, the code after the await expression (console.log('After calling fetchData');) is executed before the promise is resolved, demonstrating the non-blocking nature of async/await.
Follow-up 3
What are the advantages of using async/await over Promises?
There are several advantages of using async/await over Promises:
Readability: Async/await allows you to write asynchronous code that looks like synchronous code, making it easier to understand and maintain. It eliminates the need for chaining
.then()methods and handling callbacks.Error handling: Async/await simplifies error handling by allowing you to use try/catch blocks to catch and handle errors. This makes the code more readable and reduces the chances of introducing bugs.
Sequential execution: Async/await allows you to write asynchronous code that executes sequentially, making it easier to reason about the flow of the code. You can use control structures like loops and conditionals without the need for complex nesting or chaining of Promises.
Debugging: Async/await provides better debugging experience compared to Promises. You can set breakpoints and step through the code as if it were synchronous, making it easier to identify and fix issues.
Overall, async/await provides a more intuitive and concise way to write asynchronous code in JavaScript.
Live mock interview
Mock interview: JavaScript Promises
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.