GAP·MAP
← all breakdowns
PythonDecoratorsHigher-Order Functionsjunior level

Python Decorators Without the Magic

the question

What are decorators in Python, and what does the `@decorator` syntax actually do?

what they're testing: The interviewer is testing whether you can reason about functions as objects, name binding, and reusable behavior around a callable.

a strong answer

A decorator takes the object produced by a function or class definition and decides what gets bound to that name. For a function, @logged above def fetch(): ... is roughly fetch = logged(fetch). This happens when the definition executes, often while a module is imported, not every time fetch is called.

To add behavior on each call, a decorator commonly returns a wrapper. The wrapper accepts *args and **kwargs, does work before or after calling the original function, and returns its result. That makes decorators a good fit for reusable concerns such as logging, authorization, timing, and caching. A decorator can also register a function and return the original unchanged, so wrapping is common rather than mandatory.

With stacked decorators, the one closest to the function is applied first: @outer over @inner becomes outer(inner(fetch)). Wrappers should normally use functools.wraps so tools still see the original name, docstring, annotations, and __wrapped__ link. The same syntax works for classes, where the completed class object is passed to the decorator and the returned value is bound to the class name.

Where people slip

the tempting wrong answer, and what's actually true

  • A decorator runs only when the decorated function is called.

    The decorator is applied when the definition executes, while code inside a returned wrapper can run on each later call.

  • A decorator must return an inner wrapper function.

    Its return value is bound to the decorated name, so it can return the original callable, a wrapper, or even another kind of object.

  • Decorators permanently change the original function's body.

    Decoration passes the function object to a callable and binds that callable's returned value to the original name.

  • Stacked decorators are applied from top to bottom.

    The decorator nearest the definition is applied first, so `@outer` above `@inner` produces `outer(inner(function))`.

If they push further

How does a decorator accept its own arguments?

In `@retry(3)`, `retry(3)` runs first and returns the actual decorator. Python then calls that decorator with the function object.

Why should a wrapper use `functools.wraps`?

Without it, introspection sees the wrapper's metadata instead of the original function's. `functools.wraps` copies key metadata and exposes the original through `__wrapped__`.

What happens if a decorator returns `None`?

The decorated name is rebound to `None`. The definition succeeds, but calling that name later raises a `TypeError` because `None` is not callable.

Sources

Now answer it yourself.

Reading a strong answer is easy. Producing one under pressure is the skill the interview tests. Gapmap grades your answer against the same bar an interviewer would.

was this useful?

beta

The interviewer part is in the works.

The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.