The Hot Path: Finding and Optimizing It
The hot path is the code that executes on every request or on the most performance-critical requests in a system. It is the code where a 1ms improvement has the largest impact on the overall system latency, and where a 5ms regression is immediately visible in p99 latency metrics. Finding the hot path requires profiling, not intuition -- experienced engineers are wrong about which code is slow as often as they are right. Optimizing the hot path requires removing unnecessary work, deferring work to background tasks, and caching the results of expensive operations.
Written by Yashveer Singh, founder of Yashveer Labs.
What you actually need to know
- Performance optimization without profiling is guesswork. Measure first; optimize the bottleneck that measurement reveals.
- N+1 queries are the most common and most impactful hot path problem in web applications. They are invisible without profiling and obvious after it.
- Moving work to background tasks -- email sending, webhooks, analytics -- is the highest-return change for most request handlers. Synchronous work that does not need to be synchronous is waste.
- Caching is most effective when the computation is expensive and the results are reusable across requests. Cache the computation, not the database query.
- Measure after every optimization. An optimization that does not improve the p99 latency in production did not address the actual bottleneck.
| Optimization Type | Typical Latency Reduction | Implementation Effort | Risk Level |
|---|---|---|---|
| Fix N+1 queries | 50-95% of query time | Low-medium | Low |
| Background task conversion | 20-80% of request time | Medium | Low |
| Response caching | 90%+ for cached requests | Medium | Medium (invalidation) |
| Database query optimization | 30-70% of query time | Medium | Low |
| Connection pooling | 10-40% | Low | Low |
| Reduce serialization | 5-30% | Low-medium | Low |
The core argument
The performance investigation that starts with a hunch ("I think the database queries are slow") and ends with an optimization based on that hunch is optimizing a hypothesis. Sometimes the hypothesis is right and the optimization works. Often the hypothesis is wrong and the optimization has no measurable effect because the actual bottleneck was somewhere else.
The correct sequence: instrument the system, measure the request latency breakdown, identify the functions consuming the most time, optimize the highest-cost function, re-measure to verify the improvement. This sequence is reliable because it is based on evidence rather than intuition. Engineers who skip the measurement step consistently optimize the wrong things.
I have done performance investigations on five different Node.js services in the past two years. In three of them, my initial hypothesis about the bottleneck was wrong. In one, I was certain the problem was a slow database query (it was); in the others, I found N+1 query patterns I had not anticipated, a synchronous external API call inside a request handler that was blocking for 200ms, and a JSON serialization loop that was running in the hot path at 50ms per request.
None of the last three would have been found without profiling. The profiler output was unambiguous -- the call tree showed exactly where the time was being spent. The fix in each case was clear once the bottleneck was identified.
Finding the hot path with a profiler
The Node.js profiler built into V8 is accessible through the --prof flag:
``bash node --prof app.js ``
This generates a isolate-*.log file that can be processed with node --prof-process to produce a human-readable output showing which functions consumed the most CPU time. Clinic.js wraps this in a better interface:
``bash npx clinic flame -- node app.js ``
The flame graph output shows the call stack for every sampled moment during the profiling run. The wide horizontal bars are the functions that were on the stack most often -- these are the hot path.
For Python services, py-spy produces flame graphs from a running process without requiring code instrumentation:
``bash py-spy record -o profile.svg --pid 12345 ``
For Java/JVM services, async-profiler and the Java Flight Recorder produce CPU and allocation profiles that identify the hot methods and the hot allocation sites.
The profiling output answers the question "where is the time being spent?" -- not where you think it is, but where it actually is.
The N+1 query pattern
The N+1 query pattern is the most common hot path problem in ORM-using web applications. The pattern:
- Fetch a list of objects (1 query)
- For each object in the list, fetch a related object (N queries)
- Total: N+1 queries for what could be done in 1-2 queries
The concrete example: a user list endpoint that fetches all users and then makes a separate query for each user's subscription status:
``typescript // N+1: one query for users + one query per user for subscription const users = await User.findAll(); // 1 query const withSubscription = await Promise.all( users.map(user => Subscription.findOne({ where: { userId: user.id } })) // N queries ); ``
The fix: eager loading in a single JOIN query or a single bulk query:
``typescript // 1 query with eager loading const users = await User.findAll({ include: [{ model: Subscription }] }); ``
For 50 users, the N+1 version makes 51 database queries. The eager loading version makes 1. The latency difference is proportional to the number of users and the database query overhead.
Moving work to background tasks
Synchronous work in the request handler is work that the user waits for. Work that does not need to complete before the response is returned should be moved to a background queue.
The operations that are commonly synchronous when they should be asynchronous: sending welcome emails after signup (the user does not need to wait for the email to be sent), delivering webhooks to external systems (the customer does not need to wait for the webhook acknowledgment), tracking analytics events (analytics are not part of the user's response), and sending Slack notifications (internal notifications are not blocking).
Moving these to a background queue (Bull, BullMQ, or a managed queue like SQS) removes their latency from the request handler and allows them to retry on failure without blocking the user.
The median latency reduction from moving email sending to a background task: 100-300ms (the typical time for an email provider's API call). For a signup endpoint that was blocking on email delivery, this improvement is immediately visible in the p99 latency.
Response caching for expensive operations
Response caching stores the result of an expensive computation and returns it directly for subsequent requests with the same input. The highest-return cache targets: page rendering for content that changes infrequently (blog posts, product listings), expensive aggregation queries that produce dashboard data, and third-party API responses that are valid for a specific time window.
The cache hit rate and the invalidation strategy are the design decisions that determine whether caching improves or degrades the system:
Cache hit rate: A cache that is hit on 5 percent of requests provides little benefit. A cache that is hit on 90 percent of requests provides massive benefit. The hit rate depends on the key design -- caches keyed by specific user ID have lower hit rates than caches keyed by data that is shared across users.
Invalidation strategy: Caches that are not invalidated when the underlying data changes produce stale data. The strategies: TTL-based expiry (simple, eventually consistent), write-invalidation (invalidate on write, more complex, immediately consistent), and cache-aside (application manages cache explicitly, maximum control).
Common mistakes engineers make with hot path optimization
- Optimizing without profiling first. The intuition about what is slow is wrong often enough that measurement is always required before optimization.
- Optimizing code that is not in the hot path. A function that runs 10 times per day does not need to be optimized, even if it is slow.
- Not measuring after the optimization. An optimization that did not improve the target metric did not fix the bottleneck -- the actual bottleneck is still somewhere else.
- Adding caching before fixing N+1 queries. Caching the results of N+1 queries caches the symptom rather than fixing the cause. Fix the queries first, then evaluate whether caching provides additional benefit.
- Optimizing for throughput when the problem is latency (or vice versa). Throughput (requests per second) and latency (milliseconds per request) are related but distinct. An optimization that increases throughput may not reduce p99 latency.
Where to start: a 3-step hot path investigation
Step 1: Identify the three slowest endpoints by p99 latency. Your monitoring should have this data. If it does not, add timing instrumentation to every request handler. The three slowest endpoints are the candidates for hot path investigation.
Step 2: Profile the slowest endpoint under realistic load. Use Clinic.js, py-spy, or the relevant profiler for your stack. Generate the flame graph. Identify the top 3 functions by time consumed.
Step 3: Fix the highest-cost bottleneck and re-measure. If it is an N+1 query, add eager loading. If it is a synchronous external API call, move it to a background task. If it is a repeated computation, add caching. After the fix, re-profile to confirm the improvement and identify the next bottleneck.
The Code That Runs Every Time
Yashveer Singh. Founder of Yashveer Labs. The most satisfying performance work I have done is hot path investigation, because the result is always specific and measurable. The N+1 query on the Nexli platform's feed endpoint was making 180 database queries per request; after eager loading, it made 2. The p99 latency on the feed endpoint dropped from 850ms to 40ms. That improvement was visible to users immediately -- a feed that was noticeably slow became one that felt instant. The profiler found it in 20 minutes; the fix took 45 minutes. The ratio of investigation time to impact is why hot path profiling is the highest-leverage performance work available.
Related reading
- The Garbage Collection Tax: A Backend Story
- The HTTP Caching Strategy That Most Teams Get Wrong
- The Database Query That Slowed Everything Down
- The Observability Stack That Pays for Itself
Frequently asked
About me and why that should matter to you
Yashveer Singh. Full stack developer. Founder of Yashveer Labs. Based in New Delhi. The reason it should matter to you is that most engineers writing about this topic have not actually done it. I have. The code is on GitHub. The systems are on real URLs. The portfolio has the proof. The contact channel is Instagram. If the work needs to get done, that is how you reach me.
Posts that line up with this one.
- Performance Optimization
The Garbage Collection Tax: A Backend Story
How garbage collection pressure creates latency spikes in Node.js and JVM services -- and the profiling and architectural changes that reduce the tax.
- Performance Optimization
Backend Performance Budgets: How to Set Them
A backend performance budget is a written commitment to specific latency targets for specific endpoints. Without one, performance is whatever shipped last. With one, regressions get caught in CI.
- Performance Optimization
The Caching Hierarchy: Browser, CDN, Edge, Application, Database
Every web application has five caching layers. Understanding which one to use for which data is how fast applications stay fast at scale.
- Performance Optimization
Image Optimization at Scale: AVIF, WebP, Responsive Images
Images are the largest contributor to page weight on most web products. Here is the format selection, responsive image, and delivery strategy that cuts load time without manual work.