"It got slower" is two different tasks depending on one fact: is there a known-good "before" to compare against? If yes, this is a regression hunt, and the fastest path to the cause is diffing what changed between fast and slow. If no — nobody remembers what it used to cost, there's no prior commit or release to point at — that's a generic perf audit instead, a different and slower task. Most real regressions, once a baseline exists, fall into one of five shapes.
A loop that used to batch a query, API call, or file read now issues one per item. Signature: latency scales linearly with the size of an input collection that used to be a flat cost regardless of size.
Check: count the number of downstream calls (queries, requests, reads) made per operation, before and after the baseline. If that count now scales with an input size where it used to be constant, this is it.
New logging, validation, serialization, or a blocking call landed inline on a path where it used to be absent, deferred, or async. Often arrives as a "small" addition — a debug log line, an extra validation pass — that nobody profiled because it looked too minor to matter.
Check: diff the exact function/route/query between baseline and now for anything new that executes unconditionally and synchronously. Time that addition in isolation; small-looking code can still dominate a hot path called thousands of times.
A change from O(n) to O(n²) or worse, usually hidden inside a library call or a refactor that looked like a simplification — list.index() or in on a list replacing a set/dict lookup, a nested loop replacing a single pass, a sort added inside a loop that used to sort once outside it.
Check: for the specific function that slowed down, does its cost now grow faster than linearly with input size? Test with 2x and 10x the input and compare the ratio of time taken, not just the absolute numbers.
A cache was invalidated more aggressively than before, a cache key changed shape and now misses where it used to hit, or a memoized value is recomputed per-call instead of per-request or per-session.
Check: compare cache hit rate before and after the baseline for the specific cache on this path, not the aggregate hit rate across the whole system — an aggregate rate can look fine while the one cache that matters here silently dropped to zero.
A shared pool — DB connections, threads, worker processes — is now smaller relative to load, or something upstream now holds a resource from that pool longer than it used to, starving everything waiting behind it.
Check: look at pool size and average hold time before and after the baseline, not just pool size alone — a pool that didn't shrink can still regress if something now holds each connection twice as long.
State which of the five is actually responsible with a diff-evidence citation — a commit, a line number, a config value that changed — not whichever is generically most common. If the diff doesn't clearly show the cause yet, name the specific measurement that would confirm it (a timing log around the suspect call, an EXPLAIN plan, a flamegraph of the hot path) rather than shipping a guess. Then say whether the proposed fix is a real fix or a mitigation: raising a pool size masks resource contention until load grows again, while releasing the resource earlier at the actual line is the fix.
None of this requires an LLM on a small, well-understood codebase — it's five shapes and a diff. Where a model helps is on a large or unfamiliar one: walking an actual dependency bump, a multi-file refactor, or a config change across every place it touches to find which specific change the slow path executes, instead of guessing from the commit message, which is what the perf-regression-hunter skill in the Claude Code Debug Pack does. If the slowdown is intermittent rather than consistent, see the companion guide on diagnosing flaky tests; if you're not yet sure the regression is even isolated to the right layer, see the one on root-cause tracing; if you're staring at a pile of error logs instead of a single known-slow path, see the one on error log triage; if the slowdown only appears after the process has been running a while and keeps getting worse, see the one on memory leak hunting; if the slowdown or wrong result depends on thread/task timing rather than input size, see the one on concurrency bug finding.