Optimization starts with a user-visible or operational problem and a measurement that reproduces it. Complexity, I/O, allocation, database calls, and serialization often matter more than shortening one expression.
After this lesson, you can separate benchmarking from profiling, measure a focused operation with timeit, locate cumulative runtime with cProfile, inspect allocations with tracemalloc, and verify that a proposed change improves the complete workload without changing behavior.
timeit repeats a small statement with timer setup designed for benchmarking. Benchmark complete alternatives with representative data and avoid claiming universal speed from one machine or one tiny input.
from timeit import repeat
values = list(range(10_000))
samples = repeat("9_999 in values", globals=globals(), number=1_000, repeat=5)
print(len(samples))
print(all(sample >= 0 for sample in samples))
5
True
Actual durations depend on hardware and runtime, so the tutorial verifies the measurement shape rather than inventing a timing result.
cProfile records call counts and time spent in functions. Sort by cumulative time to find call paths that dominate the workload; the profiler adds overhead and is not a replacement for a controlled benchmark.
python -m cProfile -o profile.stats app.py
python -m pstats profile.stats
Inside pstats, use sort cumulative and stats to inspect the most expensive call paths.
Changing repeated linear membership checks from a list to a set can alter complexity more than a syntax-level rewrite. Confirm that the new data structure preserves ordering, duplicates, memory use, and mutation behavior required by the feature.
| Operation | Typical Choice |
|---|---|
| Repeated membership | set or dict |
| FIFO ends | collections.deque |
| Ordered mutable sequence | list |
| Lazy transform pipeline | generator or itertools |
tracemalloc tracks Python memory allocations and can compare snapshots around a workload. It does not account for every native allocation made by extension libraries, so pair it with process-level memory evidence when needed.
import tracemalloc
tracemalloc.start()
before = tracemalloc.take_snapshot()
values = [str(number) for number in range(1_000)]
after = tracemalloc.take_snapshot()
changes = after.compare_to(before, "lineno")
print(bool(changes))
True
0 of 2 checked
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.