Start with timeit for micro-benchmarks, cProfile for call-level hotspots, line_profiler for per-line detail
Pick the tool based on scope. For a small piece of code, timeit gives repeatable timings with minimal setup. For a whole program or function, cProfile tells you where time is spent across calls; sort by cumulative time to find the expensive subtree and by tottime to find the function doing the real work. py-spy attaches to a running process without restarting it, which is invaluable in production. For line-level detail inside one hot function, line_profiler tells you exactly which line dominates. The interpretation rules matter: ncalls and tottime point to the culprit, cumtime identifies the subtree, and a high number of primitive calls often means an algorithmic problem rather than a constant-factor problem.
timeit: micro-benchmarks; use the command line form for a fair comparison across variants.
cProfile: call-level profiling; run python -m cProfile -s cumtime script.py, or profile programmatically.
py-spy: sampling profiler that attaches to a live process and works in production without code changes.
line_profiler: per-line detail for one function once you already know where to look.
memory_profiler and tracemalloc for memory, as a separate axis from time.
Common mistake: profiling with a debugger attached or with print statements in the loop, which distorts results.
Common mistake: optimizing the top function by tottime when the real cost is in a call it makes. Check cumtime before you refactor.
Version note: cProfile has been in the standard library since Python 2.5. py-spy is third-party and works across 3.x versions.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience