Picture this. Your app has been running fine for months. No crashes, no alarms, nothing that would make you lose sleep. Then one afternoon, during your busiest hour, everything just stalls. Requests pile up. Users hit refresh, then refresh again. Your dashboard shows CPU usage sitting comfortably at 40 percent, which makes no sense because if the server has capacity left, why does it feel like the whole app is holding its breath?
The answer is almost never "the server is overloaded." The answer, more often than any team wants to admit, is that somewhere in the code a single operation is blocking the event loop, and everything else is waiting in line behind it.
This is one of those bugs that doesn't announce itself. It doesn't throw an exception. It doesn't show up in error logs. It just quietly taxes every single request that comes in after it, and by the time anyone notices the pattern, it has already cost real money, real users, and in some cases, real trust.
What "Blocking the Event Loop" Actually Means
If you write JavaScript on Node.js, you already know the event loop is the engine that lets a single thread handle thousands of concurrent connections. It works by constantly checking a queue: is there a callback ready to run? Run it. Is there another? Run that too. The trick is that each task is supposed to be short. Hand off the heavy lifting like file reads, database calls, and network requests to the system, then come back when the result is ready.
The problem starts when a piece of code refuses to hand anything off. A synchronous loop that processes 50,000 records in memory. A regular expression that backtracks catastrophically on a strange input. A JSON.stringify call on a payload nobody expected to be that large. None of these are asynchronous by nature, so the event loop cannot step aside and serve another request while they run. It has to finish that one operation first, no matter how many other users are waiting.
Python developers hit a version of this too, even though the language wasn't originally built around a single event loop the way Node was. Once you adopt asyncio for a FastAPI service or an async worker, the same rule applies. Call a blocking library function, a synchronous ORM query, or a CPU heavy computation inside an async route, and you have just frozen every other coroutine scheduled on that same loop. The async keyword gives a false sense of safety. Writing async def does not make your code non blocking. It only makes it capable of being non blocking if you never call something synchronous inside it.
PHP tells a slightly different story, because the traditional PHP-FPM model spins up a new process per request, so one slow request doesn't directly starve another the way it does in Node. But the moment a team adopts something like Swoole, ReactPHP, or long running workers to squeeze more performance out of PHP, that isolation disappears. Now a single blocking database call, a slow file write, or an unbounded loop can stall an entire worker that was supposed to be serving hundreds of clients concurrently. Teams migrating from traditional PHP to async PHP frameworks often carry old blocking habits straight into a new environment that punishes them far more severely.
The Real Cost, In Numbers Teams Actually Feel
This isn't an academic problem. A payment gateway integration that does synchronous JSON parsing on a large webhook payload can add hundreds of milliseconds to every single request behind it in the queue, and during a flash sale, that queue can be hundreds of requests deep within seconds. A chat application where one user's message triggers a synchronous image resize can freeze typing indicators for every other user connected to that same server instance. A reporting endpoint that loops through a large dataset in memory instead of paginating the query can take down an entire API for two or three seconds at a time, several times a day, at exactly the moments when traffic is highest.
None of these failures show up as "the server crashed." They show up as p95 latency creeping upward, as support tickets that say "the app feels slow sometimes," as churn that nobody can point to a root cause for. Event loop blocking rarely takes a system down in one dramatic moment. It bleeds performance quietly, request after request, until someone finally profiles the app and finds the one function that has been sitting there the whole time, hogging the thread.
Why Smart Developers Still Make This Mistake
The uncomfortable truth is that this isn't a beginner mistake. Senior engineers write blocking code inside async functions all the time, usually because the blocking part doesn't look blocking. A third party SDK that wraps a synchronous HTTP client. A logging library that writes to disk synchronously by default. A cryptographic hashing function that takes 300 milliseconds on a large input and nobody thought to check. A recursive function that occasionally receives deeply nested input and spirals into a computation that eats the thread alive.
The event loop doesn't care about intent. It doesn't care that your function is "usually fast." It only cares whether the operation you just called returns control back to it quickly or not. And most teams don't find out where they crossed that line until production tells them, usually during the worst possible week to be debugging it.
Spotting It Before It Spots You
A few signals are worth watching for before things get bad. Rising tail latency (p95 and p99) while average latency looks fine is a classic tell, since it usually means a handful of requests are stalling everyone behind them. CPU usage that looks moderate while your app still feels sluggish under load is another, because event loop blocking often looks nothing like a resource exhaustion problem on a dashboard. And if you've never profiled your event loop lag directly, that's usually the first gap worth closing, since most teams are flying blind on this specific metric until something forces their hand.
Diagnosing it properly, and fixing it without introducing new bugs, is a deeper conversation than one blog post can hold. It touches worker threads, task queues, chunked processing, offloading to background jobs, and knowing exactly which library calls in your stack are quietly synchronous when you assumed they weren't.
This Is Exactly the Kind of Mistake Code Crimes Was Written to Catch
I wrote Code Crimes: Security & Performance Mistakes in Modern Code because bugs like this one rarely get taught properly. They get discovered the hard way, in production, under pressure, by developers who are good at their jobs but were never shown what a blocking call actually looks like disguised as an innocent line of code.
The book walks through real world cases across Python, PHP, and JavaScript, the three languages most of us are actually shipping in today. Each "crime" is presented the way it really happens: the code that looked fine in review, the impact it had once it hit real traffic or a real attacker, and the professional fix that actually holds up, not just a patch that hides the symptom.
Event loop blocking is one of the performance crimes covered in depth, alongside a long list of security mistakes that quietly put real applications and real users at risk. If you've ever shipped code that looked correct, passed review, and still caused a problem three months later, this book was written with you in mind.
You can read more about it here: Code Crimes: The Book Every Developer Needs Before Their Next Code Review
And if you're ready to see which of these crimes might already be sitting in your own codebase, you can grab the book on Amazon here: https://www.amazon.com/dp/B0H678BFCK
Top comments (0)