Skip to Content
Course content

231: Common JavaScript Interview Questions on Async Code

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I interviewed a candidate who had a sparkling portfolio and five years of experience. We got to the technical round, and I gave them a small snippet of code containing a setTimeout, a Promise.resolve(), and a standard console.log. I asked them to predict the order of the logs. They confidently told me the timer would go first because it was defined first in the code. When I explained that the Promise would actually jump the queue, they looked genuinely blindsided. They knew how to use async/await to fetch data, but they didn't understand the "plumbing" of the JavaScript engine. That's the difference between a coder and an engineer, and it's exactly what interviewers are hunting for when they grill you on async code.

The Microtask Queue vs. The Macrotask Queue

When an interviewer asks you "What is the Event Loop?", they aren't just looking for a textbook definition. They want to know if you understand why a Promise behaves differently than a setTimeout. In JS, we have the Call Stack, the Macrotask Queue (where setTimeout and setInterval live), and the Microtask Queue (where Promise callbacks and MutationObserver live).

Here is the crucial bit: the Event Loop will not move on to the next Macrotask until the Microtask Queue is completely empty. If a Promise resolves and adds a callback to the microtask queue, that callback will execute before any pending timers, even if those timers were set to 0 milliseconds. I've seen people lose a lot of points by forgetting that promises basically "cut the line."

console.log('A'); 
setTimeout(() => console.log('B'), 0); 
Promise.resolve().then(() => console.log('C')); 
console.log('D');

// Output: A, D, C, B
// Why? A and D are synchronous. C is a microtask. B is a macrotask.

The async/await Loop Trap

This is perhaps the most common "gotcha" in mid-level interviews. You'll likely be asked why an async function inside a .forEach() doesn't actually wait for the promises to resolve. It's because forEach is just a higher-order function that executes a callback; it doesn't check if that callback returns a promise, and it certainly doesn't await it.

If you need to execute async operations in sequence, you have to use a for...of loop or a standard for loop. If you use forEach, the loop will finish executing almost instantly, firing off all your async calls in parallel without waiting for any of them to finish. It's a subtle bug that can cause absolute chaos in a production database if you're accidentally hammering an API with 1,000 simultaneous requests instead of one at a time.

Parallel Execution with Promise.all

Once you've proven you can handle sequential code, the interviewer will usually pivot to performance. They'll ask: "Your code works, but it's slow. How do you fix it?" This is your cue to talk about Promise.all or Promise.allSettled.

If you have three API calls that don't depend on each other, awaiting them one by one is a waste of time. You're creating a waterfall where each request waits for the previous one to round-trip to the server. By wrapping them in Promise.all([p1, p2, p3]), you trigger all three requests simultaneously. Just remember the catch: Promise.all is "fail-fast." If any single promise rejects, the whole thing rejects immediately. If you need to know the result of every call regardless of failure, Promise.allSettled is your best friend. I always mention this distinction in interviews because it shows you've actually dealt with flaky APIs in the real world.




📋 Practical Task

Refactoring the Sequential API Fetcher

You have been handed a piece of legacy code that fetches user profiles and their associated posts. Currently, the code is written sequentially, meaning it waits for the user profile to load, then waits for the posts to load, and then waits for the settings to load. This is causing a noticeable lag in the UI.

Your Goal: Refactor the fetchUserData function. The userProfile must be fetched first (because the other two calls need the userId from it), but the userPosts and userSettings should be fetched in parallel to save time.


const mockApi = {
  getUserProfile: () => new Promise(res => setTimeout(() => res({ id: 1, name: 'Dev' }), 100)),
  getUserPosts: (id) => new Promise(res => setTimeout(() => res(['Post 1', 'Post 2']), 100)),
  getUserSettings: (id) => new Promise(res => setTimeout(() => res({ theme: 'dark' }), 100)),
};

async function fetchUserData() {
  console.time('FetchTime');
  
  // CURRENT SEQUENTIAL IMPLEMENTATION
  const profile = await mockApi.getUserProfile();
  const posts = await mockApi.getUserPosts(profile.id);
  const settings = await mockApi.getUserSettings(profile.id);
  
  console.timeEnd('FetchTime');
  return { profile, posts, settings };
}

fetchUserData().then(data => console.log('Data loaded:', data));

Modify the function so that getUserPosts and getUserSettings run concurrently. Your FetchTime should drop from roughly 300ms to roughly 200ms.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.