Decorators

A decorator is a function applied with @ syntax to a class, method, or field that can observe or replace it — fundamentally a higher-order function that takes the decorated thing and returns an enhanced version.

Learn Decorators in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

A decorator is gift-wrapping. You hand over a present (a method or class) and get back the same present in nicer packaging — maybe with a ribbon that logs who opened it, or a tamper-proof seal that makes it read-only. The gift still works as before; the wrapper just adds behaviour around it. A decorator factory is choosing the wrapping paper first: withVersion("3.1") picks the design, then wraps.

1. A Decorator Is a Higher-Order Function

Strip away the @ syntax and a method decorator is just a function that receives the original method and returns a replacement. A @log decorator wraps the method so every call is logged, then delegates to the original. The TS 5 form adds a context argument, but the mechanic is the same wrapping you can do by hand.

2. A @readonly Field Decorator

A field decorator returns an initializer that runs per instance, so it can transform or lock a field. A @readonly decorator's runtime job is to make the property non-writable — under the hood, Object.defineProperty with writable: false . That's the same primitive whether you write the decorator or the defineProperty call yourself.

3. Class Decorators & Factories

A class decorator receives the constructor and can augment or replace the whole class — adding statics, sealing it, registering it. When you need to pass options, use a decorator factory : @withVersion("3.1") is a function that returns the actual decorator. Note this is TS 5 stage-3 syntax — not the legacy experimentalDecorators that Angular and NestJS still rely on.

🎯 Your Turn

Write a @double decorator as a higher-order function — it wraps a method so its result is doubled. Fill in the two blanks marked ___ , then run it.

No blanks this time — just a brief and a starting outline. Build the caching decorator as a higher-order function, run it, and check your output against the example in the comments.

Practice quiz

At its core, what is a decorator?

  • A CSS class
  • A runtime polyfill
  • A higher-order function that receives the decorated thing and can return an enhanced version
  • A type alias

Answer: A higher-order function that receives the decorated thing and can return an enhanced version. Every decorator is fundamentally a higher-order function applied with @ syntax.

Do TypeScript 5 / stage-3 decorators require the 'experimentalDecorators' tsconfig flag?

  • No — stage-3 (TC39) decorators need no special flag
  • Yes, always
  • Only for class decorators
  • Only in strict mode

Answer: No — stage-3 (TC39) decorators need no special flag. TS 5's stage-3 decorators follow the finalized TC39 proposal and work with no flag.

What extra argument do stage-3 decorators receive that the old experimental ones did not?

  • A prototype object
  • The tsconfig
  • A reflect-metadata handle
  • A context object (e.g. ctx.name, ctx.kind)

Answer: A context object (e.g. ctx.name, ctx.kind). Stage-3 decorators receive a context object describing what is being decorated.

Why do Angular and NestJS still use the legacy experimentalDecorators?

  • They are written in JavaScript
  • They rely on emitDecoratorMetadata for dependency injection
  • Stage-3 decorators are slower
  • They predate TypeScript

Answer: They rely on emitDecoratorMetadata for dependency injection. Those frameworks depend on emitDecoratorMetadata (reflect-metadata) for DI, which stage-3 does not emit.

A @log method decorator works by:

  • Returning a replacement function that wraps and delegates to the original
  • Deleting the method
  • Renaming the method
  • Freezing the class

Answer: Returning a replacement function that wraps and delegates to the original. A method decorator returns a wrapper that adds behaviour and calls the original method.

What runtime primitive does a @readonly field decorator use to lock a property?

  • Object.freeze on the class

The runtime job is Object.defineProperty(..., { writable: false }) to make the property non-writable.

A decorator FACTORY like @withVersion("3.1") is:

  • A bare decorator
  • A function that returns the actual decorator
  • A class
  • An interface

Answer: A function that returns the actual decorator. A factory is called first; it returns the real decorator, which then receives the target.

What is the difference between @sealed and @withVersion("3.1")?

  • No difference
  • @sealed is a factory
  • @withVersion cannot take arguments
  • @sealed IS the decorator (no parens); @withVersion("3.1") is a factory that must be called

Answer: @sealed IS the decorator (no parens); @withVersion("3.1") is a factory that must be called. A plain decorator has no parentheses; a factory must be invoked to produce the decorator.

Inside a wrapper, how do you preserve the original method's 'this'?

  • Ignore this
  • Use originalMethod.apply(this, args)
  • Use a global variable
  • Bind to window

Answer: Use originalMethod.apply(this, args). Call originalMethod.apply(this, args) so the wrapped method keeps its receiver.

What can a class decorator do?

  • Only rename the class
  • Only add CSS
  • Receive the constructor and augment or replace the whole class (e.g. add statics)
  • Nothing at runtime

Answer: Receive the constructor and augment or replace the whole class (e.g. add statics). A class decorator receives the constructor and can add statics, seal it, register it, or replace it.