# The Timing Decorator

> Wrap any function to capture how long it takes.

Canonical URL: <https://datadriven.io/problems/the_timing_decorator>

Domain: Python · Difficulty: medium · Seniority: L4

## Problem

Implement a @timer decorator that wraps a function and prints its execution time. The decorator must support any args and kwargs. The test harness receives a list of function calls (each ['name', args, kwargs]) and expects a list of booleans indicating the timer worked.

## Worked solution and explanation

### Why this problem exists in real interviews

This tests **decorator pattern**, **closures**, and **`*args`/`**kwargs` forwarding**. Decorators are a Python-specific feature that probes understanding of higher-order functions and function wrapping.

---

### Break down the requirements

#### Step 1: Define the outer decorator function

The decorator takes a function as its argument and returns a wrapper.

#### Step 2: Define the inner wrapper that accepts any arguments

Use `*args, **kwargs` to forward all positional and keyword arguments to the wrapped function.

#### Step 3: Measure elapsed time around the function call

Record `time.time()` before and after calling the wrapped function. Print the difference.

#### Step 4: Return the original function's result

The wrapper must return whatever the wrapped function returns, preserving the call contract.

---

### The solution

**Closure-based timing wrapper with functools.wraps**

```python
import time
import functools
def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"{func.__name__} took {elapsed:.4f}s")
        return result
    return wrapper
@timer
def demo():
    total = 0
    for i in range(1000000):
        total += i
    return total
```

> **Time and Space Complexity**
>
> **Time:** O(1) overhead from the timing wrapper itself. The measured function's complexity is unchanged.
> 
> **Space:** O(1) for the closure. No additional data structures.

> **Interviewers Watch For**
>
> `functools.wraps(func)` preserves the wrapped function's `__name__`, `__doc__`, and other metadata. Omitting it makes debugging harder since `wrapper` replaces the original function's identity.

> **Common Pitfall**
>
> Forgetting to return the result of `func(*args, **kwargs)`. Without `return result`, the decorated function always returns `None`, silently breaking callers.

---

## Common follow-up questions

- How would you make the decorator accept parameters, like a minimum threshold? _(Tests the triple-nested function pattern: `def timer(threshold)` returning the actual decorator.)_
- How would you log timing to a file instead of printing? _(Tests parameterizing the output destination.)_
- How does this interact with async functions? _(Tests knowledge of `async def wrapper` and `await func(...)` for async-compatible decorators.)_
- What are the limitations of `time.time()` for benchmarking? _(Tests awareness of `time.perf_counter()` for higher resolution and monotonic guarantees.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/the_timing_decorator)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). 100% free data engineering interview prep. Live code execution against Postgres 16, Python 3.11, and Spark sandboxes. No paywall, no premium tier, no signup gate.