What Are Decorators?

A decorator is a design pattern or language feature that allows a developer to dynamically modify or enhance the behavior of functions, methods, or classes without permanently modifying their original structure.

In simple terms, a decorator is a wrapper — it intercepts calls to a function or class and can modify its behavior before or after execution.

Decorators are most commonly associated with Python, but the concept exists in many languages under different names:

  • JavaScript: Decorators (proposed)
  • Java / C#: Annotations / Attributes
  • TypeScript: Experimental decorators (stable with config)
  • Scala / Kotlin: Annotations with metaprogramming

1. Decorator Pattern in OOP

The original Decorator Design Pattern comes from the Gang of Four (GoF) design patterns.

Intent:

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

UML View:

  • Component: defines an interface
  • ConcreteComponent: the core object
  • Decorator: wraps the component
  • ConcreteDecorator: adds behavior

This pattern is widely used in GUI toolkits, logging, and stream manipulation.

2. Function Decorators in Python

Python treats functions as first-class objects, allowing them to be passed as arguments and returned from other functions — a key enabler for decorators.

Basic Example:

def my_decorator(func):
    def wrapper():
        print("Before function runs")
        func()
        print("After function runs")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Output:

Before function runs
Hello!
After function runs

3. Syntax Explained

Using @decorator_name is shorthand for:

say_hello = my_decorator(say_hello)

This transforms the function say_hello into the wrapped version at definition time.

4. Working with Arguments

To make decorators flexible, you usually include *args and **kwargs in the wrapper:

def log_args(func):
    def wrapper(*args, **kwargs):
        print(f"Arguments: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

Example:

@log_args
def multiply(x, y):
    return x * y

5. Preserving Function Metadata

By default, decorators override the function’s name and docstring. Use functools.wraps() to fix this:

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

6. Decorators with Parameters

Want to customize your decorator? Use nested functions:

def repeat(n):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def greet():
    print("Hi!")

7. Real-World Use Cases

Use CaseExample
LoggingLog each function call
AuthenticationCheck credentials before allowing access
CachingStore and return previously computed results
Rate LimitingLimit how often a function runs
ValidationEnsure input types or constraints
PerformanceTime how long a function takes
Retry LogicAutomatically retry on failure
Access ControlAdmin-only or role-based permissions

8. Class Decorators

Decorators can also be used on classes in Python:

def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class Database:
    pass

9. Chaining Multiple Decorators

You can stack decorators:

@decorator_one
@decorator_two
def my_func():
    pass

Execution order:

  1. decorator_two wraps my_func
  2. decorator_one wraps the result

10. Built-in Python Decorators

DecoratorDescription
@staticmethodMarks a method as static (no self)
@classmethodFirst argument is cls, not self
@propertyTurns method into a read-only property
@functools.lru_cacheMemoization for fast repeated calls
@dataclasses.dataclassAutomatically adds init, repr, etc.

11. Decorators in Other Languages

JavaScript (proposed):

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

class Person {
  @readonly
  name = 'John';
}

TypeScript:

Enable via tsconfig.json:

{
  "experimentalDecorators": true
}

Used in Angular for @Component, @Injectable, etc.

12. Testing Decorated Functions

Be careful when testing decorated functions:

  • They may hide internal logic
  • Use functools.wraps() to preserve metadata
  • Consider undecorated versions for unit tests

13. When Not to Use Decorators

ConcernRisk
Debugging difficultyHidden execution layers
OverengineeringFor simple one-liners, unnecessary
Stack depthDeeply nested wrappers can obscure call stack
PerformanceWrappers can add overhead in performance-critical code

14. Best Practices

  • Use functools.wraps() to preserve identity
  • Keep decorators generic and composable
  • Limit side effects inside wrappers
  • Add docstrings to your decorators
  • Use decorators to enforce single responsibility

Summary

ConceptDescription
DecoratorA wrapper that modifies behavior of functions/classes
First-class functionRequired to support decorators
Syntax@decorator_name
Common UsesLogging, access control, retries, metrics
LanguagesPython, JS, TS, Java (annotations), C# (attributes)
Pattern OriginGang of Four’s Decorator Design Pattern

Decorators are one of the most elegant ways to encapsulate cross-cutting concerns — reusable, readable, and powerful.

Related Keywords

  • Wrapper Function
  • Higher-Order Function
  • First-Class Functions
  • Metadata
  • Annotations
  • Aspect-Oriented Programming
  • Proxy Pattern
  • Singleton Pattern
  • Reflection
  • LRU Cache
  • Memoization
  • Monkey Patching
  • Metaprogramming
  • Code Injection
  • Type Hints
  • Function Composition
  • Middleware
  • Dependency Injection