Performance bugs that look like functional bugs
Some of the most confusing bugs I've chased weren't functional at all — they were performance problems wearing a functional disguise. The feature "doesn't work", but the real cause is that it's too slow.
Here's a pattern worth recognising, because it eats hours: a bug reported as broken behaviour — "the button does nothing," "it logs me out," "the data's wrong" — turns out, after investigation, to be a timing problem. Nothing is functionally wrong; something is just slow enough that the system gives up, races itself, or hits a limit. If you only think in functional terms, you chase the wrong cause. The tell is almost always the same: it's intermittent, and it's worse under load or on a slow connection.
The disguises
"The button does nothing." Tap, no response, so it reads as a dead button. Really, the request is just slow, and either the UI gives no feedback (no spinner) or it timed out client-side while the request was still in flight. The function works; it's too slow to look like it works — and a user who taps again can trigger a duplicate action.
"It randomly logs me out." Feels like a broken session. Often it's a slow auth/refresh call timing out under load, or a race between a token refresh and a request firing before the new token lands. The auth logic is fine; the timing isn't.
"The data is wrong / missing." Reads like a data bug. Sometimes it's a slow query that times out and returns partial or empty results, which the UI then renders as "no data" instead of "still loading" or "failed." Nothing's corrupt; something's just too slow to finish.
"It works on my machine." The eternal one. The feature is genuinely fine on a fast dev box and falls over on a mid-range phone, a slow network, or a loaded production database — the performance bugs only real conditions reveal.
"It breaks when lots of people use it." A functional flow that's perfect with one user and corrupts data or errors under concurrency. The logic is right for one; the contention is the bug.
How to tell it's actually performance
The signals are consistent, and learning to read them saves the wasted hours:
Spotting a performance bug in functional clothing
- It's intermittent — works most of the time, fails unpredictably (timing, not logic)
- It's worse under load, on slow networks, or on weaker hardware
- It can't be reproduced on a fast dev machine but happens in the field
- The "failure" looks like a timeout, a partial result, or a no-feedback dead end
- Double-actions or duplicate records appear (slow request + retry)
- It correlates with time of day / traffic, not with specific inputs
- Logs show slow queries, timeouts, or long response times around the reported "break"
Why it matters for how you test
Two practical consequences. First, when a functional bug is intermittent and load- or network-correlated, suspect performance before you tear apart the logic — check response times, query duration, and timeouts around the event. Half the time the "functional bug" is a slow call the system handled badly. Second, this is the argument for a pre-release performance smoke test: a regression that doubles a response time often presents as a functional flake — a button that "sometimes doesn't work" — so the thing that catches it isn't more functional testing, it's a glance at the latency numbers. The bugs that waste the most time are the ones filed under the wrong category; learning to smell a timing problem behind a "broken feature" is one of the higher-leverage instincts in QA.
// RELATED QA.CODES RESOURCES
Common Bug
// related
How to test when requirements are unclear
A field-tested approach for testing a story when the acceptance criteria are vague, missing, or contradictory.
Risk-based testing when everything is urgent
How to prioritise testing when the timeline just got cut in half and everything is labelled critical.