Skip to content
Python Basics: Measuring Small Operations with timeit

Python Basics: Measuring Small Operations with timeit

October 13, 2026

Before replacing readable code with a faster-looking alternative, measure a small, representative operation. Python’s standard-library timeit module helps you repeat that operation without writing your own stopwatch loop.

Before you start

These independent examples were tested on Linux with Bash, Python 3.12.14 and Python 3.13.5. No packages or environment variables are required. The commands use python3; substitute python3.13 to test that interpreter.

In one Bash session, create a fresh working directory:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Save each source block in this directory using its stated filename. Avoid timeit.py, which would shadow the standard-library module. These programs print results without writing data files.

Time a callable

Save as demo_timeit_1.py:

import timeit

words = ["pear", "apple", "plum"] * 100

def sort_words():
    return sorted(words)

loops = 2_000
elapsed = timeit.timeit(sort_words, number=loops)
print("Runs:", loops)
print(f"Total seconds: {elapsed:.6f}")
print(f"Microseconds per run: {elapsed / loops * 1_000_000:.3f}")
print("Original first word:", words[0])

Run it:

python3 demo_timeit_1.py

Example output from Linux; timing values vary with the machine, interpreter, and other running programs:

Runs: 2000
Total seconds: 0.020075
Microseconds per run: 10.038
Original first word: pear

number counts executions. The returned seconds cover the whole batch; divide by the count to get time per execution. Passing sort_words schedules calls, while passing sort_words() would run it immediately and pass its result. sorted() leaves the input unchanged, so each run receives the same word order.

Repeat a comparison

Save as demo_timeit_2.py:

import timeit

items = list(range(1_000))
lookup = set(items)
target = 999
loops = 10_000

assert (target in items) == (target in lookup)
for label, collection in [("list", items), ("set", lookup)]:
    timer = timeit.Timer(
        "target in collection",
        globals={"target": target, "collection": collection},
    )
    samples = timer.repeat(repeat=3, number=loops)
    best_us = min(samples) / loops * 1_000_000
    print(f"{label}: {len(samples)} batches, best {best_us:.3f} us/run")

Run it:

python3 demo_timeit_2.py

Example output; your timing values will differ:

list: 3 batches, best 4.758 us/run
set: 3 batches, best 0.019 us/run

globals supplies names used by the timed expression. Each sample is a batch total. The smallest sample reduces the effect of interruptions; it is not a typical application latency. Only membership lookup is timed here, excluding collection construction. Try a missing target or a smaller collection before applying the result to your own workload.

Check repeated side effects

Save as demo_timeit_3.py:

import timeit

pending = ["first", "second"]
timer = timeit.Timer(pending.pop)
try:
    timer.timeit(number=3)
except IndexError:
    print("Expected error: the third pop found an empty list")
print("Remaining items:", pending)

def pop_from_fresh_data():
    values = ["first", "second"]
    return values.pop()

elapsed = timeit.timeit(pop_from_fresh_data, number=3)
print("Fresh-data runs completed:", 3)
print("Timing is nonnegative:", elapsed >= 0)

Run it:

python3 demo_timeit_3.py

Output, including an intentionally caught error:

Expected error: the third pop found an empty list
Remaining items: []
Fresh-data runs completed: 3
Timing is nonnegative: True

Repeated execution also repeats mutations. The second callable creates data on every run, so its measurement includes that creation. A setup statement would run once per batch, outside the measured interval, rather than before every execution.

Keep the measurement narrow

By default, timeit temporarily disables cyclic garbage collection. That can omit an important cost for allocation-heavy programs. Keep network calls, file changes, and other irreversible work out of a microbenchmark. Use timings to investigate a specific operation, then test the full application separately.

After the examples, remove the saved files and empty working directory in the same Bash session:

rm -- demo_timeit_1.py demo_timeit_2.py demo_timeit_3.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

References

Acknowledgements

This post was created with assistance from AI.

Last updated on