JUNE 2026 Coding interviews test recall. kodr.run tests engineering judgment
JavaScriptAdvanced20 minGeneral

JavaScript Async & Promises Interview Questions (With Code Examples)

Why interviewers ask this

Async questions separate candidates more sharply than almost any other JavaScript topic, because the code often looks correct and still has a real problem: it's slower than it needs to be, it swallows an error silently, or it has a subtle race condition that only shows up under specific timing. This guide is built around those problems specifically.

The first four questions get full treatment: working code, the common mistake, and the explanation. The remaining six are tighter: one working example and the insight it tests.

How interviewers read an async/Promises answer

LayerWhat a weaker answer doesWhat a stronger answer does
Concurrency awarenessAwaits independent operations one at a time inside a loopRecognizes independent work and runs it in parallel with Promise.all
Error handlingWraps code in try/catch without considering what happens to errors in unawaited promisesKnows which errors try/catch actually catches, and handles the rest explicitly
Method selectionReaches for Promise.all by default, regardless of whether partial failure should be toleratedChooses between all, allSettled, race, and any based on what failure behavior the situation needs
CommunicationPresents working async code without explaining the execution orderNarrates what runs synchronously, what's queued as a microtask, and what waits on I/O

1How do you run multiple independent async operations in parallel instead of sequentially?

JavaScript · sequential-await-in-loop.js
async function fetchAllSequential(ids) {
    const results = [];
    for (const id of ids) {
        const data = await fetchUser(id); // waits for each one before starting the next
        results.push(data);
    }
    return results;
}

This works, but if each fetchUser call takes 200ms and there are five ids, the total time is roughly 1000ms, since each call waits for the previous one to finish before starting. The operations don't depend on each other, so there's no reason to serialize them:


JavaScript · parallel-promise-all.js
async function fetchAllParallel(ids) {
    const promises = ids.map(id => fetchUser(id)); // all start immediately
    return Promise.all(promises); // wait for all of them together
}

Here, all five requests start at roughly the same time, so the total time is closer to 200ms, the duration of the slowest single request, not the sum of all of them.

ApproachTotal time for 5 independent 200ms calls
Sequential await in a loop~1000ms
Promise.all with map~200ms

Evaluates: whether the candidate recognizes independent async work and parallelizes it. This is one of the most common real-world performance bugs, and the sequential version is dangerous specifically because it produces correct output, just slowly, so it's easy to miss without testing at realistic scale.

2How does error handling differ between try/catch with async/await and .catch() with Promises?

JavaScript · try-catch-async-await.js
async function getUser(id) {
    try {
        const response = await fetch(`/api/users/${id}`);
        if (!response.ok) throw new Error("User not found");
        return await response.json();
    } catch (err) {
        console.error("Failed to fetch user:", err.message);
        return null;
    }
}

This correctly catches a rejected fetch promise or a thrown error inside the try block. The common mistake is assuming try/catch also catches errors from promises that aren't awaited:


JavaScript · unawaited-promise-error-gap.js
async function getUserBroken(id) {
    try {
        fetch(`/api/users/${id}`).then(res => res.json()); // missing await
    } catch (err) {
        console.error("This will never run for a rejected fetch");
    }
}

Since the fetch(...).then(...) chain isn't awaited, its rejection happens outside the synchronous flow the try/catch is actually watching, and the error becomes an unhandled promise rejection instead of being caught.

Evaluates: understanding that try/catch only catches errors from code it's actually waiting on. An unawaited promise's rejection escapes it entirely, which is a common source of silently swallowed errors in real applications.

3What's the difference between Promise.race and Promise.any?

JavaScript · promise-race-vs-any.js
const fast = new Promise((resolve) => setTimeout(() => resolve("fast done"), 100));
const slow = new Promise((_, reject) => setTimeout(() => reject("slow failed"), 50));


Promise.race([fast, slow])
    .then(console.log)
    .catch(err => console.log("race rejected:", err));
// race rejected: slow failed (the FIRST to settle, whether it resolves or rejects)


Promise.any([fast, slow])
    .then(console.log)
    .catch(err => console.log("any rejected"));
// fast done (the first to RESOLVE; ignores rejections unless everything rejects)

Promise.race settles as soon as any promise settles, resolved or rejected, whichever happens first. Promise.any specifically waits for the first successful result and only rejects if every promise rejects.

MethodSettles whenUse when
Promise.raceThe first promise settles, regardless of outcomeYou want whichever finishes first, success or failure (e.g., a timeout pattern)
Promise.anyThe first promise resolves successfullyYou want the first success and don't care about earlier failures (e.g., trying multiple servers)

Evaluates: whether the candidate can pick correctly between the two based on whether an early failure should short-circuit the result. Confusing them is a common mistake precisely because their names sound similar and both settle "early."

4How do you convert a callback-based function into one that returns a Promise?

JavaScript · promisify-callback-function.js
function readFileCallback(path, callback) {
    // imagine this calls back with (error, data)
}


function readFilePromise(path) {
    return new Promise((resolve, reject) => {
        readFileCallback(path, (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
}


// Now usable with async/await instead of nested callbacks
async function loadConfig() {
    const data = await readFilePromise("./config.json");
    return JSON.parse(data);
}

The naive alternative keeps using the callback directly, which works for a single call but gets unmanageable once multiple async steps depend on each other:


JavaScript · callback-hell-nested.js
// "Callback hell": each step nests inside the previous one's callback
readFileCallback("./config.json", (err1, data1) => {
    if (err1) return console.error(err1);
    readFileCallback("./secrets.json", (err2, data2) => {
        if (err2) return console.error(err2);
        // and so on, nesting deeper with each step
    });
});

Evaluates: knowing how to wrap a callback-style API in a new Promise((resolve, reject) => {...}) constructor, which is the standard technique ("promisifying") for using older callback-based APIs with modern async/await code.

5What happens if you forget to use await before a function call that returns a Promise?

JavaScript · missing-await-returns-promise.js
async function getData() {
    return "data";
}


function useData() {
    const result = getData(); // missing await
    console.log(result); // Promise { 'data' }, not "data"
}

Evaluates: knowing that without await, you get the Promise object itself, still pending or already settled, rather than its resolved value. This is one of the most common beginner mistakes when first learning async/await.

6How do you run async operations with a concurrency limit, instead of all at once?

JavaScript · async-concurrency-limit-batching.js
async function processWithLimit(items, limit, fn) {
    const results = [];
    for (let i = 0; i < items.length; i += limit) {
        const batch = items.slice(i, i + limit);
        const batchResults = await Promise.all(batch.map(fn));
        results.push(...batchResults);
    }
    return results;
}

Evaluates: recognizing that unlimited Promise.all isn't always the right answer either, running 10,000 requests at once can overwhelm a server or hit a rate limit, so batching in fixed-size groups is a middle ground between fully sequential and fully parallel.

7How do you iterate over an array of async operations using for await...of?

JavaScript · for-await-of-async-generator.js
async function* fetchUsersLazily(ids) {
    for (const id of ids) {
        yield await fetchUser(id);
    }
}


async function processUsers(ids) {
    for await (const user of fetchUsersLazily(ids)) {
        console.log(user);
    }
}

Evaluates: familiarity with async generators and for await...of, which processes results one at a time as they become available, rather than waiting for every promise to resolve before processing any of them, as Promise.all would.

8What's the difference between the microtask queue and the macrotask queue?

JavaScript · microtask-vs-macrotask-queue.js
console.log("1");
setTimeout(() => console.log("2 (macrotask)"), 0);
Promise.resolve().then(() => console.log("3 (microtask)"));
queueMicrotask(() => console.log("4 (microtask)"));
console.log("5");
// Output: 1, 5, 3, 4, 2

Evaluates: understanding that all queued microtasks (Promise callbacks, queueMicrotask) run before the next macrotask (setTimeout, setInterval), even if the macrotask was scheduled first. This builds directly on the event loop question from the anchor guide, going one level deeper into why the ordering is what it is.

9How do you implement retry logic for a flaky async operation?

JavaScript · async-retry-logic.js
async function fetchWithRetry(fn, retries = 3, delay = 500) {
    for (let attempt = 1; attempt <= retries; attempt++) {
        try {
            return await fn();
        } catch (err) {
            if (attempt === retries) throw err;
            await new Promise(resolve => setTimeout(resolve, delay));
        }
    }
}

Evaluates: whether the candidate can combine try/catch, a loop, and a delay into a working retry pattern, a common real-world requirement for network calls that occasionally fail transiently.

10What's a race condition in async JavaScript, and how do you avoid one?

JavaScript · race-condition-stale-query-guard.js
// Bug: if the user changes the search term quickly, an older, slower
// request can resolve after a newer one, overwriting fresher results
let latestQuery = "";


async function search(query) {
    latestQuery = query;
    const results = await fetchResults(query);
    if (query === latestQuery) { // only apply results if still the latest query
        renderResults(results);
    }
}

Evaluates: recognizing that async operations don't necessarily resolve in the order they were started, so code that assumes the most recently started request is also the most recently resolved one can display stale data. The guard clause here (checking query === latestQuery before rendering) is the standard fix for this specific pattern.

How should you prepare for an async and Promises interview?

  1. Default to parallel, then narrow to sequential only when there's a real dependency. The sequential-await-in-a-loop mistake is the single most common performance bug in this category, and it's avoidable just by asking "does step two actually need step one's result?"
  2. Know exactly what try/catch does and doesn't catch. Unawaited promises are the most common gap, and it's a distinction interviewers specifically probe for.
  3. Practise explaining execution order out loud. For any snippet mixing synchronous code, setTimeout, and Promises, be able to narrate what runs first, second, and third, and why, before running it.

Frequently asked questions

Who is this guide for, and what JavaScript experience does it assume? This guide is for candidates preparing for advanced interviews who already understand basic Promises and event loop ordering, covered in our anchor guide. It goes further into patterns used in real production async code.

Is async/await faster than using .then() chains directly? No, they compile to the same underlying Promise mechanics and perform identically. async/await is generally easier to read, especially for sequential logic, but performance is not a reason to prefer one over the other.

What's the most common async mistake in real production code, not just interviews? Running independent async operations sequentially with await in a loop instead of in parallel with Promise.all. It produces correct but needlessly slow code, which makes it easy to miss in code review since nothing looks obviously wrong.

Why does an unhandled promise rejection matter if the app doesn't crash immediately? In Node.js, unhandled rejections are increasingly treated as fatal errors in newer versions, and even where they aren't, silently swallowed errors make debugging significantly harder later, since the failure and its symptom can be far apart in the code.

How is this different from the general JavaScript interview guide? The Top JavaScript Coding Interview Questions guide introduces Promises and basic event loop ordering. This guide goes further into concurrency patterns, error handling nuances, and race conditions that come up at a more advanced level.



Related tutorials: Top JavaScript Coding Interview Questions · JavaScript Closures & Scope Interview Questions · JavaScript Array Methods Interview Questions