What is a decorator factory and when would you use one?
A decorator is a function that takes a function and returns a modified function. A **decorator factory** is a function that takes arguments and *returns* a decorator. It adds one layer of nesting to allow the decorator to be parameterized.
Without a factory, a decorator is applied as `@my_decorator`. To pass arguments, you'd want `@my_decorator(arg1, arg2)`. This syntax requires `my_decorator(arg1, arg2)` to be called first, returning a decorator—that's a factory.
The structure:
```python def repeat(n): # factory — takes arguments def decorator(func): # actual decorator — takes function def wrapper(*args, **kwargs): for _ in range(n): func(*args, **kwargs) return wrapper return decorator
@repeat(3) def greet(name): print(f'Hello, {name}') ```
Here `repeat(3)` is called at decoration time, returning `decorator`, which then wraps `greet`. The `n=3` is captured in the closure of `wrapper`.
The extra function level (factory → decorator → wrapper) is always the pattern. The factory receives configuration; the decorator receives the function; the wrapper receives call-time arguments.
When to use a decorator factory: any time you want the decorator's behavior to be configurable per use-site—retry count (`@retry(max_attempts=3)`), cache TTL (`@cache(ttl=60)`), required permissions (`@require_role('admin')`), rate limit per endpoint (`@rate_limit(calls=100, period=60)`).
`functools.wraps` should always be applied to the wrapper to preserve the original function's `__name__`, `__doc__`, and `__wrapped__` attribute—without it, introspection and stack traces show `wrapper` instead of the original function name, making debugging painful.
A common pattern is making the factory optional (supporting both `@my_decorator` and `@my_decorator(arg)`), which requires checking whether the argument to the factory is callable—but this adds complexity and is usually not worth it outside library code.
Explains the three-level nesting (factory → decorator → wrapper), shows how arguments are captured via closure, gives a practical use case.
Explains why the extra nesting is needed for argument passing, demonstrates functools.wraps usage and explains why it matters, gives multiple real use cases (retry, cache, auth), and discusses the optional-argument factory pattern.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment