Measuring code execution time in this code
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Measuring execution time is easy to do badly and surprisingly hard to do well. A useful measurement depends on timing the right section of code, using an appropriate clock, and understanding whether you want a rough check, a comparison benchmark, or a full profile.
Start with a Simple Wall-Clock Measurement
For a quick answer, use a high-resolution monotonic timer. In Python, time.perf_counter() is the standard choice for this kind of timing.
This gives you an elapsed time in seconds for the code between the two calls. It is useful for quick local checks, but a single number should be treated as a rough sample rather than a final benchmark.
Time Only the Work You Care About
A very common mistake is measuring setup plus the real workload and then blaming the wrong part for the runtime. If you are benchmarking sorting, for example, generate the data before the timer starts.
If list creation stayed inside the timed block, the number would be less useful because it would mix data generation with sorting cost.
Wrap Code in Functions for Repeatable Tests
As soon as timing becomes more than a quick sanity check, put the code under test inside a function. That makes the benchmark easier to read and easier to compare against alternatives.
This keeps the comparison explicit and reduces the chance that inline benchmark code becomes confusing.
Use timeit for Comparisons
If the goal is to compare two implementations fairly, timeit is usually better than timing a single run by hand. It repeats the code many times and reduces some of the noise from background activity.
Repeated timings are more informative than one sample. The fastest run is often a better representation of the real cost because it is the least affected by unrelated system activity.
Measuring Async Code Correctly
Async code has one extra trap: creating a coroutine is not the same thing as running it. You must time the awaited execution.
If you stop the timer before await, the result measures almost nothing and tells you nothing useful about the real workload.
Profiling Helps When You Do Not Know the Bottleneck
Sometimes the question is not "how long does this one snippet take" but "why is the whole program slow". In that case, profiling is usually more useful than hand timing guesses.
Profilers help identify where the cumulative time actually goes. That can prevent wasted effort on code that only looked suspicious.
Keep Context with the Number
A timing value without context is not very informative. Good measurements usually include at least the input size, the environment, and the number of repetitions. If a benchmark changed after a refactor, you want enough context to explain whether the code, the data, or the machine changed.
That is also why tiny performance differences should be treated carefully. If two versions differ by a few microseconds in a noisy environment, the result may not matter or even be real.
Common Pitfalls
The most common mistake is trusting one run. Timings fluctuate, and a single sample can be misleading.
Another issue is including setup work inside the measured block. Imports, random-data generation, and file reading can dominate the number and hide the behavior you actually wanted to inspect.
Developers also sometimes optimize a microbenchmark that does not affect real application performance. Faster isolated code is only valuable if it matters in production.
Finally, avoid using time.time() for benchmarking when time.perf_counter() is available. The latter is designed for high-resolution elapsed-time measurements.
Summary
- Use
time.perf_counter()for quick elapsed-time measurements. - Time only the code you actually want to evaluate.
- Wrap benchmark targets in functions for readable comparisons.
- Use
timeitwhen comparing alternative implementations. - Profile the full program when the true bottleneck is unknown.

