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
endModule 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
More in Programming Techniques