If your Node.js app is slow, the cause is usually not Node itself — it is something blocking the event loop, or the database, or asynchronous code written in a way that runs in series when it could run in parallel. Node is single-threaded by design, and that design is genuinely fast right up until one piece of code holds the thread. Below are the seven fixes, in order of impact.

The honest summary: Node.js is fast. Slow Node apps are slow because of the same handful of mistakes on every project — and almost all of them come back to one idea: never make the single thread wait.

Measure first

Before changing anything, profile. Run the built-in profiler, or a tool like clinic.js, and watch the event loop: the metric that matters most for Node is event-loop lag — how long the single thread is held away from handling requests. Record the slow endpoints, the query counts, and the p50, p95, and p99 response times. p95 is the number to fix against. A healthy Node API hits p95 under 200 milliseconds; a slow one sits over a second.

Fix 1 — Stop blocking the event loop

This is the single most important idea in Node performance. Node handles thousands of connections on one thread by never waiting — but synchronous, CPU-heavy work breaks that promise. A large synchronous loop, a heavy JSON.parse, image processing, a synchronous file read, a complex regular expression — while any of these run, the one thread is held, and every other request is frozen.

The fix is to get CPU-heavy work off the main thread. Move it to a worker thread, offload it to a separate service, or break it into chunks that yield. The rule is simple and absolute: the event loop must never be made to wait on computation.

Fix 2 — Fix the database, not Node

Just as with any backend, the most common real cause of slowness is the database. N+1 query patterns, missing indexes, no connection pooling, queries that return far more data than the endpoint needs. Node gets blamed; the database is guilty.

Profile the queries, add the missing indexes, eliminate the N+1s, and make sure you are using a properly configured connection pool rather than opening a connection per request. A slow query is a slow query in any language — and fixing it is usually the single biggest win available.

Fix 3 — Use async correctly

Node’s async and await are easy to use and easy to misuse. The most common mistake is awaiting inside a loop — running ten independent database calls one after another, when they could run at the same time. Replace a loop of sequential awaits with Promise.all, and ten 50-millisecond calls finish in 50 milliseconds instead of 500.

The other async trap is the unhandled promise rejection, which can crash the process or leave it in a bad state. Every promise needs its error path handled. Async code that runs in series when it could run in parallel, and async errors that go uncaught, are between them responsible for a large share of slow and unreliable Node apps.

Fix 4 — Cache the expensive work

The fastest operation is the one you do not perform. Put a Redis cache in front of expensive queries, computed results, and external API responses. Cache dashboard aggregations that recompute the same numbers on every request; cache anything that hits a third-party API; cache expensive serialisations. Be disciplined about invalidation — cache keys you can target precisely — and do not cache anything that must be real-time or is specific to a fast-changing user state.

Fix 5 — Offload heavy work to a queue

Anything that takes meaningful time and does not need to finish before the response should not happen in the request. Sending email, generating documents, processing uploads, calling slow external services, recomputing analytics — all of it belongs in a background job. A queue such as BullMQ, backed by Redis, lets the request return immediately while a separate worker process does the slow work. This both speeds up the response and keeps that heavy work off the request-handling thread.

Fix 6 — Right-size payloads and middleware

Two quieter sources of slowness. First, payloads: an endpoint that returns far more data than the client needs wastes serialisation time and bandwidth — return only the fields in use, paginate large lists, and stream big responses rather than building them in memory. Second, middleware: every middleware function runs on every request it matches, and a stack of rarely-needed middleware adds latency to everything. Trim the stack, scope middleware to the routes that need it, and enable response compression.

Fix 7 — Scale across CPU cores

A single Node process uses a single CPU core. On a multi-core machine, that leaves most of the hardware idle. The fix is to run multiple Node processes — via the cluster module, a process manager like PM2, or multiple container replicas behind a load balancer — so the application uses every core. This does not make a single slow request faster; it lets the application handle far more requests at once. Combine it with the first six fixes, never as a substitute for them.

A note on memory leaks

One slow-burn problem deserves its own mention, because it does not look like the others. A Node.js process that gradually accumulates memory it never releases — a memory leak — gets steadily slower as garbage collection works harder, and eventually crashes or is killed and restarted. It often hides for weeks, because a fresh restart always looks healthy.

The usual causes are familiar: data pushed into a long-lived array or map and never removed, event listeners attached and never detached, closures that quietly retain large objects, and caches with no eviction policy. The fix is to watch memory as a first-class metric — a process whose memory climbs steadily across a day, rather than rising and falling, is leaking. Heap snapshots taken hours apart show what is accumulating. A healthy Node app holds a stable memory profile under steady load; a climbing one is a problem to find before it finds you.

What good looks like after the seven fixes

A well-tuned Node.js application in 2026 hits these numbers: p95 latency under 200 milliseconds on real traffic; event-loop lag under 50 milliseconds so no request is starved; query count under 10 per read request; a cache hit ratio above 80 percent on cacheable endpoints; and memory that stays flat rather than climbing toward a leak. These are the targets we hold every Node build to — and the seven fixes above are the order we apply them in.

Common questions

Why is Node.js slow if it is supposed to be fast?

Node.js is fast for the workloads it is designed for — high-concurrency, I/O-bound work — and it stays fast as long as nothing blocks the event loop. Node handles thousands of connections on a single thread by never waiting, so the moment synchronous, CPU-heavy code holds that thread, every request freezes. A slow Node app is almost never slow because of the runtime; it is slow because of blocking code, database problems, or async written to run in series. Fix those and the speed returns.

What does ‘blocking the event loop’ actually mean?

Node.js runs your JavaScript on a single thread, the event loop, which handles every incoming request by quickly starting work and moving on. Blocking the event loop means running synchronous code that holds that thread — a large computation, a synchronous file read, a heavy JSON.parse, a slow regular expression. While that code runs, the thread cannot handle any other request, so the whole app appears to freeze. The cure is to keep CPU-heavy work off the main thread, using worker threads or a separate service.

Will adding more servers fix a slow Node.js app?

Not on its own. Adding servers, or running more Node processes across CPU cores, lets the application handle more requests at once — but it does nothing for a single request that is slow because the event loop is blocked or a query is unindexed. Scaling out multiplies a per-request problem instead of fixing it, and it raises your hosting bill while doing so. Fix the blocking code, the database, and the async patterns first; scale across cores and servers afterward, as the final step.

How much faster will the seven fixes make my Node app?

On a typical slow Node app with a p95 of one to two seconds, the seven fixes together usually bring p95 under 200 milliseconds and let the app handle several times the load. The largest single gains are normally unblocking the event loop and fixing the database — those two often account for most of the improvement. Caching and queues take pressure off further, and scaling across cores raises the ceiling. Exact numbers depend on the starting state, but a halving of p95 within the first two weeks of focused work is normal.

Does TypeScript make a Node.js app slower?

No. TypeScript is compiled away before the code runs — the application executes as plain JavaScript, with no runtime cost from the types. TypeScript adds a compile step in development and nothing at all in production. If anything, a typed codebase tends to be faster over its life, because it is safer to refactor and optimise without fear of breaking something. Performance problems in a Node app come from blocking code, the database, and async patterns — never from TypeScript.

Want a free Node.js performance audit?

Send us a repository URL or staging access. We will send back a short audit showing your current p95 latency, your event-loop lag, the worst offenders, and the highest-impact fixes for your app.


Talk to our Node.js team