PROGRAMMING-CONCEPTS

Decorator: Definition, Purpose, and Examples

A decorator is a programming feature that lets you wrap a function, method, or class with extra behavior without modifying its original code. It’s a way to enhance or extend functionality in a clean, reusable style.

Python and TypeScript both support decorators directly, and JavaScript supports them through the modern ECMAScript proposal. Decorators often appear in frameworks, logging systems, validation tools, and meta-programming utilities.

Think of a decorator as placing a helpful “attachment” on top of an existing feature — similar to putting a protective case on your phone. The phone remains the same, but it gains something additional.


Why Decorators Exist

Decorators help developers:

  • avoid repeating the same setup code
  • add cross-cutting features (logging, caching, authorization)
  • separate concerns cleanly
  • modify behavior dynamically
  • keep the original function easy to read

They are especially useful when the same enhancement appears across many functions or classes.


Decorators in Python

Python’s decorator syntax uses the @ symbol placed directly above a function or method. The decorator receives the original function as input and returns a wrapped version.


A Simple Python Decorator

def announce(func):
    def wrapper(*args, **kwargs):
        print("Starting operation...")
        result = func(*args, **kwargs)
        print("Finished.")
        return result
    return wrapper

Here, wrapper adds extra behavior before and after the original func.

Applying it:

@announce
def compute():
    return 20 * 3

compute()

This prints messages before and after running compute().

The original function doesn’t know anything about these additions — the decorator handles it all.


Decorators for Performance Timing

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        total = time.time() - start
        print(f"Took {total:.4f} seconds")
        return result
    return wrapper

Apply to any function you want to measure.

This is common in data analysis, where performance varies by dataset size.


Decorators with Arguments (Decorator Factories)

Sometimes you want to configure the decorator itself.

def repeat(times):
    def decorate(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                func(*args, **kwargs)
        return wrapper
    return decorate

Using it:

@repeat(3)
def ping():
    print("ping")

This calls ping() three times.

Decorator factories give you flexible, parameter-driven behavior.


Decorators in TypeScript and Modern JavaScript

TypeScript supports decorators for:

  • classes
  • methods
  • accessors
  • properties
  • parameters

JavaScript is adopting similar features through the TC39 decorators proposal.


Class Decorator Example (TypeScript)

function locked(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@locked
class Vault {
  level = 5;
}

The decorator prevents extensions to the class and its prototype.

This is a simple example of modifying metadata and behavior at the class level.


Method Decorator Example (TypeScript)

function log(_target: any, property: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${property} with`, args);
    return original.apply(this, args);
  };
}

class Calculator {
  @log
  multiply(a: number, b: number) {
    return a * b;
  }
}

The decorator intercepts calls to .multiply() and logs the arguments.

This is one of the most common real-world patterns for decorators in TypeScript.


Accessor Decorator Example

function minValue(limit: number) {
  return function (
    _target: any,
    _prop: string,
    descriptor: PropertyDescriptor
  ) {
    const original = descriptor.set!;
    descriptor.set = function (value: number) {
      if (value < limit) {
        throw new Error(`Value must be >= ${limit}`);
      }
      original.call(this, value);
    };
  };
}

class Settings {
  private _refresh = 30;

  @minValue(10)
  set refresh(value: number) {
    this._refresh = value;
  }
}

This decorator enforces a rule on any assignment to the property.

It centralizes validation logic instead of repeating it.


Decorator-Like Patterns in Swift

Swift does not have a decorator keyword, but it supports similar behavior through property wrappers and function composition.


Property Wrapper Example

@propertyWrapper
struct NonEmpty {
    private var value: String = ""

    var wrappedValue: String {
        get { value }
        set { value = newValue.isEmpty ? value : newValue }
    }
}

struct FormField {
    @NonEmpty var name: String
}

Property wrappers behave very similarly to decorators on stored properties.

They modify how the value is stored and retrieved without changing the external API.


Real-World Uses of Decorators

Below are fresh, practical scenarios that safely illustrate the power of decorators.


Example 1: Authorization Check (Python)

def require_admin(func):
    def wrapper(user, *args, **kwargs):
        if not user.get("admin"):
            raise PermissionError("Admin privileges required.")
        return func(user, *args, **kwargs)
    return wrapper

This pattern appears in APIs, CLI tools, and dashboards.

It separates authorization from business logic.


Example 2: Caching Repeated Results (Python)

def cache(func):
    stored = {}

    def wrapper(x):
        if x not in stored:
            stored[x] = func(x)
        return stored[x]

    return wrapper

Any expensive calculation wrapped with @cache becomes far more efficient.


Example 3: Logging in TypeScript Services

function track(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`→ ${key}`, args);
    return original.apply(this, args);
  };
}

In API services or data pipelines, decorators are used to:

  • track requests
  • log transformations
  • validate parameters
  • wrap asynchronous operations

Why Developers Use Decorators

Decorators shine in situations where you want to apply the same enhancement to multiple features without scattering boilerplate everywhere.

They are ideal for:

  • logging
  • performance timing
  • input validation
  • caching
  • rate limiting
  • authorization
  • dependency injection
  • property validation
  • metadata tagging

Instead of repeating the same code 10 times, you place a decorator once and apply it where needed.


Common Mistakes

Forgetting to Preserve Function Metadata (Python)

Naive wrappers hide the original function’s name or docstring.

Using functools.wraps solves this neatly.

Overusing Decorators

They can make code hard to read if every function has multiple layers of indirection.

Confusing Decorator Order

In both Python and TypeScript, the order of decorators matters.

The outermost one applies first.

Using Decorators for Simple Tasks

Sometimes a normal helper function is clearer.


Summary

Decorators allow you to enhance classes, functions, and properties without modifying their core logic. Python uses @decorator syntax, TypeScript supports class and method decorators, and Swift mirrors much of this through property wrappers. Decorators help reduce repetitive code, keep logic clean, and handle cross-cutting concerns like logging, caching, and validation. They are a key tool for building elegant, maintainable code in modern programming.

Learn to Code for Free
Start learning now
button icon
To advance beyond this tutorial and learn to code by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

Reach your coding goals faster