Decorators are one of Python’s most powerful features—and also one of the most commonly asked topics in Python interviews.
Whether you’re learning Python for the first time, preparing for a FastAPI or Django interview, or trying to understand how frameworks like Flask and FastAPI work internally, decorators are an essential concept to master.
You have probably encountered code like this:
@app.get("/users")<br>def get_users():<br>return users
or
@login_required<br>def dashboard():<br>pass
These lines may look magical at first, but they are built on a simple and elegant Python feature called decorators
Learn Python decorators from scratch with practical examples, common mistakes, interview questions, closures,
wraps(), decorator arguments, and real-world FastAPI use cases.

Table of Contents
- What Is a Decorator?
- Why Do We Need Decorators?
- Functions Are First-Class Objects
- Functions Returning Functions
- Creating Your First Decorator
- The @ Syntax Explained
- Decorators with Function Arguments
- Preserving Metadata with functools.wraps
- Decorators That Return Values
- Decorators with Parameters
- Real-World Examples
- Stacking Multiple Decorators
- Class-Based Decorators
- Built-in Python Decorators
- Decorators in FastAPI
- Common Mistakes
- Python Decorator Interview Questions
- FAQ
- Conclusion
What Is a Decorator?
Decorators are one of Python’s most powerful and elegant features. They allow developers to modify or extend the behavior of functions and methods without changing the original source code.
A decorator is simply a function that takes another function as input, adds some functionality, and returns a new function.
Think of a decorator as a wrapper around an existing function.
For example:
def greet():
print("Hello World")
Suppose you want to log every time the function is called.
You could modify the function directly, but that quickly becomes repetitive when multiple functions need the same behavior.
Decorators solve this problem by allowing reusable behavior to be applied externally.
Why Do We Need Decorators?
In software engineering, certain functionality appears repeatedly across different parts of an application:
- Logging
- Authentication
- Authorization
- Performance monitoring
- Input validation
- Caching
- Rate limiting
- Error handling
Without decorators, this logic would be duplicated in many places.
Decorators help implement the DRY (Don’t Repeat Yourself) principle.
For example, instead of writing:
def process_order():
print("Logging started")
print("Processing order")
for every function, we can write a reusable decorator once.
Functions Are First-Class Objects
To understand decorators, you first need to understand that functions in Python are first-class objects.
This means functions can:
- Be assigned to variables
- Be passed as arguments
- Be returned from functions
- Be stored in data structures
Example:
def greet():
print("Hello")
say_hello = greet
say_hello()
Output:
Hello
The variable say_hello points to the same function object as greet.
This flexibility is the foundation of decorators.
Functions Returning Functions
Python functions can create and return other functions.
def outer():
def inner():
print("Inside inner function")
return inner
func = outer()
func()
Output:
Inside inner function
This behavior is known as a closure.
Closures are one of the building blocks of decorators.
Creating Your First Decorator
Let’s create a simple logging decorator.
def logger(func):
def wrapper():
print("Function started")
func()
print("Function finished")
return wrapper
Apply it manually:
def greet():
print("Hello World")
greet = logger(greet)
greet()
Output:
Function started Hello World Function finished
Notice that the original function remains untouched.
The @ Syntax Explained
Python provides cleaner syntax for applying decorators.
Instead of:
greet = logger(greet)
we can write:
@logger
def greet():
print("Hello World")
Python automatically converts this to:
def greet():
print("Hello World")
greet = logger(greet)
The @ symbol is simply syntactic sugar.
How Decorators Work Internally
Consider:
@logger
def greet():
print("Hello")
Execution flow:
- Python creates
greet() - Python passes
greetintologger() logger()returnswrapper()greetnow points towrapper()- Calling
greet()executeswrapper()
Visual representation:
greet() | v wrapper() | v original greet()
Decorators with Function Arguments
A common beginner mistake:
def logger(func):
def wrapper():
return func()
return wrapper
Works:
@logger
def hello():
print("Hello")
Fails:
@logger
def add(a, b):
return a + b
Error:
TypeError: wrapper() takes 0 positional arguments
Solution: Use *args and **kwargs
def logger(func):
def wrapper(*args, **kwargs):
print("Function started")
result = func(*args, **kwargs)
print("Function finished")
return result
return wrapper
Now the decorator works with any function signature.
Preserving Metadata with functools.wraps
Consider:
@logger
def greet():
"""Greets the user"""
Checking metadata:
print(greet.__name__)
Output:
wrapper
The original metadata is lost.
The Solution
from functools import wraps
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Now:
print(greet.__name__)
Output:
greet
Always use @wraps in production code.
Decorators That Return Values
A decorator should usually return the wrapped function’s result.
def logger(func):
def wrapper(*args, **kwargs):
print("Running function")
result = func(*args, **kwargs)
return result
return wrapper
Example:
@logger
def square(n):
return n * n
print(square(4))
Output:
Running function 16
Decorators with Parameters
Sometimes the decorator itself needs configuration.
Usage:
@repeat(3)
def greet():
print("Hello")
Implementation:
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
Output:
Hello Hello Hello
Notice the extra nesting layer.
This pattern is often called a decorator factory.
Real-World Examples
Example 1: Execution Timer
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Execution time: {end-start:.4f}s")
return result
return wrapper
Usage:
@timer
def slow_task():
time.sleep(2)
slow_task()
Output:
Execution time: 2.0001s
Example 2: Authentication Decorator
def login_required(func):
def wrapper(user):
if not user["logged_in"]:
print("Access denied")
return
return func(user)
return wrapper
Usage:
@login_required
def dashboard(user):
print("Welcome")
dashboard({"logged_in": True})
Output:
Welcome
Example 3: Caching Results
def cache(func):
storage = {}
def wrapper(n):
if n in storage:
return storage[n]
result = func(n)
storage[n] = result
return result
return wrapper
Usage:
@cache
def square(n):
print("Computing...")
return n * n
First call:
Computing...
Second call:
(No computation)
The value is returned from cache.
Stacking Multiple Decorators
You can apply multiple decorators to a function.
@decorator1
@decorator2
def my_function():
pass
Equivalent to:
my_function = decorator1(
decorator2(my_function)
)
Execution order:
decorator2 decorator1 function
This is a common interview question.
Class-Based Decorators
Decorators can also be implemented using classes.
class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Function called")
return self.func(*args, **kwargs)
Usage:
@Logger
def greet():
print("Hello")
Output:
Function called Hello
Built-in Python Decorators
Python provides several useful decorators.
@staticmethod
class Math:
@staticmethod
def add(a, b):
return a + b
No object instance required.
@classmethod
class User:
count = 0
@classmethod
def get_count(cls):
return cls.count
Receives the class reference.
@property
class Employee:
def __init__(self, salary):
self._salary = salary
@property
def salary(self):
return self._salary
Usage:
employee.salary
instead of:
employee.salary()
Decorators in FastAPI
FastAPI relies heavily on decorators.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def home():
return {"message": "Hello World"}
The decorator registers the function as an HTTP endpoint.
When a request arrives at /, FastAPI executes the associated function.
This is one of the most practical decorator examples you’ll encounter in modern Python development.
Common Mistakes
Forgetting to Return the Result
Wrong:
def wrapper():
func()
Correct:
def wrapper():
return func()
Forgetting *args and **kwargs
Wrong:
def wrapper():
pass
Correct:
def wrapper(*args, **kwargs):
pass
Forgetting functools.wraps
Wrong:
def wrapper(*args, **kwargs):
pass
Correct:
@wraps(func)
def wrapper(*args, **kwargs):
pass
Modifying Global State Unnecessarily
Decorators should ideally be predictable and free from hidden side effects.
Python Decorator Interview Questions
1. What is a decorator?
A decorator is a function that modifies or extends another function’s behavior without changing its source code.
2. Why are decorators useful?
They help implement reusable functionality such as:
- Logging
- Authentication
- Validation
- Caching
- Monitoring
3. What enables decorators in Python?
Functions being first-class objects.
4. What does the @ syntax do?
It converts:
@decorator
def func():
pass
into:
func = decorator(func)
5. Why use functools.wraps?
To preserve:
- Function name
- Documentation
- Metadata
- Signature information
6. What is the difference between a closure and a decorator?
A closure remembers variables from an outer scope.
A decorator is a practical application of closures used to modify functions.
7. Can decorators accept parameters?
Yes.
Example:
@retry(3)
def api_call():
pass
8. What is the order of execution for stacked decorators?
@A @B def func():
becomes:
func = A(B(func))
9. Can classes be used as decorators?
Yes, by implementing the __call__() method.
10. Where are decorators commonly used?
- FastAPI
- Flask
- Django
- Logging systems
- Monitoring tools
- Authentication frameworks
FAQ
Are decorators difficult to learn?
Initially yes, because they combine multiple Python concepts such as functions, closures, and higher-order programming. Once these concepts are understood, decorators become much easier.
Do decorators affect performance?
Decorators introduce a very small overhead because an additional function call is executed. In most applications, this overhead is negligible.
Can a decorator modify arguments?
Yes. A decorator can inspect, validate, or even modify arguments before calling the original function.
Are decorators object-oriented?
No. Decorators are primarily a functional programming feature, although class-based decorators also exist.
Can multiple decorators be applied?
Yes. Decorators can be stacked and are executed from the innermost decorator outward.
Conclusion
Decorators are one of Python’s most elegant and practical features. They allow developers to add functionality to existing code without modifying the original implementation.
By understanding decorators, you’ll gain a deeper understanding of:
- Higher-order functions
- Closures
- Function metadata
- Framework internals
- Advanced Python design patterns
Whether you’re preparing for Python interviews, building APIs with FastAPI, or developing production-grade applications, decorators are a skill you will use repeatedly throughout your Python journey.
If you’re serious about becoming a professional Python developer, mastering decorators is not optional—it’s essential.