The timeit module provides accurate timing by running code multiple times and disabling garbage collection. Essential for comparing implementations and optimizing hot paths.

Command Line Usage

# Time a simple expression
python -m timeit '"-".join(str(n) for n in range(100))'
# 10000 loops, best of 5: 25.3 usec per loop
 
# Time with setup
python -m timeit -s 'import random' 'random.random()'
# 5000000 loops, best of 5: 50.4 nsec per loop
 
# Specify number of executions
python -m timeit -n 1000000 'x = 1 + 1'

Basic Python Usage

import timeit
 
# Time a string statement
time = timeit.timeit('x = sum(range(100))', number=100000)
print(f"Total: {time:.3f}s")
print(f"Per call: {time/100000*1e6:.2f}µs")
 
# With setup code
time = timeit.timeit(
    stmt='random.random()',
    setup='import random',
    number=1000000
)

Timing Functions

import timeit
 
def my_function():
    return sum(range(100))
 
# Time a callable
time = timeit.timeit(my_function, number=100000)
print(f"{time/100000*1e6:.2f}µs per call")
 
# Or use lambda
time = timeit.timeit(lambda: sum(range(100)), number=100000)

repeat() for Statistical Accuracy

import timeit
 
# Run multiple trials
times = timeit.repeat(
    stmt='"-".join(map(str, range(100)))',
    setup='',
    repeat=5,    # Number of trials
    number=10000 # Executions per trial
)
 
print(f"Best: {min(times)/10000*1e6:.2f}µs")
print(f"Worst: {max(times)/10000*1e6:.2f}µs")
print(f"Mean: {sum(times)/len(times)/10000*1e6:.2f}µs")

Timer Class

import timeit
 
# Create reusable timer
timer = timeit.Timer(
    stmt='sorted(data)',
    setup='import random; data = [random.random() for _ in range(1000)]'
)
 
# Auto-determine number of runs
number, time = timer.autorange()
print(f"{number} loops, {time/number*1e6:.2f}µs per loop")
 
# Run specific number
times = timer.repeat(repeat=3, number=1000)

Comparing Implementations

import timeit
 
# List comprehension vs map
setup = 'data = list(range(1000))'
 
list_comp = timeit.timeit(
    '[x*2 for x in data]',
    setup=setup,
    number=10000
)
 
map_func = timeit.timeit(
    'list(map(lambda x: x*2, data))',
    setup=setup,
    number=10000
)
 
print(f"List comp: {list_comp:.3f}s")
print(f"Map: {map_func:.3f}s")
print(f"Winner: {'List comp' if list_comp < map_func else 'Map'}")

Passing Local Variables

import timeit
 
def benchmark_sort(data_size):
    setup = f'import random; data = [random.random() for _ in range({data_size})]'
    
    time = timeit.timeit('sorted(data)', setup=setup, number=100)
    return time / 100
 
# Or use globals
data = list(range(1000))
time = timeit.timeit('sorted(data)', globals=globals(), number=1000)

Decorator for Quick Timing

import timeit
from functools import wraps
 
def benchmark(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        timer = timeit.Timer(lambda: func(*args, **kwargs))
        n, total = timer.autorange()
        print(f"{func.__name__}: {total/n*1e6:.2f}µs ({n} loops)")
        return func(*args, **kwargs)
    return wrapper
 
@benchmark
def my_function(n):
    return sum(range(n))
 
my_function(1000)

Context Manager Timing

import timeit
from contextlib import contextmanager
 
@contextmanager
def timed(label="Block"):
    timer = timeit.default_timer
    start = timer()
    try:
        yield
    finally:
        elapsed = timer() - start
        print(f"{label}: {elapsed*1000:.2f}ms")
 
with timed("Sort operation"):
    sorted(range(100000, 0, -1))

Comparing Data Structures

import timeit
 
def compare_lookup():
    setup_list = 'data = list(range(10000))'
    setup_set = 'data = set(range(10000))'
    setup_dict = 'data = {i: i for i in range(10000)}'
    
    list_time = timeit.timeit('9999 in data', setup=setup_list, number=100000)
    set_time = timeit.timeit('9999 in data', setup=setup_set, number=100000)
    dict_time = timeit.timeit('9999 in data', setup=setup_dict, number=100000)
    
    print(f"List lookup: {list_time:.4f}s")
    print(f"Set lookup:  {set_time:.4f}s")
    print(f"Dict lookup: {dict_time:.4f}s")
 
compare_lookup()
# List lookup: 0.8234s
# Set lookup:  0.0089s  <- O(1) vs O(n)
# Dict lookup: 0.0091s

String Concatenation

import timeit
 
n = 10000
 
# Plus operator
plus = timeit.timeit(
    's = ""; exec("for i in range(100): s += str(i)")',
    number=n
)
 
# Join
join = timeit.timeit(
    '"".join(str(i) for i in range(100))',
    number=n
)
 
# F-string (fixed content)
fstring = timeit.timeit(
    'f"{1}{2}{3}{4}{5}"',
    number=n
)
 
print(f"Plus: {plus:.3f}s")
print(f"Join: {join:.3f}s")
print(f"F-string: {fstring:.3f}s")

Avoiding Pitfalls

import timeit
 
# BAD: Including import in timing
bad = timeit.timeit('import json; json.dumps({"a": 1})', number=10000)
 
# GOOD: Setup imports separately
good = timeit.timeit(
    'json.dumps({"a": 1})',
    setup='import json',
    number=10000
)
 
# BAD: Not enough iterations
unreliable = timeit.timeit('x = 1 + 1', number=1)
 
# GOOD: Let autorange decide
timer = timeit.Timer('x = 1 + 1')
n, _ = timer.autorange()

Multiline Statements

import timeit
 
code = '''
result = []
for i in range(100):
    if i % 2 == 0:
        result.append(i * 2)
'''
 
time = timeit.timeit(code, number=10000)
print(f"Loop: {time:.3f}s")
 
# Compare to comprehension
comp = timeit.timeit(
    '[i * 2 for i in range(100) if i % 2 == 0]',
    number=10000
)
print(f"Comprehension: {comp:.3f}s")

CLI Options

# Basic timing
python -m timeit 'stmt'
 
# With setup
python -m timeit -s 'setup' 'stmt'
 
# Specify iterations
python -m timeit -n 1000 'stmt'
 
# Specify repeats
python -m timeit -r 5 'stmt'
 
# Time in seconds (vs per-loop)
python -m timeit -u sec 'stmt'
 
# Verbose output
python -m timeit -v 'stmt'

Best Practices

DoDon't
Use setup for importsInclude imports in stmt
Run many iterationsTime single execution
Use repeat() for varianceTrust single measurement
Compare relative timesFocus on absolute times
Test realistic data sizesBenchmark edge cases only
Disable GC (default)Benchmark with GC artifacts

When to Use What

# Quick check: timeit CLI
# python -m timeit 'code'
 
# Script comparison: timeit.timeit()
timeit.timeit('code', number=10000)
 
# Statistical analysis: timeit.repeat()
timeit.repeat('code', repeat=5, number=10000)
 
# Auto-scaling: Timer.autorange()
timer = timeit.Timer('code')
timer.autorange()

timeit gives you reliable microbenchmarks. Use it to compare implementations before optimizing.

React to this post: