Python

Verified against Claude Code · 2026-07-27

Find out why Python code is actually slow before optimizing it

A profiling-first prompt that requires naming the real bottleneck category with a specific tool and command before suggesting any optimization, instead of guessing at generic speedups that may not touch the actual problem.

Claude CodeChatGPT (GPT-5.1)Claude (Sonnet 4.6)Cursor 2.14 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

This code is slow. Before you suggest any optimization, help me find out why — an optimization based on a guess is as likely to make no difference as to help, and can waste more engineering time than the slowness itself.

CODE
A function that dedupes a 500k-row list by checking membership against a growing Python list.

SYMPTOM
Takes 40+ seconds on 500k rows; was fine at 10k rows a few months ago.

INPUT SCALE
Currently 500k rows, expected to reach 2-3M rows within the next two quarters.

CONSTRAINTS ON THE FIX
Must stay single-threaded (runs inside a Celery worker with its own concurrency model already).

PROCESS
1. Classify the likely bottleneck category: CPU-bound (the computation itself is slow), I/O-bound (waiting on network, disk, or a database), memory-bound (excessive allocation, GC pressure, or swapping), or algorithmic (the wrong Big-O for the input size). State which you suspect and the specific line or pattern that makes you suspect it, and check that hypothesis against Currently 500k rows, expected to reach 2-3M rows within the next two quarters. — a fix that only helps at 10x the current scale is a different priority than one needed right now.
2. Name the exact profiling step to confirm it before changing anything: cProfile or snakeviz for CPU hotspots, py-spy for profiling a running process without modifying code, memory_profiler or tracemalloc for memory, line_profiler for line-by-line cost inside one function. Give the actual command to run, not just the tool's name.
3. Once the bottleneck is confirmed, propose the fix ranked by likely impact per effort — an algorithmic fix that changes O(n squared) to O(n) ranks above micro-optimizing a loop's variable access, and should be presented first even if it's more work to implement.
4. For any fix that trades memory for speed, or changes behavior under concurrency, say so explicitly, and check it against Must stay single-threaded (runs inside a Celery worker with its own concurrency model already)..
5. Record a concrete before number from Takes 40+ seconds on 500k rows; was fine at 10k rows a few months ago. as the baseline — not a vague "it's slow," an actual wall-clock time or throughput figure — so the fix has something specific to beat rather than a moving target.
6. Note where the profiling tool itself might distort the result: cProfile's per-call instrumentation overhead can make a function relatively look slower than it actually is in production if it's called an extreme number of times, so say explicitly if that risk applies here and whether py-spy's sampling approach (near-zero overhead) would give a more trustworthy picture instead.
7. State how to verify the fix actually helped: re-run the same profiling step and compare numbers against the baseline from step 5, not "this should be faster" — a fix that isn't measured against a real before number isn't confirmed, it's assumed.

OUTPUT FORMAT
Bottleneck hypothesis, then the profiling command to confirm it, then the baseline number, then a ranked fix list, then the verification step with the after number compared against the baseline.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

Forcing a bottleneck classification before any fix counters the default behavior of proposing generic micro-optimizations — swapping a for loop for a list comprehension, say — that often don't touch the actual bottleneck at all and can leave the real problem completely unaddressed while looking like productive work was done. Naming specific real tools with their actual invocation (cProfile's command-line module flag, py-spy for a running process with zero code changes, line_profiler for per-line cost) turns "profile your code" from an aspiration into an executable next step that produces real numbers instead of another guess. The input_scale field matters because the right fix genuinely depends on where the code sits on its growth curve: an O(n squared) algorithm that's merely annoying at 500k rows becomes an operational emergency at 3M rows, so naming the expected future scale changes whether a fix is nice-to-have or urgent, and changes which of two competing fixes actually deserves the engineering time first. Ranking fixes by algorithmic impact before micro-optimization reflects the real order-of-magnitude difference at scale: an O(n squared) membership check against a growing list, fixed by switching to a set, changes a 40-second run into a sub-second one, which no amount of loop micro-tuning would ever reach — and naming that ranking explicitly stops the model from leading with the least impactful fix just because it's the easiest one to describe in a paragraph. Requiring a concrete baseline number before proposing anything, and comparing the after number against that same baseline rather than a fresh vague impression, is what turns "this should be faster" into an actual falsifiable claim — profiling tools produce numbers precisely so a fix can be judged against reality instead of against how convincing the explanation sounds. Flagging cProfile's own instrumentation overhead matters because it's a specific, well-documented distortion: cProfile adds real per-call cost to every function it tracks, which can make a function called millions of times inside a hot loop appear disproportionately expensive relative to its true production cost, so a profiling-first workflow that doesn't account for this can end up confidently optimizing the wrong function — exactly the failure mode this whole prompt exists to prevent, just relocated one step later into the profiling tool itself instead of the original guess.

What you get back

Bottleneck hypothesis: algorithmic — "if item not in seen_list" inside the loop is an O(n) scan against a growing Python list, making the whole dedupe O(n squared). Confirm with: python -m cProfile -s cumulative dedupe_script.py — expect the membership-check line to dominate cumulative time at this input size. Fix, ranked: 1) swap seen_list for a set() — O(1) average membership check, changes the loop to O(n) overall, and matters more urgently given the 2-3M row target within two quarters. 2) (lower impact) avoid re-hashing unhashable row objects by deduping on a derived key instead. Verify: re-run the same cProfile command on the 500k-row input and confirm total runtime drops from ~40s to sub-second, not just "looks faster."

Verified against

Claude Code Sonnet 4.6 · 2026-07-27

Changelog

  • 2026-07-27 Initial publish, verified against Claude Code (Sonnet 4.6) on Python 3.12 with cProfile and py-spy.

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All Python prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY