Programming Techniques

Decorators & Annotations

Wrapping or augmenting functions, methods, and classes without modifying their source.

Overview

A decorator wraps a function, method, or class to augment its behaviour without modifying its source code. Decorators are a form of metaprogramming that separates concerns cleanly, the original code describes what to do, while decorators handle how (logging, timing, caching, auth checks).

Origin

The decorator pattern was formalised in the Gang of Four Design Patterns book (1994). Python adopted decorator syntax with @ in version 2.4 (2004). JavaScript has a stage 3 proposal. TypeScript supports class and property decorators through its compiler.

Examples

Python-style decorator in JavaScript (manual wrapping)

function logged(fn) {
  return function(...args) {
    console.log(`Calling ${fn.name}(${args.join(', ')})`)
    const start  = performance.now()
    const result = fn.apply(this, args)
    const ms     = (performance.now() - start).toFixed(2)
    console.log(`${fn.name} completed in ${ms}ms -> ${result}`)
    return result
  }
}

function timed(fn) {
  return function(...args) {
    const start = performance.now()
    const r = fn.apply(this, args)
    metrics.record(fn.name, performance.now() - start)
    return r
  }
}

// Compose decorators manually
const expensiveCalc = logged(timed(function calculateTotal(items) {
  return items.reduce((s, i) => s + i.price, 0)
}))

Ruby module prepend as a decorator

module Cacheable
  def find(id)
    @cache ||= {}
    @cache[id] ||= super
  end
end

module Auditable
  def save
    result = super
    AuditLog.record(self.class, :save, id)
    result
  end
end

class UserRepository
  prepend Cacheable
  prepend Auditable

  def find(id)
    DB.query("SELECT * FROM users WHERE id = ?", id)
  end

  def save
    DB.execute("UPDATE users SET ...")
  end
end

Module prepend inserts the module before the class in the method lookup chain. super calls the original method. This is the idiomatic Ruby decorator without modifying the original class.

Use Cases

  • 01Cross-cutting concerns: logging, metrics, tracing, and auth without polluting business logic
  • 02Retry logic around API calls without duplicating try/catch in every caller
  • 03Rate limiting or throttling specific methods
  • 04TypeScript class decorators for ORM annotations (@Column, @Entity, @PrimaryKey)
  • 05React higher-order components wrapping components with data-fetching or auth

When Not to Use

  • //When the wrapping logic is tightly coupled to the specific function, a direct call is clearer
  • //When stacking many decorators makes the call chain opaque in stack traces
  • //For rarely-executed code where the overhead of function wrapping outweighs the organisational benefit

Technical Notes

  • In Ruby, alias_method_chain was the historical approach. Module prepend is the modern, cleaner alternative
  • TypeScript decorators affect compilation output. Property decorators are evaluated at class definition time, not instance creation time
  • Decorating async functions requires the wrapper to be async too and to await the inner call
  • Python's functools.wraps preserves the wrapped function's metadata (name, docstring), always use it when writing decorators in Python