LearnFrenzy
Sign in Take a free test

JavaScript interview · Set 2 of 10

Asynchronous JavaScript: Event Loop and Promises

  • 5 follow-up questions
  • 4 key points
  • Code examples
Test yourself

One thread, many tasks

Understanding the event loop explains output-order puzzles, frozen pages and slow APIs. Combine it with solid promise patterns and you can answer most async questions confidently.

  • Event loopStack, microtasks and macrotasks.
  • PromisesStates, combinators and async/await.
  • PatternsDebounce, cancellation and parallel calls.

JavaScript runs on one thread, yet it handles thousands of network calls and clicks. The event loop is how — and it is the most common senior front-end question. Expect to predict the output order of a snippet and to write a debounce function.

1. Explain the event loop.

JavaScript runs one call stack. Async work (timers, network, events) is handled by the browser or Node, and their callbacks wait in queues. When the stack is empty, the event loop first runs all microtasks (promise callbacks, queueMicrotask), then takes one macrotask (a timer, an I/O callback, an event), then microtasks again, rendering between tasks in the browser.

console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// prints 1, 4, 3, 2

2. What is a promise and what states can it be in?

A promise represents a value that will be available later. It is pending, then settles once as either fulfilled with a value or rejected with a reason. then handles success, catch handles errors and finally runs either way.


3. Promise.all vs allSettled vs race vs any?

  • Promise.all — resolves with all results, rejects as soon as one fails.
  • Promise.allSettled — waits for all and reports each outcome; never rejects.
  • Promise.race — settles with the first to settle (success or failure), e.g. a timeout.
  • Promise.any — resolves with the first success; rejects only if all fail.
const [user, orders] = await Promise.all([api.user(id), api.orders(id)]);

const results = await Promise.allSettled(urls.map(u => fetch(u)));
const failed = results.filter(r => r.status === 'rejected').length;

4. How do you handle errors with async/await?

Wrap awaits in try/catch. Check response.ok with fetch, because a 404 or 500 does not reject the promise — only network failures do.

async function loadProfile(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    showToast('Could not load profile');
    throw err;
  }
}

5. Sequential vs parallel awaits — what is the common mistake?

Awaiting independent calls one after another makes the total time the sum of all calls. Start them together and await them with Promise.all so the total is the slowest call.

// slow: ~600 ms if each takes 200 ms
const a = await getA();
const b = await getB();
const c = await getC();

// fast: ~200 ms
const [a2, b2, c2] = await Promise.all([getA(), getB(), getC()]);

6. Write a debounce function.

Debounce delays a function until calls stop for a given time — ideal for search-as-you-type and window resize.

function debounce(fn, wait = 300) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), wait);
  };
}
searchBox.addEventListener('input', debounce(e => search(e.target.value), 300));

7. How do you cancel a fetch request?

Pass an AbortController's signal; calling abort() rejects the promise with an AbortError. AbortSignal.timeout(ms) gives a ready-made timeout.

const controller = new AbortController();
fetch('/api/report', { signal: controller.signal })
  .catch(err => { if (err.name !== 'AbortError') throw err; });
controller.abort();   // user left the page

await fetch('/api/slow', { signal: AbortSignal.timeout(5000) });

8. What blocks the event loop and how do you avoid it?

Long synchronous work — big loops, heavy JSON parsing, complex calculations — keeps the stack busy, so clicks and rendering freeze. Break work into chunks, move it to a Web Worker (or a worker thread in Node), or do it on the server.


Remember

Key points

  • Microtasks first

    Promise callbacks run before the next timer.

  • fetch does not reject on 404

    Check response.ok.

  • Parallelise independent awaits

    Use Promise.all.

  • Do not block the thread

    Chunk work or use workers.

Interview drill

Follow-up questions

What the interviewer asks after your first answer. Turn on practice mode, answer each one aloud, then reveal.

  1. 1 What is the difference between debounce and throttle? Basic

    Debounce waits until activity stops; throttle runs at most once per interval while activity continues (scroll, mousemove).

  2. 2 What happens if a promise rejection is not caught? Intermediate

    An unhandledrejection event fires; in Node it crashes the process by default. Always end chains with catch or use try/catch with await.

  3. 3 Is async/await faster than promises? Basic

    No — it is syntax on top of promises. It makes code easier to read and errors easier to handle.

  4. 4 How would you limit concurrency to 3 requests at a time? Advanced

    Keep a pool of workers that each take the next URL from a shared queue until it is empty.

    async function mapLimit(items, limit, worker) {
      const results = [];
      let next = 0;
      async function run() {
        while (next < items.length) {
          const i = next++;
          results[i] = await worker(items[i]);
        }
      }
      await Promise.all(Array.from({ length: limit }, run));
      return results;
    }
  5. 5 How does the Node.js event loop differ from the browser? Intermediate

    Node has phases (timers, poll for I/O, check for setImmediate, close callbacks) and process.nextTick, which runs before other microtasks. The principle — one thread, queues, microtasks first — is the same.

Preparing with friends?

Share this page with your study group or placement batch.

Search LearnFrenzy

Esc

Quick links