How to fix event loop blocking in Node.js
Event loop blocking is one of the most common performance issues in Node.js, causing unresponsive servers and poor request handling capacity. As the creator of CoreUI with over 12 years of Node.js development experience since 2014, I’ve optimized numerous APIs suffering from blocked event loops. The event loop handles all asynchronous operations, and blocking it with CPU-intensive synchronous code prevents Node.js from processing other requests. The solution involves identifying blocking operations and either making them asynchronous or offloading them to worker threads.
Identify blocking operations using profiling tools and move CPU-intensive tasks to worker threads.
// BAD: Blocking synchronous operation
app.get('/process', (req, res) => {
const data = JSON.parse(fs.readFileSync('large-file.json', 'utf8')) // Blocks event loop
const result = processData(data) // CPU-intensive
res.json(result)
})
// GOOD: Non-blocking with async/await
app.get('/process', async (req, res) => {
const data = JSON.parse(await fs.promises.readFile('large-file.json', 'utf8'))
const result = await processDataAsync(data)
res.json(result)
})
// BETTER: Offload to worker thread for CPU-intensive work
const { Worker } = require('worker_threads')
app.get('/process', async (req, res) => {
const worker = new Worker('./process-worker.js', {
workerData: { file: 'large-file.json' }
})
worker.on('message', (result) => {
res.json(result)
})
worker.on('error', (err) => {
res.status(500).json({ error: err.message })
})
})
Create process-worker.js:
const { parentPort, workerData } = require('worker_threads')
const fs = require('fs').promises
async function process() {
const data = JSON.parse(await fs.readFile(workerData.file, 'utf8'))
const result = heavyComputation(data) // Runs in separate thread
parentPort.postMessage(result)
}
process()
Best Practice Note
Use clinic.js doctor to detect event loop delays and identify blocking operations. Keep synchronous operations under 10ms to maintain responsiveness. For regex operations on user input, use timeouts to prevent ReDoS attacks. Break large loops into smaller chunks using setImmediate to yield to the event loop. This is exactly how we optimize CoreUI backend services—profiling endpoints, moving image processing to workers, and ensuring sub-millisecond event loop latency under load.



