"Can you explain how Python decorators work?"
Decorators are a core Python concept that appears in virtually every Python codebase — from Flask routes to pytest fixtures to Django views. Understanding decorators demonstrates your grasp of closures, first-class functions, and Pythonic design patterns, making it a standard interview question for Python roles.
Start with fundamentals: in Python, functions are first-class objects — they can be passed as arguments, returned from other functions, and assigned to variables.
Explain closures: an inner function that remembers the variables of its enclosing scope even after the outer function has returned.
Define decorators: a function that takes a function as input and returns a new function with extended behavior. The @decorator syntax is syntactic sugar for func = decorator(func).
Show the standard pattern: outer function takes the target function, inner wrapper function calls the original function with additional logic before/after.
Cover advanced patterns: decorators with arguments (triple nesting), class-based decorators (__call__), functools.wraps to preserve metadata, and stacking multiple decorators.
Python decorators are built on two key concepts: first-class functions and closures. Since functions are objects in Python, they can be passed as arguments and returned from other functions. A closure is an inner function that captures variables from its enclosing scope. A decorator is simply a function that takes a function as input and returns a new function, typically adding behavior before or after the original function executes. The @decorator syntax is syntactic sugar — writing @log_time above a function definition is equivalent to func = log_time(func). The standard implementation pattern is: def my_decorator(func): def wrapper(*args, **kwargs): # before logic; result = func(*args, **kwargs); # after logic; return result; return wrapper. You should always use @functools.wraps(func) on the wrapper to preserve the original function's __name__, __doc__, and __module__ attributes — without it, debugging becomes difficult because introspection shows the wrapper's metadata. For decorators with arguments (like @retry(max_attempts=3)), you need an additional level of nesting: the outermost function takes the arguments and returns the actual decorator. Class-based decorators implement __call__ and can maintain state between calls, which is useful for things like rate limiting or caching. Decorators stack from bottom to top: @A @B def func() is equivalent to func = A(B(func)). In practice, decorators are everywhere: Flask's @app.route for URL routing, @property for computed attributes, @staticmethod/@classmethod for method types, @functools.lru_cache for memoization, and @pytest.fixture for test setup.
Always mention functools.wraps — forgetting it is a common real-world bug and interviewers notice if you know about it.
Be ready to write a decorator from scratch: timing decorator, retry decorator, or authentication decorator.
Know the execution order when stacking decorators — bottom decorator wraps first, top decorator wraps last.
Use HireMe AI during interviews to quickly recall decorator syntax and common patterns.
@decorator passes the function directly to decorator(func). @decorator() first calls decorator() which returns the actual decorator, then that decorator wraps the function. The parentheses version allows passing arguments to the decorator.
Without it, the decorated function loses its original __name__, __doc__, and other metadata, replaced by the wrapper's. This breaks debugging, logging, and documentation tools.
Yes. Class decorators take a class and return a modified class. @dataclass is a well-known example. You can also use them to add methods, modify __init__, or register classes.
HireMe AI listens to your interview in real-time, detects questions, and generates instant answer suggestions — so you never freeze up again.
Try HireMe AI Free