Decorators Interview Questions & Answers
5 questions Updated 2026-06-18
Python interview questions on decorators, functools.wraps, decorators with arguments, class-based decorators, stacking order, and real-world use cases.
Read the in-depth guidePython Decorators Explained — Wrapping Functions, functools.wraps, and Decorators with ArgumentsA decorator is a callable that takes a function and returns a (usually
wrapped) function, letting you add behaviour without modifying the original.
The @decorator syntax above a def is just sugar for reassigning the name to
the decorator's result: func = decorator(func).
def log_calls(func):
def wrapper(*args, **kwargs): # accept any signature
print(f"calling {func.__name__}")
return func(*args, **kwargs) # delegate to the original
return wrapper
@log_calls
def add(a, b):
return a + b
# equivalent to: add = log_calls(add)
add(2, 3) # prints "calling add", returns 5
This works because functions are first-class objects — they can be passed around and returned. Decorators are the idiomatic way to factor out cross-cutting concerns (logging, timing, caching, access control).
Without it, the wrapper replaces the original function's identity: the
decorated object reports the wrapper's __name__, __doc__, signature, and
__module__, which breaks introspection, debugging, and tools that rely on
metadata. functools.wraps copies that metadata from the original onto the
wrapper.
import functools
def log_calls(func):
@functools.wraps(func) # copy name, docstring, __wrapped__, etc.
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@log_calls
def greet():
"say hello"
...
greet.__name__ # "greet" (without wraps -> "wrapper")
greet.__doc__ # "say hello"
It also sets __wrapped__, so inspect.signature and unwrapping still work.
Rule of thumb: always apply @functools.wraps(func) to your wrapper — it's
effectively free and prevents subtle bugs.
You add another layer of nesting: an outer function takes the decorator's
arguments and returns the actual decorator, which takes the function and returns
the wrapper. So @repeat(3) first calls repeat(3) to get a decorator, which
is then applied to the function.
import functools
def repeat(n): # takes the decorator argument
def decorator(func): # takes the function
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3) # repeat(3) returns 'decorator'
def ping():
print("pong")
The mental model: @repeat(3) is ping = repeat(3)(ping) — three calls deep.
Remember the parentheses: @repeat(3) (with args) differs from @repeat (passing
the function directly), and forgetting them is a common bug.
A class becomes a decorator by being callable — define __call__. The
__init__ receives the decorated function; __call__ runs the wrapping logic on
each invocation. This is handy when the decorator needs to hold state (like a
call count) in a clean, attribute-based way.
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func) # the class-based wraps
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"call #{self.count}")
return self.func(*args, **kwargs)
@CountCalls
def hello():
print("hi")
hello(); hello() # "call #1" then "call #2"
hello.count # 2 — state lives on the instance
Use functools.update_wrapper (the function-form of wraps) to preserve
metadata. Class decorators shine for stateful decorators; for simple stateless
ones, a nested function with a nonlocal closure is usually lighter.
Decorators apply bottom-up (nearest the function first) at definition time, but the resulting wrappers execute top-down at call time. Stacking is just nested application: the top decorator wraps the result of the ones below it.
@a
@b
def f(): ...
# equivalent to: f = a(b(f)) — b wraps first, a wraps outermost
def bold(fn):
return lambda: "<b>" + fn() + "</b>"
def italic(fn):
return lambda: "<i>" + fn() + "</i>"
@bold
@italic
def text():
return "hi"
text() # "<b><i>hi</i></b>" — bold is outer, runs around italic
So the closest decorator is applied first but its logic runs innermost.
Order matters whenever decorators have side effects or transform results — e.g.
put @staticmethod outermost, or @app.route above @login_required so auth
runs before the view.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.