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.