Node.js Async Programming Interview Questions Promises, Async/Await, and Callbacks
Master Node.js asynchronous programming interview questions callbacks, Promises, async/await, error handling, and concurrency control.
Node.js Async Programming Interview Questions
Async programming is at the core of Node.js. Here are the most common interview questions.
Q1: What Is a Callback? What Is Callback Hell?
Answer: A callback is a function passed as an argument to another function, executed after an async operation completes.
// Callback fs.readFile("file.txt", (err, data) => { if (err) throw err; console.log(data); });
Callback hell (pyramid of doom):
fs.readFile("a.txt", (err, a) => { fs.readFile("b.txt", (err, b) => { fs.readFile("c.txt", (err, c) => { // Deeply nested... }); }); });
Solution: Use Promises or async/await.
Q2: What Is a Promise?
Answer: A Promise represents a value that will be available in the future. It has 3 states: pending, fulfilled, rejected.
const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Success!"); // or reject("Error!") }, 1000); }); myPromise .then(result => console.log(result)) .catch(error => console.error(error));
Q3: What Is async/await?
Answer: async/await is syntactic sugar over Promises, making async code look synchronous.
async function readFile() { try { const data = await fs.promises.readFile("file.txt", "utf-8"); console.log(data); } catch (err) { console.error("Error:", err); } }
Q4: Promise.all vs Promise.allSettled vs Promise.race
Answer:
// Promise.all All must succeed, or all fail const [users, posts] = await Promise.all([ fetchUsers(), fetchPosts() ]); // Promise.allSettled All complete, regardless of success/failure const results = await Promise.allSettled([ fetchUsers(), fetchPosts() ]); // results: [{ status: "fulfilled", value: ... }, { status: "rejected", reason: ... }] // Promise.race First to complete (success or failure) const fastest = await Promise.race([ fetchFromAPI1(), fetchFromAPI2() ]);
Q5: How Do You Handle Errors in async/await?
Answer: Use try-catch:
async function getUser(id) { try { const user = await User.findById(id); if (!user) throw new Error("User not found"); return user; } catch (err) { console.error("Failed:", err.message); throw err; // Re-throw for the caller } }
Q6: How Do You Run Async Operations in Parallel?
Answer:
// Sequential (slow 6 seconds total) const user = await fetchUser(); // 2s const posts = await fetchPosts(); // 2s const comments = await fetchComments(); // 2s // Parallel (fast 2 seconds total) const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]);
Q7: How Do You Limit Concurrency?
Answer:
// Process 3 at a time const batchSize = 3; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); await Promise.all(batch.map(item => processItem(item))); }
The Takeaway
Async programming questions cover: callbacks and callback hell (deeply nested callbacks solution: Promises/async-await), Promises (pending/fulfilled/rejected, then/catch/finally), async/await (syntactic sugar, try-catch), Promise.all vs allSettled vs race, parallel vs sequential execution (Promise.all for parallel), and concurrency limiting (batch processing). Always use async/await for clean, readable async code.
Callback hell is deeply nested callbacks (pyramid of doom) when multiple async operations depend on each other. Fix it by using Promises (.then chains) or async/await (makes async code look synchronous with try-catch). Extract logic into named functions for readability.
Promise.all: all must succeed, or all fail (fast fail). allSettled: all complete regardless of success/failure (returns status and value/reason for each). race: first to complete wins (success or failure). Use all for parallel independent operations, allSettled when you want all results, race for timeouts.
Use try-catch blocks around await calls. Catch the error, log it, and either re-throw it (for the caller to handle) or return a default value. For unhandled rejections, add process.on('unhandledRejection', handler) as a safety net.
Use Promise.all: const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]). All three start immediately and run in parallel. Total time is the longest operation, not the sum. Compare to sequential (await each one separately) which takes the sum of all times.
Process items in batches: slice the array into chunks of N, use Promise.all for each batch, and await each batch before processing the next. Or use a library like p-limit or Bluebird's Promise.map with concurrency option.
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

