One of the most frequently asked JavaScript interview questions is:
What is a Promise and why do we need it?
Interviewers often ask:
- What problems do Promises solve?
- What are Promise states?
- How does Promise chaining work?
- How does Promise error handling work?
- What is the difference between
Promise.all()andPromise.allSettled()? - How do Promises interact with the Event Loop?
- How is async/await related to Promises?
Let's understand Promises from first principles.
1. Why Do We Need Promises?#
Before Promises, asynchronous JavaScript was commonly written using callbacks.
Example:
getUser(function (user) {
getPosts(user.id, function (posts) {
getComments(posts[0].id, function (comments) {
console.log(comments);
});
});
});
This creates:
Callback Hell
Problems:
- Deep nesting
- Difficult error handling
- Harder debugging
- Poor readability
- Difficult composition
Promises were introduced to make asynchronous code more manageable.
2. What is a Promise & Its States#
A Promise is an object that represents the eventual completion or failure of an asynchronous operation.
Think of a Promise as:
A Placeholder
For A Future Value
Example:
const promise = fetch("/users");
At this moment:
Data Is Not Available Yet
The Promise represents the future result.
Real-Life Analogy#
Imagine ordering food online.
Order Placed
↓
Food Being Prepared
↓
Delivered
A Promise works similarly:
Pending
↓
Fulfilled
or
Rejected
Promise States#
Every Promise exists in one of three states.
Pending
Operation Still Running
Fulfilled
Operation Completed Successfully
Rejected
Operation Failed
Visual Representation#
Pending
/ \
/ \
Fulfilled Rejected
A Promise can change state only once.
3. Creating a Promise#
Example:
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Success");
} else {
reject("Failed");
}
});
Resolve#
resolve(value);
Marks the Promise as:
Fulfilled
Reject#
reject(error);
Marks the Promise as:
Rejected
4. Consuming a Promise & Error Handling#
promise.then((result) => {
console.log(result);
});
Output:
Success
Handling Errors#
promise.catch((error) => {
console.error(error);
});
Output:
Failed
Complete Example#
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data Loaded");
}, 1000);
});
promise.then((data) => {
console.log(data);
});
Output after 1 second:
Data Loaded
5. Promise Chaining & Flow Visualization#
One of the biggest advantages of Promises.
Example:
fetchUser()
.then((user) => {
return fetchPosts(user.id);
})
.then((posts) => {
return fetchComments(posts[0].id);
})
.then((comments) => {
console.log(comments);
});
Instead of:
Nested Callbacks
we get:
Linear Flow
How Chaining Works#
Every:
.then()
returns a new Promise.
Example:
Promise.resolve(5)
.then((value) => {
return value * 2;
})
.then((value) => {
console.log(value);
});
Output:
10
Promise Chain Visualization#
Promise
↓
then()
↓
then()
↓
then()
Each step receives the previous result.
Error Handling in Chains#
Example:
fetchUser()
.then((user) => {
throw new Error("Failed");
})
.catch((error) => {
console.log(error.message);
});
Output:
Failed
Why This Is Powerful#
A single:
.catch()
can handle errors from the entire chain.
Example:
task1().then(task2).then(task3).catch(handleError);
Much cleaner than nested callbacks.
6. Execution Order & Event Loop Priority#
What is the output?
console.log("Start");
const promise = new Promise((resolve) => {
console.log("Inside");
resolve();
});
console.log("End");
Answer#
Output:
Start
Inside
End
Important:
Promise Constructor Executes Immediately
Many developers incorrectly assume it is asynchronous.
Event Loop Interview Question#
What is the output?
console.log("1");
Promise.resolve().then(() => {
console.log("2");
});
console.log("3");
Answer#
Output:
1
3
2
Why?
.then()
creates a:
Microtask
Microtasks execute after synchronous code completes.
Promise vs setTimeout#
What is the output?
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve().then(() => {
console.log("3");
});
console.log("4");
Answer#
Output:
1
4
3
2
Why?
Execution order:
Sync Code
↓
Microtasks
↓
Macrotasks
Promises use:
Microtask Queue
Timers use:
Macrotask Queue
7. Parallel Promises: Promise.all vs. allSettled vs. race vs. any#
Runs multiple Promises concurrently.
Example:
Promise.all([fetchUsers(), fetchPosts(), fetchComments()]);
Behavior:
Wait For All
Example#
Promise.all([Promise.resolve("A"), Promise.resolve("B")]).then((result) => {
console.log(result);
});
Output:
["A", "B"]
Promise.all Failure Behavior#
Promise.all([Promise.resolve("A"), Promise.reject("Error")]);
Output:
Rejected
One failure rejects the entire Promise.
Promise.allSettled()#
Waits for all Promises regardless of success or failure.
Example:
Promise.allSettled([Promise.resolve("A"), Promise.reject("Error")]);
Output:
[
{
status: "fulfilled",
value: "A",
},
{
status: "rejected",
reason: "Error",
},
];
Promise.race()#
Returns the first settled Promise.
Example:
Promise.race([fetchUsers(), timeout()]);
Useful for request timeouts.
Promise.any()#
Returns the first fulfilled Promise.
Example:
Promise.any([api1(), api2(), api3()]);
Success from any source wins.
Promise.finally()#
Runs regardless of success or failure.
Example:
fetchData()
.then(handleSuccess)
.catch(handleError)
.finally(() => {
hideLoader();
});
Useful for cleanup.
8. Promises vs. Async/Await#
Promise:
fetchUser()
.then((user) => {
return fetchPosts(user.id);
})
.then(console.log);
Async/Await#
const user = await fetchUser();
const posts = await fetchPosts(user.id);
console.log(posts);
Cleaner syntax.
Important Interview Point#
Async/Await:
Does Not Replace Promises
Instead:
Built On Top Of Promises
9. Common Interview Q&A#
Can a Promise Change State Twice?#
No.
Example:
resolve("A");
resolve("B");
reject("Error");
Only the first state change matters.
Does Creating a Promise Make It Asynchronous?#
No.
The Promise constructor executes immediately.
What Queue Do Promise Callbacks Use?#
Microtask Queue
What Happens If a Then Callback Returns a Promise?#
The next .then() waits for that Promise to settle.
What Happens If a Then Callback Throws?#
The Promise becomes rejected.
The error propagates to the nearest .catch().
Senior-Level Interview Answer#
A Promise is an object that represents the eventual completion or failure of an asynchronous operation. It can be in one of three states: pending, fulfilled, or rejected. Promises solve callback hell by providing a structured way to compose asynchronous operations through chaining. Methods such as
then,catch, andfinallyallow success handling, error handling, and cleanup. Promise callbacks are scheduled in the Microtask Queue, which gives them higher priority than timer callbacks in the Event Loop. Modern async/await syntax is built on top of Promises and provides a cleaner way to write asynchronous code.
Common Interview Mistakes#
❌ "Promises are asynchronous."
Correct:
Promise callbacks are asynchronous.
The Promise constructor executes immediately.
❌ "Async/Await replaces Promises."
Correct:
Async/Await is built on top of Promises.
❌ "Promise.all waits even if one Promise fails."
Correct:
Promise.all rejects immediately
when any Promise rejects.
❌ "Promises use the Callback Queue."
Correct:
Promises use the Microtask Queue.
Promise API Summary#
| API | Behavior |
|---|---|
| Promise.resolve() | Creates fulfilled Promise |
| Promise.reject() | Creates rejected Promise |
| Promise.all() | Waits for all Promises |
| Promise.allSettled() | Waits for all results |
| Promise.race() | First settled Promise wins |
| Promise.any() | First fulfilled Promise wins |
| finally() | Runs regardless of outcome |
Key Takeaways#
- Promise States: A Promise represents an async operation that exists in one of three states: pending, fulfilled, or rejected.
- Chaining: Methods like
thenandcatchreturn new Promises, preventing nesting and resolving callback hell. - Event Loop Priority: Promise callbacks are processed inside the Microtask Queue, prioritizing them over standard timers.
- Parallel APIs: Use
Promise.allfor parallel operations, andPromise.allSettledto fetch resources without cascading failures.