Article Performance

The AI Performance Fix Plan

Ask an AI assistant to optimize a function and it will almost always return a diff. Memoize this, cache that, batch the other thing. Source code alone cannot tell it whether that function accounted for sixty percent of your response time or nothing at all. Performance work is a measurement problem wearing a coding problem's clothes: AI can help run the instruments when it has access, but the evidence still has to come from the real system and workload.

Last reviewed: Jul 31 2026

A dark technical diorama on a wet reflective floor: a cyan cloud of data tiles feeds a black segmented pipeline glowing amber from underneath, beside a rising column of stacked measurement bars whose tallest bar burns amber.
Everything on the platform is cold except one stretch of pipe. Finding it is the work; the diff comes after.

TL;DR

Do not ask AI to make code faster. Measure first, then ask it to explain a specific number. Record a baseline on realistic data, locate the bottleneck with a profiler, an explain plan, or a trace, write down the target and what you are willing to spend to reach it, change one thing, and measure again under the same conditions. A performance change without a before and an after is not a fix — it is a refactor with a hopeful commit message.

A Fix Without a Number Is a Guess

Most performance regressions are reported in feelings. The dashboard is sluggish. Search takes forever. The export times out sometimes. Those are real signals, but none of them identify a cause, and none of them can tell you afterwards whether you fixed anything. The first job is turning a complaint into a measurement that can move.

That measurement has to name three things: what is being timed, where it is being timed, and on which data. "The report page is slow" becomes "the report endpoint takes 4.2 seconds at p95 for accounts with more than 10,000 rows, measured server-side, excluding client render." Now there is something to compare against. Averages hide the problem — a p50 of 200ms and a p99 of 9 seconds describes a feature that most users find fine and some users find broken, and only one of those numbers is worth optimizing.

Distinguish the three quantities that get casually merged into the word "slow." Latency is how long one operation takes. Throughput is how many operations complete per unit of time. Resource cost is what each one consumes in CPU, memory, connections, or money. Improving one frequently degrades another. Batching raises throughput and raises tail latency. Caching cuts latency and raises memory and staleness risk. Decide which one you are actually being paid to improve before anyone writes code.

Key idea

AI can read code and reason about algorithmic complexity, but it cannot infer your runtime from source alone. Unless you give it access to the relevant environment and instruments, it does not know your row counts, index statistics, cache hit rates, connection pool limits, payload sizes, network topology, concurrency levels, or which of the forty functions in a request path actually consumes the time. Every one of those is an empirical fact about a running system, and every one of them can invert the correct answer.


The Six-Part Performance Plan

1. State the symptom and who it hurts

Write down the user-visible problem, the affected flow, the affected population, and how often it happens. "Checkout is slow for large carts, roughly 3% of orders, and support has escalated it twice this month" scopes the work. A symptom without an affected user is usually a preference. This step also decides whether the work is worth doing at all: a 40% improvement on a page nobody opens is not a win, and a 5% improvement on the request path every session hits may be.

2. Establish a baseline on realistic data

Measure the current behavior before touching anything, on data that resembles production in size and shape. Record p50, p95, and p99 rather than a single run, note the environment, the dataset, the concurrency, the cache state, and whether the measurement is cold or warm. Save the raw output. A baseline you cannot reproduce later is not a baseline — you will need to run the exact same measurement after the change, and "roughly four seconds, I think" does not survive that comparison.

3. Locate the bottleneck with evidence, not intuition

Use the instrument that matches the layer: a CPU or allocation profiler for compute, an explain plan and query log for the database, a request trace or waterfall for distributed calls, a browser performance panel for render and network. Attribute the time. If the trace shows 3.6 of 4.2 seconds inside one query, the application code is not the story. If no single component dominates, say so explicitly — death by a thousand cuts is a different kind of problem and it needs a different kind of plan than a hot spot does.

4. Set the target and the price you will pay

Name the number that means done: "p95 under 800ms on the same dataset." Then name what you are willing to trade for it — added memory, added complexity, staleness in a cache, a denormalized column, a background job, an index that slows writes. Performance is bought, not found. Writing the price down before you start is what stops a two-day investigation from turning into a two-week architectural rewrite that nobody approved.

5. Change one thing and re-measure under the same conditions

Apply the smallest change that addresses the located bottleneck, then rerun the identical measurement. Same dataset, same environment, same concurrency, same cache state. If you bundle four optimizations into one diff and the number improves by 30%, you have learned nothing about which one earned it — and you are now maintaining three changes that may have contributed zero. Keep the measurement output in the pull request so the reviewer sees evidence rather than a claim.

6. Protect the result and confirm it in production

A local improvement is a hypothesis about production. Verify it with real traffic: the same percentile, on the same dashboard, over a window long enough to cover the traffic pattern. Then add whatever prevents the regression from returning silently — a performance assertion in the test suite, an alert on the percentile, or a documented budget. Also check what you did not intend to change: error rates, memory, database load, and cost per request all belong in the after-picture.


The Measurement Contract

Before the first line of code changes, fill this in. If a row is blank, the fix cannot be evaluated — and neither can AI's suggestion for it.

Field Question it answers Failure if left blank
Operation What exactly is being timed, from which boundary to which boundary? Before and after measure different things and appear to improve.
Metric Latency, throughput, or resource cost — and at which percentile? An average improves while the tail that users complain about gets worse.
Dataset How much data, with what distribution and cardinality? A fix validated on 50 rows behaves differently on 5 million.
Environment Where is it measured, under what concurrency and cache state? A warm laptop benchmark predicts nothing about a cold, contended server.
Target What number means the work is finished? Optimization continues until someone gets bored or breaks something.
Accepted cost What complexity, memory, staleness, or write cost is allowed? The fix ships and the bill for it arrives in a later incident.
Review test

Ask the author of a performance pull request one question: what was the number before, what is it now, and how was each one produced? If the answer is a description of the code change rather than two measurements taken the same way, the diff is unverified. It might still be an improvement. Nobody in the room currently knows.


Give AI the Profile, Not the File

"This function is slow, make it faster" invites invention. The model has no measurement, so it pattern-matches on shapes that are sometimes slow and returns whichever optimization is most common in its training data for code that looks like yours. You get a plausible diff aimed at an unverified target.

Paste the evidence instead. Profiler output with self and total time. The explain plan with the actual row counts and the chosen index. The trace spans with durations. The timing breakdown per phase. Then the request becomes tractable: here is where the time goes, explain why this step costs what it costs, and propose the smallest change that would reduce it. That is a question about a specific number, and AI is genuinely good at those — reading an explain plan and noticing a sequential scan on a filtered column, or spotting that a loop issues one query per row.

Ask it to rank hypotheses rather than to produce code. A good response looks like a short ordered list: most likely cause, expected magnitude of improvement, and the measurement that would prove or disprove it. That framing keeps you in the loop where the judgment lives, and it makes wrong answers cheap to detect, because each hypothesis arrives with the test that kills it.

Sanitize what you paste. Query plans, traces, and profiler output routinely embed customer identifiers, email addresses, internal hostnames, and sample values from production rows. Strip them the same way you would strip any other production data before it leaves your environment — see Sanitizing Code and Data Before Sending to AI.


Optimizations That Cost More Than They Save

The suggestions below are all legitimate techniques. Each one is also a common way to make a system worse while the benchmark says otherwise.

A cache without an invalidation story. Adding a cache is a two-line change; deciding what happens when the underlying data changes is the actual work. If the plan does not say what invalidates the entry, how stale a read may be, and what the user sees during that window, the cache is a correctness bug scheduled for later.

An index that slows every write. Indexes accelerate reads and tax inserts, updates, and storage. On a write-heavy table, an index added to fix one report can degrade the path that matters more. Measure the write side too, and check whether the query planner will even use the index you added.

Parallelism introduced without ordering guarantees. Turning a sequential loop into concurrent work is an easy suggestion and a reliable source of race conditions, connection pool exhaustion, and downstream rate limiting. Faster and occasionally wrong is not faster.

Memoization that keeps memory alive. An unbounded memo table is a leak with a friendly name. If the key space is user-controlled or grows with traffic, the improvement lasts until the process runs out of memory during peak hours.

An N+1 replaced by one enormous query. Collapsing per-row queries into a single join usually helps — until the join produces a multiplied result set that transfers ten times the data or spills to disk. The fix for N+1 is usually batching or an explicit preload, not the widest join available.

Micro-optimizations inside a network-bound path. Tightening a loop that costs 3ms in a request that spends 900ms waiting on an external call is a change with no upside and a real review cost. This is where a located bottleneck earns its keep: it tells you which improvements are arithmetically incapable of mattering.

Scope check

Performance changes tend to grow. A query fix becomes a schema change; a schema change becomes a migration; a migration becomes a rewrite. Keep the diff inside what the measurement justified, and if the evidence genuinely points at an architectural problem, stop and plan it as one — see The AI Change Budget and The AI Database Migration Plan.


Copy-Paste Prompt: Performance Analysis

Prompt

"Analyze this performance problem before proposing any code. Symptom and affected users: [symptom]. Operation being measured, from boundary to boundary: [operation]. Baseline measurements including p50/p95/p99, dataset size and shape, environment, concurrency, and cache state: [numbers]. Evidence — profiler output, explain plan with actual row counts, trace spans, or phase timings: [paste]. Relevant code paths: [paths]. Target number and accepted cost in complexity, memory, staleness, or write performance: [target and price]. First, attribute the measured time across components and state clearly what the evidence does and does not show. Then list unknowns that require a measurement I have not provided. Then output: (1) ranked hypotheses for the dominant cost, each with expected magnitude of improvement and the specific measurement that would confirm or refute it, (2) the smallest change that addresses the top hypothesis, (3) what that change trades away, including write cost, memory, staleness, concurrency risk, and added complexity, (4) the exact re-measurement procedure so before and after are comparable, and (5) what else could regress and should be checked. Flag any suggestion whose benefit you cannot estimate from the evidence given, and do not propose optimizations to components that the evidence shows are not on the critical path. Do not write the patch yet."

Answer the unknowns with real measurements before asking for the patch. Then implement one change, rerun the identical measurement, and review the diff and the numbers together. A performance pull request that arrives without its before-and-after is asking the reviewer to take the improvement on faith.


Common Failure Modes

The first is optimizing without a baseline, which makes success unfalsifiable. The second is benchmarking on a development dataset small enough to hide the actual behavior. The third is reporting an average and shipping a worse tail. The fourth is bundling several optimizations into one diff so no individual change can be credited or reverted. The fifth is measuring a warm path once and calling it representative. The sixth is accepting an AI suggestion for a component that the profile never implicated. The seventh is fixing the number and never checking what the fix cost in memory, write throughput, correctness, or cloud spend.

The correction is the same sequence every time: name the symptom, baseline it reproducibly, locate the cost with an instrument, set a target and a price, change one thing, re-measure identically, and confirm the result in production with a guard against its return.


Conclusion

AI is a strong partner for performance work once the measurement exists. It reads explain plans quickly, recognizes algorithmic patterns, drafts benchmark harnesses, explains why a particular access path is expensive, and argues usefully about trade-offs. It can even help collect the evidence when it can run the relevant tools. What it cannot do is determine what is slow from source code alone, and a suggestion aimed at the wrong component is not a small error — it is work that adds complexity and improves nothing.

So keep the division of labor honest. The instruments say where the time goes. You decide what the number needs to be and what you will pay for it. AI helps you get there faster once both of those are written down.

Related reading

Continue with Debugging with AI, AI Observability, AI-Assisted Database Design, Why Small Diffs Win With AI, and The AI Verification Ladder.


Back to Home