Back to Python & Coding

PYTHON & CODING · QUESTION 78

What is a decorator in Python, and how does it work?

On this page
Intermediate 4 min read
Interview-ready answerThe concise version to remember

A decorator is a callable that takes a function or class and returns a replacement, often wrapping the original with additional behavior. The `@decorator` syntax applies that transformation at definition time. Decorators are useful for cross-cutting concerns such as logging, caching, or authorization; use `functools.wraps` so wrapped functions preserve useful metadata.

LET'S MAKE IT SIMPLE

First, the idea in plain English

Functions are values in Python, so one function can receive another function and return a new one. The new function can do something before or after calling the original. The `@` notation is a compact way to apply this wrapper where the function is defined.

A USEFUL COMPARISON

Picture it this way

A decorator is like putting a reusable cover around a tool: the tool still does its original job, while the cover adds a safety label or tracking step. The wrapper must preserve how the tool is identified and called, or it can surprise other code.

FOLLOW THE JOURNEY

A wrapper adds behavior around a function call

Trace definition-time decoration and runtime calls through a wrapper.

A wrapper adds behavior around a function callTrace definition-time decoration and runtime calls through a wrapper.Function call flow
01Define original functionCreate a function object with its behavior and metadata.
02Apply decoratorPass the function to a callable that returns a replacement.
03Call wrapperRun added behavior and delegate to the original function.

This simplified visual highlights the main relationships. In practice, the exact design depends on the requirements; Cross-cutting logic is reused without editing every function body.

A function is passed to a decorator, which returns a wrapper; calls enter the wrapper, run added behavior, invoke the original function, and return its result.

How to read this visual
This simplified visual highlights the main relationships. In practice, the exact design depends on the requirements; Cross-cutting logic is reused without editing every function body.

UNDER THE HOOD

How it works, step by step

01

Define original function

Create a function object with its behavior and metadata.

Why it matters: The decorator receives this callable.

02

Apply decorator

Pass the function to a callable that returns a replacement.

Why it matters: The replacement can wrap or transform the original.

03

Call wrapper

Run added behavior and delegate to the original function.

Why it matters: Cross-cutting logic is reused without editing every function body.

OUTSIDE THE TEXTBOOK

A practical example

A timing decorator records how long a function takes. The wrapper captures a start time, calls the original function, records elapsed time, and returns the original result. It should use `try/finally` if timing must be recorded even when the function raises an exception.

GO A LITTLE DEEPER

Technical details that matter

  • Decorators can accept configuration by using an outer function that returns the actual decorator.
  • Decorators are applied from the closest `@` line outward when stacked.
  • Use `functools.wraps` to copy name, documentation, and annotations to the wrapper.
Python example
from functools import wraps

def announce(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        print("Starting")
        return function(*args, **kwargs)
    return wrapper

@announce
def greet(name):
    return f"Hello, {name}"

`@announce` is equivalent to `greet = announce(greet)`. The wrapper accepts the original function’s arguments and delegates to it.

MAKE A GOOD CHOICE

When is this useful?

  • Applying logging, timing, caching, or authorization consistently.
  • Adding reusable behavior around framework callbacks.
  • Composing function transformations declaratively.
Consider the alternative

Use an explicit helper or context manager when the behavior is local or needs a visible lifetime. Avoid decorators when they hide important control flow.

WATCH OUT FOR

Common misunderstandings

  • Forgetting to return the wrapped function’s result.
  • Dropping metadata by omitting `functools.wraps`.
  • Assuming decorators run on every call rather than when the function is defined.

REAL-WORLD TRADE-OFFS

Limitations and failure cases

  • Nested wrappers can make debugging and stack traces harder.
  • Decorators can hide side effects or alter signatures if metadata is not preserved.
  • Order matters when multiple decorators are stacked.

WHAT TO REMEMBER

Key takeaways

  1. 1A decorator transforms a function or class.
  2. 2The `@` syntax applies it at definition time.
  3. 3Use `wraps` and preserve arguments, return values, and exceptions.

PRACTICE THE FOLLOW-UP

What might an interviewer ask next?

When does a decorator execute?

The decorator is applied when the decorated function or class definition executes, usually during module import; the returned wrapper runs when called.

Why use `functools.wraps`?

It preserves useful metadata such as the original function’s name and documentation and provides a `__wrapped__` reference.

SAY IT OUT LOUD

How to explain this in an interview

“A decorator is a callable that takes a function and returns a replacement, commonly a wrapper that adds behavior before or after calling the original. The `@` syntax applies it when the function is defined. I use decorators for reusable cross-cutting logic like timing or caching, and preserve metadata with `functools.wraps`.”

Continue your learning path

Connect this question to the roadmap topic it supports.

Open roadmap topic