← Back to Python

Decorators — Under the Hood

PythonSeniorpython

The Question

How do decorators work under the hood in Python?

What a Strong Answer Covers

  • takes and returns function
  • "syntactic sugar
  • "closure
  • "@functools.wraps

Senior-Level Answer

A decorator in Python is a callable -- typically a function -- that takes a function as an argument and returns a new callable. The @decorator syntax is pure syntactic sugar. Writing @log_calls above a function definition is exactly equivalent to writing f = log_calls(f) after the def statement. The decorator is applied at class or module load time, not at call time.

The most common pattern is a wrapper function. The decorator defines an inner function (the wrapper) that calls the original function, adding behavior before or after -- logging, timing, authentication, retry logic. The decorator returns the wrapper. From the caller's perspective, they are calling the original function, but they are actually calling the wrapper.

A critical implementation detail: without functools.wraps, the wrapper function replaces the original function's __name__, __doc__, and other attributes. This breaks introspection tools, documentation generators, and test frameworks that rely on function names. functools.wraps(original) applied to the wrapper copies these attributes from the original to the wrapper. Always use it in production decorators.

Decorators can also be parameterized -- meaning the decorator itself takes arguments. In this case, you need an extra level of nesting: a factory function that takes the parameters and returns the actual decorator. For example, @retry(max_attempts=3) is a call to retry(max_attempts=3) which returns a decorator, which is then applied to the function. This is three levels: factory, decorator, wrapper.

Class-based decorators are also valid. A class that implements __call__ can be used as a decorator. This is useful when the decorator needs to maintain state across calls such as a call counter.

Decorators stack. When you apply multiple decorators, they are applied bottom-up: the one closest to the def runs first, wrapping the function, then the outer decorator wraps the result. Order matters -- a caching decorator inside an authentication decorator caches authenticated results; reversed, it caches before checking auth.

Common real-world uses: @staticmethod and @classmethod (built-in), @property, @functools.lru_cache, Flask's @app.route, and authentication or rate-limiting middleware in web frameworks.

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Candidate correctly explains the @ syntax as sugar for f = decorator(f) and implements a basic wrapper, but does not know functools.wraps, parameterized decorators (triple nesting), or stacking order.

3/3 — Strong Answer

Candidate explains the desugaring, implements a working wrapper with functools.wraps, explains parameterized decorators (factory then decorator then wrapper), describes class-based decorators, and explains stacking order (bottom-up application).

Common Mistakes

  • Forgetting functools.wraps -- the wrapped function loses its name, docstring, and other metadata without it
  • Confusing parameterized decorators -- @retry(3) needs three levels of nesting, not two
  • Thinking decorators run at call time -- the decoration itself happens at definition and import time
  • Not knowing that decorators stack bottom-up -- the decorator written closest to the function applies first

Follow-Up Questions

  • What does functools.wraps actually do mechanically? — It calls functools.update_wrapper on the wrapper, copying __name__, __qualname__, __doc__, __dict__, and __module__ from the wrapped function.
  • How would you write a decorator that counts function calls? — Use a class-based decorator with a __call__ method that increments an instance attribute on each call -- state persists between calls.
  • What is the difference between @property and a regular decorator? — @property returns a descriptor object -- the descriptor protocol (__get__, __set__, __delete__) handles attribute access rather than direct calls.
  • Can a decorator modify a function's arguments? — Yes -- the wrapper intercepts args and kwargs before calling the original, and can modify, validate, or add to them before passing them on.

Related Questions

  • GIL — What It Is and What It Protects
  • ThreadPoolExecutor & ProcessPoolExecutor
  • Generators & yield
  • Context Managers
  • LEGB Rule

Can You Explain This Cold?

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
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout