Skip to main content
🛡️ Verified Technical Content: Written by Serhii Hrekov. | Last reviewed & updated in Git: July 21, 2026

How to Measure Execution Time in Python: Vanilla Python, FastAPI, Flask, and Django

· 8 min read
Serhii Hrekov
Senior Software Engineer & System Architect specializing in Python, Web Systems, Cloud Infrastructure & Automation

Measuring how long your Python code takes to run is a critical skill for performance optimization, benchmarking, and identifying bottlenecks in production applications. Depending on whether you are analyzing a single micro-operation, profiling a complex function, or tracking HTTP request-response lifecycles, Python offers a wide variety of tools.

This guide provides a comprehensive breakdown of how to measure execution time in vanilla Python, followed by specialized implementations for the three major web frameworks: FastAPI, Flask, and Django.


Measuring Execution Time in Vanilla Python

1. High-Precision Timing with time.perf_counter()

For measuring execution time of a code block or function, use time.perf_counter(). It uses a monotonic clock with the highest available resolution on your system, unaffected by system clock adjustments.

import time

def slow_calculation():
return sum(range(10**7))

start = time.perf_counter()
slow_calculation()
end = time.perf_counter()

elapsed = end - start
print(f"Executed in: {elapsed:.6f} seconds")

Note: Avoid time.time() for performance benchmarking, as it is low-resolution and can be affected by system clock sync operations.

2. Measuring CPU-Only Time with time.process_time()

If your code spends a lot of time waiting for I/O (like database queries or external API calls) and you only want to measure the active CPU time consumed by your process:

start = time.process_time()
# CPU-heavy operations here
end = time.process_time()

cpu_time = end - start
print(f"Active CPU Time: {cpu_time:.6f} seconds")

3. Reusable Timer Context Manager

A custom context manager is a clean, Pythonic way to time arbitrary blocks of code without writing boilerplate code repeatedly:

import time

class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.perf_counter()
self.elapsed = self.end - self.start
print(f"Elapsed time: {self.elapsed:.6f} seconds")

# Usage:
with Timer():
# Code to measure
_ = [i ** 2 for i in range(10**6)]

4. Micro-benchmarking with the timeit Module

To compare different micro-operations (e.g. list comprehension vs. map), use the timeit module. It runs the code thousands of times to get statistically accurate averages and automatically turns off garbage collection to avoid noise.

import timeit

# Code snippet to run
setup_code = "import math"
test_code = "math.sqrt(25)"

# Get execution time for 100,000 runs
elapsed = timeit.timeit(stmt=test_code, setup=setup_code, number=100000)
print(f"Average time per run: {elapsed / 100000:.8f} seconds")

Measuring Execution Time in FastAPI

FastAPI handles asynchronous code natively. You can measure execution time globally using middleware or selectively using async decorators.

1. Global Performance Logging (HTTP Middleware)

To track the response time of every API request and append it as a custom response header:

import time
from fastapi import FastAPI, Request

app = FastAPI()

@app.middleware("http")
async def log_execution_time(request: Request, call_next):
start_time = time.perf_counter()
response = await call_next(request)
end_time = time.perf_counter()

execution_time_ms = (end_time - start_time) * 1000
response.headers["X-Process-Time-Ms"] = f"{execution_time_ms:.2f}"

print(f"Request: {request.method} {request.url.path} took {execution_time_ms:.2f}ms")
return response

2. Async Endpoint Decorator

For timing specific asynchronous endpoints without altering their core code:

import functools

def measure_async_time(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
start = time.perf_counter()
result = await func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} took {(end - start) * 1000:.2f}ms")
return result
return wrapper

@app.get("/items/{item_id}")
@measure_async_time
async def read_item(item_id: int):
return {"id": item_id}

Measuring Execution Time in Flask

In Flask, you can intercept the request cycle using application hooks or custom decorators.

1. Global Timing with before_request and after_request

Flask provides g as a context-local storage object bound to the active request. You can store the start time on g and retrieve it when sending the response:

from flask import Flask, g, request

app = Flask(__name__)

@app.before_request
def start_timer():
g.start_time = time.perf_counter()

@app.after_request
def log_timer(response):
if hasattr(g, 'start_time'):
elapsed = (time.perf_counter() - g.start_time) * 1000
response.headers["X-Process-Time-Ms"] = f"{elapsed:.2f}"
print(f"Flask path '{request.path}' took {elapsed:.2f}ms")
return response

2. Endpoint Decorator

import functools

def measure_flask_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} view took {(end - start) * 1000:.2f}ms")
return result
return wrapper

@app.route("/hello")
@measure_flask_time
def hello():
return "Hello World!"

Measuring Execution Time in Django

In Django, custom middleware classes wrap the entire request-response process.

1. Global Django Middleware

Create a file named middleware.py inside your app directory:

import time

class ExecutionTimeMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
start = time.perf_counter()
response = self.get_response(request)
end = time.perf_counter()

elapsed = (end - start) * 1000
response["X-Process-Time-Ms"] = f"{elapsed:.2f}"
print(f"Django request to '{request.path}' took {elapsed:.2f}ms")
return response

Add your middleware to the MIDDLEWARE list in settings.py:

# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# ...
'your_app.middleware.ExecutionTimeMiddleware',
]

2. View Decorator

import functools

def measure_django_view_time(view_func):
@functools.wraps(view_func)
def wrapper(request, *args, **kwargs):
start = time.perf_counter()
response = view_func(request, *args, **kwargs)
end = time.perf_counter()
print(f"View '{view_func.__name__}' ran in {(end - start) * 1000:.2f}ms")
return response
return wrapper

Sources