TypeScript Cheatsheet

Decorators

Use this TypeScript reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Overview and Setup

Decorators are a Stage 3 TC39 proposal, fully supported in TypeScript 5.0+. Two syntaxes exist: the legacy decorator (TS < 5, experimentalDecorators: true) and the new Stage 3 decorator (TS 5+, no flag needed). Both are covered here.

// tsconfig.json for legacy decorators (TS < 5 or emitDecoratorMetadata)
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true  // enables reflect-metadata support
  }
}
// TS 5+ new decorators — no config flag required
// Just use @decorator syntax — the compiler recognizes Stage 3 decorators by default

Class Decorators

New (Stage 3 / TS 5+)

// Receives: class constructor; can return a new class
function sealed<T extends abstract new (...args: any[]) => any>(target: T) {
  Object.seal(target);
  Object.seal(target.prototype);
  return target;
}

@sealed
class User {
  constructor(public name: string) {}
}

// With replacement class
function singleton<T extends new (...args: any[]) => any>(Base: T) {
  let instance: InstanceType<T> | undefined;
  return class extends Base {
    constructor(...args: any[]) {
      super(...args);
      if (instance) return instance;
      instance = this as InstanceType<T>;
    }
  };
}

@singleton
class Database {
  connect() {}
}

Legacy (experimentalDecorators)

// Legacy — receives constructor function
function logger(ctor: Function) {
  console.log(`Class ${ctor.name} created`);
}

function addTimestamp<T extends { new(...args: any[]): {} }>(ctor: T) {
  return class extends ctor {
    createdAt = new Date();
  };
}

@logger
@addTimestamp
class Report {
  constructor(public title: string) {}
}

Decorator Factories (Parametrized)

// A function that returns a decorator
function prefix(text: string) {
  return function <T extends abstract new (...args: any[]) => any>(target: T) {
    return class extends target {
      constructor(...args: any[]) {
        super(...args);
        // @ts-ignore
        this.name = `${text} ${this.name}`;
      }
    };
  };
}

@prefix("Dr.")
class Doctor {
  constructor(public name: string) {}
}
const d = new Doctor("Alice");
// d.name === "Dr. Alice"

// Legacy factory
function configurable(value: boolean) {
  return function(target: any, key: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
    return descriptor;
  };
}

Method Decorators

New (Stage 3 / TS 5+)

// context.name — method name; context.addInitializer — run after class construction
function log(target: Function, context: ClassMethodDecoratorContext) {
  const methodName = String(context.name);
  return function(this: any, ...args: any[]) {
    console.log(`Calling ${methodName} with`, args);
    const result = target.apply(this, args);
    console.log(`${methodName} returned`, result);
    return result;
  };
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

Legacy Method Decorator

// Receives: prototype, method name, property descriptor
function readonly(target: any, key: string, descriptor: PropertyDescriptor) {
  descriptor.writable = false;
  return descriptor;
}

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

function memoize(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  const cache = new Map<string, any>();
  descriptor.value = function(...args: any[]) {
    const cacheKey = JSON.stringify(args);
    if (!cache.has(cacheKey)) cache.set(cacheKey, original.apply(this, args));
    return cache.get(cacheKey);
  };
  return descriptor;
}

class MathService {
  @readonly
  PI = 3.14;

  @memoize
  fibonacci(n: number): number {
    if (n <= 1) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }

  @deprecated
  oldMethod() {}
}

Property Decorators

New (Stage 3 / TS 5+)

// ClassFieldDecoratorContext — can add initializers
function required(target: undefined, context: ClassFieldDecoratorContext) {
  context.addInitializer(function(this: any) {
    const key = context.name;
    Object.defineProperty(this, key, {
      get() { return this[`_${String(key)}`]; },
      set(val) {
        if (val === undefined || val === null)
          throw new Error(`${String(key)} is required`);
        this[`_${String(key)}`] = val;
      },
    });
  });
}

class Form {
  @required
  email!: string;
}

Legacy Property Decorator

// Receives: prototype and property name (no descriptor)
function validate(target: any, key: string) {
  let value: any;
  Object.defineProperty(target, key, {
    get: () => value,
    set: (val: any) => {
      if (typeof val !== "string") throw new TypeError(`${key} must be a string`);
      value = val;
    },
  });
}

class User {
  @validate
  name!: string;
}

Accessor Decorators

New (Stage 3 / TS 5+)

// Decorates auto-accessors (accessor keyword — TS 4.9+)
function clamp(min: number, max: number) {
  return function(target: ClassAccessorDecoratorTarget<unknown, number>, context: ClassAccessorDecoratorContext) {
    return {
      get(this: unknown) { return target.get.call(this); },
      set(this: unknown, val: number) {
        target.set.call(this, Math.max(min, Math.min(max, val)));
      },
    };
  };
}

class Slider {
  @clamp(0, 100)
  accessor value = 50;
}

const s = new Slider();
s.value = 150; // clamped to 100

Legacy Accessor Decorator

// Same signature as method decorator
function enumerable(value: boolean) {
  return function(target: any, key: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
    return descriptor;
  };
}

class Person {
  private _name = "Alice";

  @enumerable(false)
  get name() { return this._name; }
}

Parameter Decorators (Legacy Only)

// Receives: prototype, method name, parameter index
function logParam(target: any, key: string, index: number) {
  const existing: number[] = Reflect.getOwnMetadata("log_params", target, key) ?? [];
  existing.push(index);
  Reflect.defineMetadata("log_params", existing, target, key);
}

class Api {
  greet(@logParam name: string, @logParam greeting: string): string {
    return `${greeting}, ${name}`;
  }
}

Decorator Composition (Stacking)

// Decorators apply bottom-up (innermost first at runtime)
@A
@B
@C
class MyClass {}
// Application order: C → B → A
// (factories evaluated top-down: A() → B() → C(); decorators applied C → B → A)

// Example
function first()  { return (t: any) => { console.log("first applied");  return t; }; }
function second() { return (t: any) => { console.log("second applied"); return t; }; }

@first()
@second()
class Test {}
// Output:
// "second applied"
// "first applied"

Reflect Metadata (Legacy / NestJS / Angular)

// Requires: "emitDecoratorMetadata": true + npm install reflect-metadata
import "reflect-metadata";

// Built-in metadata keys emitted by TypeScript
Reflect.getMetadata("design:type", target, key);       // property type
Reflect.getMetadata("design:paramtypes", target, key); // constructor/method param types
Reflect.getMetadata("design:returntype", target, key); // method return type

// Custom metadata
function Injectable(target: Function) {
  Reflect.defineMetadata("injectable", true, target);
}

function Inject(token: symbol) {
  return function(target: any, key: string | symbol, index: number) {
    Reflect.defineMetadata(`inject:${index}`, token, target);
  };
}

@Injectable
class Service {
  constructor(@Inject(Symbol("DB")) private db: Database) {}
}

Common Decorator Patterns

// Bind method to instance (fixes `this` for callbacks)
function bound(target: any, key: string, descriptor: PropertyDescriptor) {
  return {
    configurable: true,
    get(this: any) {
      const bound = descriptor.value.bind(this);
      Object.defineProperty(this, key, { value: bound, configurable: true });
      return bound;
    },
  };
}

// Throttle a method
function throttle(ms: number) {
  return function(_: any, __: string, descriptor: PropertyDescriptor) {
    const fn = descriptor.value;
    let lastCall = 0;
    descriptor.value = function(...args: any[]) {
      const now = Date.now();
      if (now - lastCall >= ms) { lastCall = now; return fn.apply(this, args); }
    };
  };
}

// Time execution
function time(target: any, key: string, descriptor: PropertyDescriptor) {
  const fn = descriptor.value;
  descriptor.value = function(...args: any[]) {
    const t0 = performance.now();
    const result = fn.apply(this, args);
    console.log(`${key} took ${(performance.now() - t0).toFixed(2)}ms`);
    return result;
  };
}

class Component {
  @bound
  handleClick() { console.log(this); }

  @throttle(300)
  onScroll() { /* ... */ }

  @time
  render() { /* expensive */ }
}

TS 5+ Stage 3 Context Object Reference

Decorator TypeContext TypeKey Properties
ClassClassDecoratorContextname, addInitializer
MethodClassMethodDecoratorContextname, static, private, addInitializer
FieldClassFieldDecoratorContextname, static, private, addInitializer
AccessorClassAccessorDecoratorContextname, static, private, addInitializer
GetterClassGetterDecoratorContextname, static, private, addInitializer
SetterClassSetterDecoratorContextname, static, private, addInitializer