Python
Time Module
Elapsed Time
Programming
Coding Techniques

Measuring elapsed time with the Time module

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Measuring elapsed time in Python is straightforward, but the best function depends on what you are actually measuring. If you care about wall-clock duration, short benchmark precision, or CPU-only work, the time module gives different tools for each case. Picking the right clock matters more than most examples suggest.

Basic Elapsed Time with time.time()

The simplest approach is to capture the current Unix timestamp before and after some work, then subtract the two values.

python
1import time
2
3start = time.time()
4
5time.sleep(1.2)
6
7elapsed = time.time() - start
8print(f"Elapsed: {elapsed:.3f} seconds")

This works well for rough timing, logging, and user-visible duration reporting. It measures wall-clock time, which means it includes sleep, waiting on I/O, and time when your process is not actively using the CPU.

Better Precision with time.perf_counter()

For benchmarking or measuring short code paths, time.perf_counter() is usually the better choice. It provides a high-resolution timer intended specifically for performance measurements.

python
1import time
2
3def compute_total():
4    return sum(i * i for i in range(100_000))
5
6start = time.perf_counter()
7compute_total()
8elapsed = time.perf_counter() - start
9
10print(f"High-resolution elapsed time: {elapsed:.6f} seconds")

If you are comparing two implementations, perf_counter should usually be your default. It still measures elapsed real time, but with higher precision and a more stable clock source for benchmarking.

Measuring CPU Time with time.process_time()

Sometimes you do not care about waiting time. You only want to know how much CPU time the process actually consumed. That is what time.process_time() is for.

python
1import time
2
3start = time.process_time()
4
5total = 0
6for i in range(5_000_000):
7    total += i
8
9cpu_elapsed = time.process_time() - start
10print(f"CPU time: {cpu_elapsed:.6f} seconds")

Unlike time.time() and time.perf_counter(), this excludes time spent sleeping or waiting for external resources. That makes it useful for CPU-bound profiling.

A Small Timing Helper

If you are timing multiple code paths, wrapping the logic in a helper keeps the code cleaner.

python
1import time
2from collections.abc import Callable
3from typing import Any
4
5def measure(func: Callable[..., Any], *args: Any, **kwargs: Any) -> tuple[Any, float]:
6    start = time.perf_counter()
7    result = func(*args, **kwargs)
8    elapsed = time.perf_counter() - start
9    return result, elapsed
10
11def slow_addition(limit: int) -> int:
12    return sum(range(limit))
13
14value, seconds = measure(slow_addition, 1_000_000)
15print(value)
16print(f"Took {seconds:.6f} seconds")

This keeps your timing code consistent and makes it easier to swap timer functions later.

Repeated Benchmarks

Single measurements can be misleading because of caching, interpreter warmup, background processes, and input variance. If you want a more realistic benchmark, time the operation several times.

python
1import time
2
3def work():
4    return sum(i * i for i in range(50_000))
5
6durations = []
7
8for _ in range(5):
9    start = time.perf_counter()
10    work()
11    durations.append(time.perf_counter() - start)
12
13print("Fastest:", min(durations))
14print("Average:", sum(durations) / len(durations))

This is still lightweight, but it produces a more defensible result than a single run.

When to Use timeit Instead

The time module is excellent for application code, quick diagnostics, and custom timing helpers. For serious micro-benchmarking of tiny snippets, the timeit module is often better because it automates repetition and reduces measurement noise.

Still, understanding the clocks in time is important because they are what many production systems use for logging and metrics.

Common Pitfalls

One frequent mistake is benchmarking with time.time() and assuming it is the highest precision option. It is fine for rough timing, but time.perf_counter() is usually the better fit for performance measurements.

Another problem is comparing wall-clock time to CPU time without realizing they measure different things. A function that waits on network I/O may show large elapsed time with perf_counter but very little CPU time with process_time.

Developers also sometimes leave print statements, logging, or setup work inside the measured region. That distorts the result and makes comparisons harder to trust.

Finally, avoid drawing conclusions from a single run. Timings naturally vary, especially on busy machines or in interactive environments such as notebooks.

Summary

  • Use time.time() for simple wall-clock duration measurement.
  • Use time.perf_counter() for high-resolution elapsed timing and most benchmarks.
  • Use time.process_time() when you care only about CPU consumption.
  • Repeat measurements when comparing implementations.
  • Keep setup and unrelated work outside the timed region so results stay meaningful.

Course illustration
Course illustration

All Rights Reserved.