Performance rarely breaks because of one obviously bad line of code. It usually degrades through a chain of individually reasonable decisions: a little more JavaScript, one more database query, a larger API response, a synchronous third-party call. The job is to find which part of that chain is actually consuming the time.
1. Start with the user-visible symptom
A slow page is a symptom, not a diagnosis. Start by defining what is slow: initial navigation, largest content rendering, interaction readiness, an API request, a report, a background job, or an entire workflow. Then capture a baseline under representative conditions. Browser performance data, server timing, request traces and database metrics should answer the same question from different layers.
const started = performance.now();
const result = await loadDashboard();
console.log({ durationMs: performance.now() - started });
return result;
2. Check the browser before blaming the backend
The browser can spend significant time parsing HTML, compiling and executing JavaScript, calculating styles, laying out elements and painting pixels. Large bundles, unnecessary client-side work, render-blocking resources and excessive DOM work can make an application feel slow even when the API responds quickly. Use browser performance tooling and real-user data to distinguish network delay from main-thread work.
3. Inspect the network path
Look at DNS, connection setup, TLS, request/response time, transfer size and the number of requests. Compression, caching and resource prioritisation can reduce transfer cost, but do not optimise blindly. A 2 MB response that is requested once may be less important than a 200 KB response that blocks every page view.
4. Find expensive application work
Profile the application around the slow request. Look for repeated computation, unnecessary serialization, N+1 database access, synchronous calls to external systems and work that does not need to happen before the response. Measure wall-clock time as well as CPU time because waiting on I/O can dominate a request without using much CPU.
5. Treat the database as a measurable dependency
Database performance problems often appear as application latency. Inspect query duration, query count, rows scanned, lock waits and execution plans. Indexes can help selective queries, but indexes also add write and storage cost. Fix the query pattern and access path that the evidence identifies instead of adding indexes to every column.
6. Audit third-party dependencies
Payment providers, analytics, maps, email, identity providers and other external APIs can add latency or failure modes outside your process. Record their duration and error rate separately. Decide which calls are required for the critical path and which can move to asynchronous processing.
7. Make performance regressions visible
A one-time optimisation is not a performance strategy. Add performance checks to the delivery process: representative load tests where appropriate, query monitoring, error and latency dashboards, and real-user monitoring for important user journeys. Track percentiles such as p95 or p99 when tail latency matters; averages can hide slow experiences.
8. A practical bottleneck checklist
Before changing code, answer these questions: What user-visible operation is slow? What is the measured baseline? Which layer consumes the most time? Is the delay CPU, I/O, lock contention, transfer, rendering or a remote dependency? Can the work be removed, reduced, cached, batched, paginated or made asynchronous? How will the fix be verified after deployment?
Engineering references
This article is an original synthesis of established engineering practices. The references below are included for readers who want the underlying technical documentation and deeper treatment.