Python Decorators — How They Work and How to Write Them
The Interview Question
How do decorators work in Python? Can you write one from scratch?
Expert Answer
A decorator is a function that takes a function as input and returns a new function that usually extends the original's behavior. The @decorator syntax is syntactic sugar — @my_decorator above a function definition is equivalent to func = my_decorator(func). Under the hood, decorators work because functions are first-class objects in Python — they can be passed as arguments, returned from other functions, and assigned to variables. The standard decorator pattern uses a wrapper function with *args and **kwargs to handle any argument signature, calls the original function inside, and returns the result. Use functools.wraps to preserve the original function's name, docstring, and other metadata — without it, debugging and introspection break because the function appears to be the wrapper instead of itself.
Key Points to Hit in Your Answer
- @decorator is sugar for func = decorator(func)
- Always use @functools.wraps to preserve function metadata
- Decorators with arguments need an extra layer of nesting (decorator factory)
- Common patterns: logging, timing, authentication, caching, retry logic
- Class-based decorators use __call__ for instances that act as functions
- functools.lru_cache is the built-in memoization decorator
Code Example
import functools
import time
# Basic decorator pattern
def timer(func):
@functools.wraps(func) # preserves func.__name__, __doc__
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function(n):
time.sleep(n)
slow_function(1) # "slow_function took 1.0012s"
# Decorator with arguments (decorator factory)
def retry(max_attempts=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=5, delay=2)
def flaky_api_call():
...
What Interviewers Are Really Looking For
Writing a decorator from scratch in the interview is a common ask. The key things: *args/**kwargs for flexibility, functools.wraps for metadata preservation, and returning the wrapper. If asked about decorators with arguments, the triple-nested function pattern (factory → decorator → wrapper) is what they want to see.
Practice This Question with AI Grading
Reading about interview questions is a start — but practicing with real-time AI feedback is how you actually get better. Goliath Prep grades your answers instantly and tells you exactly what you're missing.
Start Practicing Free →