Your app is slow because one specific thing in the request path is slow. It is a database query that scans too many rows, an external API call you wait on, a cold start, or work you do on every request that you do not need to. "The app is slow" is not something you fix by guessing or by buying a bigger server. You fix it by measuring which requests are slow, then tracing one of them to see exactly where the time goes. Here is how to do that.
"The app is slow" is the wrong question
When someone says the app is slow, they almost never mean the whole app. They mean a page took too long to load, or a button spun, or a report did not come back. The app is a hundred different request paths, and slowness lives in one of them at a time. So the first move is to stop treating it as one big fog and turn it into a specific question: which request is slow, and where in that request does the time go.
This matters because the two instincts people reach for first are usually wrong. The first is to guess ("it must be the database", "it must be the server"), which sends you optimizing something that was never the problem. The second is to scale up, add a bigger instance or more of them, which hides the symptom for a while and costs money without telling you what was actually slow. Both skip the one step that works: look at where the time is spent.
Speed is worth this effort because it maps directly to outcomes. A Google-commissioned study by Deloitte of 37 brands and over 30 million sessions found that a 0.1 second improvement in mobile site speed lifted retail conversions by 8.4% and travel conversions by 10.1%.1 Slow is not just annoying, it is users leaving.
Where slowness actually comes from
Before the how, it helps to know what you are looking for. In practice, almost all app slowness comes from a short list of causes, and they are the same whether you wrote the code yourself or an AI wrote it for you:
- Slow database queries. A query with no index scans the whole table, so it is instant with a thousand rows and painful with a million. This is the single most common cause. We go deep on it in why is my database query slow.
- The N+1 pattern. Code that runs one query to get a list, then one more query per item in a loop. It looks fine in development and turns one request into hundreds of database round trips in production.
- External calls you wait on. A payment provider, an email service, an LLM API. Your request is only as fast as the slowest thing it waits for, and you do not control their latency.
- Cold starts. On serverless platforms the first request after idle has to spin up the runtime before it does any work, so it is seconds slower than the rest for no visible reason.
- Doing too much on the request. Work that could be done once and cached, or moved to a background job, done inline on every request instead: resizing an image, recomputing a total, loading more data than the page shows.
Notice that none of these are "the server is too small". They are specific, and each one shows up in a specific place in the request. That is exactly why tracing works and guessing does not.
Why the average lies, and what to measure instead
The first real step is to find which requests are slow, and here most dashboards mislead you by showing the average response time. The average hides the exact thing you care about. If 95 requests take 50 milliseconds and 5 take 4 seconds, the average still looks like a few hundred milliseconds, and you conclude everything is fine while a real slice of users sits waiting.
Latency is one of the four golden signals from Google's site reliability engineering practice, the core set of things worth watching in any production system, alongside traffic, errors, and saturation.2 The way to watch latency honestly is with percentiles, not the mean. Track p95 and p99: the p95 is the response time that 95% of requests come in under, and the p99 is the value 99% come in under. The p99 is the slow tail your unhappiest users feel.
Dean and Barroso's paper "The Tail at Scale" made the case that in real systems the tail is what defines the experience, because as a single request fans out to more services, the chance it hits the slow p99 of at least one of them climbs quickly.3 The practical takeaway is small: measure the tail, per route, and react when a route drifts above its own normal. That tells you which request to go look at.
| Metric | What it tells you | Why it can mislead |
|---|---|---|
| Average (mean) | A rough sense of typical speed | A few very slow requests barely move it, so it hides the tail |
| p50 (median) | The middle request, a fair "typical" | Says nothing about the slow requests at all |
| p95 | Where the slow requests start | Good early warning, still misses the worst 5% |
| p99 | The slow tail real users feel | Noisier, but this is what a slowness complaint usually is |
| Max | The single worst request | One outlier can be a fluke, do not tune to it alone |
How to find the exact cause: trace one slow request
Once you know which route is slow, you stop guessing by tracing a slow request. A trace records the path of a single request through your system and breaks it into spans, where each span is one unit of work with a start time and an end time.4 Put together, the spans show you the timeline of the request: how long was spent in your code, in each database query, in each outbound call. The slow span is your answer.
This is the whole trick. Instead of "the checkout is slow", a trace tells you "the checkout spent 1.9 seconds of its 2.1 seconds in a single query against the orders table". Now you know exactly what to fix, and you can prove the fix worked because the span gets shorter. You get traces by instrumenting your app with OpenTelemetry, which auto-instruments most frameworks, database drivers, and HTTP clients, so the spans appear without you writing timing code by hand.
The steps, in order:
- Watch p95 and p99 latency per route, and find the route that is slow or getting slower.
- Pull up a trace of an actual slow request on that route, not an average.
- Read the spans and find the one that holds most of the time.
- Fix that specific thing: add an index, batch the N+1, cache the call, move it to a background job.
- Watch the percentile come back down to confirm it.
A worked example: why is login slow
"Login is slow" is a great example because login looks trivial and is not. A single login request often does a database lookup for the user, a password hash comparison that is slow on purpose for security, sometimes a call to an external identity provider or an email or SMS step, and a write to create the session. Any one of those can be the slow part, and they need different fixes.
Guessing here is a trap. If you assume it is the password hash and tune that, you might weaken your security for nothing while the real cost was an unindexed lookup on the email column, or a synchronous "send a login notification email" call blocking the response. A trace settles it in seconds: it shows the login request as spans, and one of them, the user query, the hash, the email call, holds the time. You fix that one.
The same approach answers "why is my API slow", "why is this page slow", and "why does the app slow down under load". The question changes, the method does not: measure the tail, trace a slow one, fix the slow span.
Finding it without living in dashboards
Traditional tools give you the traces and then leave you to go read them, which means learning a query language and living in a dashboard. Fixter is built the other way. You drop in the SDK, which is OpenTelemetry under the hood, and your logs, traces, and metrics are stored. Then you ask about them in plain language over MCP, from Claude Code or Cursor, the same editor you already work in.
So "why is login slow" is a question you type, and the answer comes back with the slow span already found and the traces attached. When a route's latency drifts up, Fixter runs the first pass of the investigation for you: it finds the slow requests, reads the traces, and tells you which step got slower and since when, instead of just alerting that something is slow and leaving the digging to you.
Your code never leaves your machine for this. Fixter stores the production signal, the traces and metrics, and you use that to find the slow part, then make the change yourself in your editor. If you want the broader picture of what to watch beyond latency, see monitoring errors in production.
Key takeaways
- An app is slow because of one specific thing in the request path, not "the whole app"
- The usual causes are slow queries, N+1 patterns, external calls you wait on, cold starts, and doing too much per request
- Averages hide slow requests, measure p95 and p99 latency per route instead
- Trace a slow request, read the spans, and fix the one span that holds the time
- Fixter finds the slow span for you and answers "why is X slow" in plain language over MCP
Frequently asked questions
Why is my app slow?
An app is almost always slow because of one specific thing in the request path: a database query scanning too many rows, an external API call you wait on, a cold start, or work you do on the request that could be avoided. The fix is to trace a slow request and see where the time actually goes, not to guess.
How do I find out what is making my app slow?
Measure latency per route at the 95th and 99th percentile so you catch the slow requests the average hides, then trace one of those slow requests. A trace breaks the request into spans and shows the time spent in each step, so you see the exact query or call that is slow instead of guessing.
Why does my app feel slow when the average response time looks fine?
Averages hide the slow requests. If most requests are fast and a few are very slow, the mean stays low while real users hit the slow tail. Google and Barroso's work on tail latency shows the 99th percentile is what users actually feel, so you should track p95 and p99, not the average.
Why is my login slow specifically?
Login is a common slow spot because it does more than it looks: a database lookup, a slow password hash by design, sometimes an external identity or email call, and session writes. Tracing a slow login request shows which of those steps is eating the time, and usually it is one of them, not all.
What is a good response time for a web app?
There is no universal number, it depends on the route, but a common practical target is a p95 under a few hundred milliseconds for interactive requests. What matters more than a fixed number is watching the percentile over time and reacting when a route gets slower than its own baseline.
Is app slowness a code problem or an infrastructure problem?
Usually code and data: slow queries, N+1 patterns, and synchronous external calls cause most slowness before scale becomes the issue. Infrastructure matters under load, but the fastest way to know which is at fault is to trace a slow request rather than assume you need a bigger server.
Sources
- Deloitte and Google, Milliseconds Make Millions (0.1s mobile speed improvement lifted retail conversions 8.4%, travel 10.1%; 37 brands, 30M+ sessions)
- Google, Site Reliability Engineering, the four golden signals (latency, traffic, errors, saturation)
- Dean and Barroso, The Tail at Scale, Communications of the ACM, 2013 (why the p99 tail defines user experience)
- OpenTelemetry, Traces (a span is a timed unit of work; a trace shows where a request spends its time)