Python · The language, not just syntax
How do decorators work and what are their pitfalls?
Short answer
A decorator is a callable that takes a function and returns a replacement, usually a wrapper that adds behaviour around the original call. functools.wraps preserves the wrapped function's metadata.
The @decorator syntax is sugar. Writing @log above def f is equivalent to f = log(f). Because the wrapper is a closure over the original function, it can run code before and after the call, retry it, cache results or register it.
import functools
import time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
elapsed = time.perf_counter() - start
print(func.__name__, round(elapsed, 4))
return wrapper
@timed
def work(n):
return sum(range(n))
work(1000)Variants and pitfalls
- Decorators with arguments need an extra outer function: @retry(times=3)
- Stacked decorators apply bottom-up: the closest decorator wraps first
- functools.lru_cache and cache are ready-made memoisation decorators
- Without wraps, introspection tools and debuggers see the wrapper signature
Decorators run at import time. A decorator that performs I/O, reads configuration or registers global state can make imports slow and order-dependent. They also obscure control flow, so use them for cross-cutting concerns such as logging, timing, authentication and caching rather than core business logic.
Follow-ups you should expect
Interviewers use these to check whether the first answer was understanding or memorisation.
- 1How would you write a decorator that takes arguments?
- 2In what order do stacked decorators execute?
- 3Why is functools.wraps important?
- 4What are the downsides of hiding logic in decorators?
Say it in the interview
A decorator is a callable that receives a function and returns a replacement. The @ syntax is just reassignment, and the replacement is usually a closure that adds behaviour before or after the call. I always use functools.wraps so names, docstrings and signatures survive. I keep decorators for cross-cutting concerns like timing, retries, caching and auth, because they run at import time and can hide control flow.
Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.
Related questions
Reading is step one
Now say it out loud.
Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live Python mock interview and get scored feedback on depth, clarity and confidence.