Mid-Level Advanced Patterns 6 min read

Python Decorators — How They Work and How to Write Them

The Interview Question

How do decorators work in Python? Can you write one from scratch?

Expert Answer

A decorator is a function that takes a function as input and returns a new function that usually extends the original's behavior. The @decorator syntax is syntactic sugar — @my_decorator above a function definition is equivalent to func = my_decorator(func). Under the hood, decorators work because functions are first-class objects in Python — they can be passed as arguments, returned from other functions, and assigned to variables. The standard decorator pattern uses a wrapper function with *args and **kwargs to handle any argument signature, calls the original function inside, and returns the result. Use functools.wraps to preserve the original function's name, docstring, and other metadata — without it, debugging and introspection break because the function appears to be the wrapper instead of itself.

Key Points to Hit in Your Answer

  • @decorator is sugar for func = decorator(func)
  • Always use @functools.wraps to preserve function metadata
  • Decorators with arguments need an extra layer of nesting (decorator factory)
  • Common patterns: logging, timing, authentication, caching, retry logic
  • Class-based decorators use __call__ for instances that act as functions
  • functools.lru_cache is the built-in memoization decorator

Code Example

import functools
import time

# Basic decorator pattern
def timer(func):
    @functools.wraps(func)  # preserves func.__name__, __doc__
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} took {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_function(n):
    time.sleep(n)

slow_function(1)  # "slow_function took 1.0012s"

# Decorator with arguments (decorator factory)
def retry(max_attempts=3, delay=1):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=5, delay=2)
def flaky_api_call():
    ...

What Interviewers Are Really Looking For

Writing a decorator from scratch in the interview is a common ask. The key things: *args/**kwargs for flexibility, functools.wraps for metadata preservation, and returning the wrapper. If asked about decorators with arguments, the triple-nested function pattern (factory → decorator → wrapper) is what they want to see.

Practice This Question with AI Grading

Reading about interview questions is a start — but practicing with real-time AI feedback is how you actually get better. Goliath Prep grades your answers instantly and tells you exactly what you're missing.

Start Practicing Free →