Using timer() in Python — more precisely, the timeit module’s timeit() function — provides the most accurate and repeatable way to benchmark small code snippets or entire functions by running them many times (default 1,000,000) and measuring total elapsed wall-clock time. It disables garbage collection by default during timing, minimizes system interference, and gives reliable microsecond-level precision — ideal for comparing implementations, profiling bottlenecks, optimizing pandas/Polars operations, or validating performance in production code. In 2026, timeit remains the gold standard for micro-benchmarks — far more trustworthy than manual time.perf_counter() loops for short durations, and often combined with decorators or context managers for real-world function timing.
Here’s a complete, practical guide to using timeit (and its Timer class) in Python: basic timing, multiple runs, setup/teardown, function timing, real-world examples, and modern best practices with type hints, precision, pandas/Polars integration, and comparison to manual timing.
Basic timeit.timeit() — time a small snippet or callable; runs multiple times for statistical stability.
import timeit
def slow_sum(n: int) -> int:
return sum(range(n))
# Time the function (default 1,000,000 runs — too many for slow funcs; adjust number)
elapsed = timeit.timeit(lambda: slow_sum(1_000_000), number=10)
print(f"Average time per run: {elapsed / 10:.6f} seconds")
Using timeit.Timer — more control: setup code, repeat runs, precise statistics.
timer = timeit.Timer(
stmt="slow_sum(100_000)",
setup="from __main__ import slow_sum",
globals=globals() # optional, for accessing current scope
)
# Run 100 times and get statistics
result = timer.repeat(repeat=5, number=100)
print(f"Min time: {min(result):.6f} seconds")
print(f"Average time: {sum(result) / len(result):.6f} seconds")
Timing real pandas/Polars operations — use timeit to benchmark transformations reliably.
import pandas as pd
import polars as pl
import timeit
df_pd = pd.DataFrame({'A': range(1_000_000)})
df_pl = pl.DataFrame({'A': range(1_000_000)})
# Pandas benchmark
pd_time = timeit.timeit(lambda: df_pd['A'].mean(), number=100)
print(f"Pandas mean: {pd_time / 100:.6f} s/run")
# Polars benchmark
pl_time = timeit.timeit(lambda: df_pl['A'].mean(), number=100)
print(f"Polars mean: {pl_time / 100:.6f} s/run")
Best practices make timeit usage accurate, reproducible, and useful. Prefer timeit over manual perf_counter for micro-benchmarks — it disables GC, minimizes overhead, and averages many runs. Modern tip: use Polars lazy API — time .collect() or .sink_* calls to profile expensive execution. Run multiple repeats — repeat=5–10 — and take min/median to account for system noise. Use setup for imports/initialization — avoid measuring setup cost. Use number=100–1000 — balance precision vs runtime; avoid too few runs (noise) or too many (long wait). Time pure functions or isolated operations — avoid I/O or external state in timed code. Use autorange() — automatically finds good number for short snippets. Combine with decorators — @timer for real-world function timing; use timeit for micro-optimizations. Disable GC manually if needed — gc.disable()/gc.enable() — but timeit does it automatically. Test timing results — compare implementations fairly on same hardware. Use time.process_time() — for CPU-only timing if I/O/sleep should be excluded.
Using timeit (and Timer) gives accurate, repeatable function timings — disable GC, multiple runs, setup/teardown, min/median statistics. In 2026, prefer timeit for micro-benchmarks, decorators/context managers for real-world profiling, Polars lazy timing, and multiple repeats for reliability. Master timing with timeit, and you’ll measure, compare, and optimize Python code precisely and confidently.
Next time you need reliable speed measurement — use timeit. It’s Python’s cleanest way to say: “Tell me how fast this really is — accurately.”