← Back to list

When await can block the event loop?

A real case where a small refactor turned async code into an event loop problem.

Dmitrii Kulikov · 2026-01-24 11:40 · 2 claps · 2.6 min read
#javascript #event-loop #async-await-javascript #software-engineering #performance
Open on Medium ↗
Wiki topics: 🌐 · Web Development

When await can block the event loop?

Most JS/TS developers know about the event loop. Many know about event loop lag. Some also heard about microtasks.

We also know one simple rule: blocking the event loop is bad.

Symptoms can be different:

  • a web page become unresponsive
  • HTTP service stop handling requests
  • even background workers are restarted by supervisor due to health checks fail

To avoid this, we usually say: don’t do extensive synchronous work, use async code with callbacks or async/await.

This sounds simple, but in real projects, surprises still happen.

This is one case that I had to fix some time ago.

Initial state

We had a worker that processed some array of items as separate job.

For each item it did three things:

  • fetched data
  • processed it and calculated a hash (synchronously)
  • sent the result somewhere else

Simplified code looked like this:

const results = [];

for (const item of items) {
  const rawData = await fetchData(item);
  const processedItem = processItem(rawData);
  results.push(processedItem);
}

await sendResults(results);

Nothing special here:

  • data fetching is async
  • hashing is synchronous, but quite fast
  • the job takes time, but acceptable and can handle plenty of items
  • event loop lag is very small

Let’s speed it up

At some point we noticed that number of items was growing. The job started to take more and more time.

It turned out that in most cases we could use cache data and avoid HTTP requests, so we added a simple cache

async function fetchData(item) {
  if (cache.has(item.id)) {
    return cache.get(item.id);
  }

  return realHttpCall(item);
}

The interface stayed the same, the logic looked the same, all tests passed and processing time improved a lot.

At first glance — a perfect improvement.

First issues

After a few months the worker started to restart on some jobs. Logs showed that the worker did not respond to the liveness probe. This usually means either a real problem with the probe or event loop lag.

Metrics didn’t clearly show the problem. This also makes sense: when the event loop is blocked, metrics are not sent and Kubernetes just kills the pod.

The bug was not obvious.

When data comes from cache, function return immediately. In practice, this is almost the same as Promise.resolve(value).

This detail is very important, as await always continues execution using the microtask queue. And we need to remember, that all microtasks must be fully executed before the event loop moves to the next macrotask.

The real problem

Because of this improvement, the loop behavior changed. Instead of real IO pauses, we got a long chain of microtasks.

Keep in mind, that we also were calculating hash synchronously. In reality, the loop became to be something like this

const results = [];

for (const item of items) {
  const rawData = await Promise.resolve(cachedItem); // immediate resolving
  const processedItem = processItem(rawData); // hashing
  results.push(processedItem);
}

await sendResults(results);

Each iteration scheduled the next microtask. The event loop did not get a chance to process timers or other requests.

Hashing is fine for 10 items, but for 100,000 items, it caused a long delay.

This situation is called event loop starvation with microtasks.

Even simple sleep with 0 timeout can fix the issue

const sleep = ms => new Promise(r => setTimeout(r, ms));

const results = [];

for (const item of items) {
  const rawData = await Promise.resolve(cachedItem);
  const processedItem = processItem(rawData);
  results.push(processedItem);
  await sleep(0);
}

await sendResults(results);

Takeaway

This might be easy to notice in small examples, but in real projects the code is much bigger and spread across many layers.

Today, code is written and refactored very fast, often with the help of AI. In such cases, event loop behavior is rarely considered.

Even a small and unrelated optimization can completely change runtime behavior. After facing such a case once, you become much better at debugging and preventing similar issues.


메타데이터
post_id
54ddff70d9e8
slug
when-await-can-block-the-event-loop-54ddff70d9e8
url
https://medium.com/@kulikovd/when-await-can-block-the-event-loop-54ddff70d9e8
canonical_url
https://medium.com/@kulikovd/when-await-can-block-the-event-loop-54ddff70d9e8
author_url
https://medium.com/@kulikovd
status
ok
fetched_at
2026-06-22 12:55:45