How do I measure which part of my Python code is slow?
Short answer
Use timeit to compare small snippets accurately and cProfile to find which functions dominate a whole program. Measure before optimising, because the slow part is often not the one you expect.
The short version
Use timeit to compare small snippets fairly, because it runs them many times and avoids common timing mistakes. Use cProfile to find which functions take the most time in a whole program. The golden rule is to measure first, because the actual bottleneck is frequently not the code you assumed, and optimising the wrong part wastes effort.
Timing a small snippet with timeit
import timeit
t = timeit.timeit("sum(range(100))", number=10000)
print(t) # total seconds for 10000 runs
timeit runs the code many times and reports the total, which smooths out the noise that makes a single measurement unreliable. It also avoids pitfalls like including one-off setup in the timing.
Comparing two approaches
The common use is deciding which of two implementations is faster.
loop = timeit.timeit("[x*x for x in range(1000)]", number=1000)
built = timeit.timeit("list(map(lambda x: x*x, range(1000)))", number=1000)
Run both and compare. Measuring settles these arguments far better than intuition, which is often wrong about what is faster.
Profiling a whole program with cProfile
For a real program, you want to know which functions dominate, not to time snippets by hand. cProfile runs your code and reports time per function.
import cProfile
cProfile.run("main()", sort="cumulative")
The output lists each function with how many times it was called and how much time it took, sorted so the biggest costs are at the top. This points you straight at the few functions worth optimising.
Reading the profile
Focus on cumulative time (time in a function and everything it calls) to find the expensive branches, and on the call count to spot functions called far more often than expected. A function taking most of the time, or being called millions of times, is your target. Everything else is usually not worth touching.
Measure before you optimise
The most common mistake is guessing where the time goes and rewriting that part, only to find it was never the bottleneck. Programs typically spend most of their time in a small fraction of the code, and it is rarely the part that looks complex. Profile first, fix the real hot spot, then measure again to confirm the change actually helped.
In notebooks
Jupyter offers convenient magics: %timeit for a single line, %%timeit for a whole cell, and %prun to profile a statement. These wrap the same tools with less typing and are the quickest way to check a suspicion while exploring.
Do not optimise prematurely
Only profile and optimise code that is actually too slow for its purpose. Clear, correct code is worth more than fast code that is never the bottleneck, so reserve this effort for the parts that measurement shows genuinely matter.