skip to content

Classes & Decorators

The type-layer features that only exist on classes: visibility modifiers, readonly and parameter properties, abstract members, implements clauses, this typing, and decorators. Interviewers lean on this area because most real TypeScript codebases — Angular, NestJS, TypeORM — are class-and-decorator shaped, and the compile-time-only nature of these features trips people up.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

page 2 of 2

A TypeScript service class stores an API token in a field declared `private token: string`, and the token keeps turning up in log lines and JSON responses. Explain how that happens and what change actually stops it.

level: seniorimportance: should knowfreq 33%

basics

~20 s

The private modifier is erased at compile time, so the token is an ordinary enumerable property that JSON.stringify, object spread and structured loggers all pick up. Moving it to a #token field, or adding a toJSON that omits it, removes it from that output.

open as a page

A TypeScript project sets experimentalDecorators to true because it uses NestJS. A new library ships decorators written against the standard TC39 form. Can the project use both kinds of decorator, and what determines the answer?

level: seniorimportance: should knowfreq 50%

basics

~20 s

No. experimentalDecorators is a whole-compilation switch: with it on, every decorator in the program is compiled with legacy semantics, so a standard-form decorator expecting (value, context) receives (target, key, descriptor) instead and misbehaves. There is no per-file or per-decorator opt-in.

open as a page

A TypeScript service uses experimentalDecorators and emitDecoratorMetadata, and its DI container resolves constructor dependencies from design:paramtypes. One parameter is typed as an interface and the container fails to resolve it. Why does the metadata record `Object` there, and how do you make that dependency injectable?

level: seniorimportance: should knowfreq 46%

basics

~20 s

An interface has no runtime value, so the compiler serialises the parameter to Object and the container has no token to look up. Fix it by depending on a class or abstract class, or by supplying an explicit injection token through a parameter decorator.

open as a page

With standard (TC39) decorators in TypeScript 5.2 and later, how does a decorator record metadata about a class, and how does a library read that metadata back once the class is defined?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Each standard decorator receives a context object with a metadata property — one shared object per class that all its decorators write into. When decoration finishes, TypeScript installs that object on the class under Symbol.metadata, where any library can read it.

open as a page

In TypeScript, this class compiles with no error: `class G { greeting = this.build(); constructor(private name: string) {} build() { return `hi ${this.name}`; } }`. What does `new G("ada").greeting` hold, and what decides the answer?

level: seniorimportance: should knowfreq 38%

basics

~20 s

It depends on how class fields are compiled. Under define semantics (useDefineForClassFields on) field initializers run before the constructor body, so the parameter property is still undefined and greeting becomes "hi undefined"; with assignment semantics it is "hi ada".

open as a page

In TypeScript, `class Base { clone(): this { return new Base(); } }` does not compile — the returned Base is not assignable to `this`. Why is the compiler right, and what are the realistic ways to type a clone or copy-on-write method?

level: seniorimportance: should knowfreq 24%

basics

~20 s

The compiler is right: this stands for the receiver's actual class, which may be a subclass with extra members, so a freshly constructed Base does not satisfy it. Return this only when you genuinely hand back the receiver; a constructed copy needs an explicit assertion.

open as a page

A TypeScript class targeting ES2022 subclasses another and redeclares an inherited property just to give it a narrower type — `class Dog extends Animal { name: string; }` — and at run time `name` comes out `undefined`. What is the compiler doing, and how do you fix it?

level: seniorimportance: should knowfreq 33%

basics

~20 s

The redeclared field is emitted as a real field definition, so after super() runs it defines name on the instance as undefined, overwriting the value the base constructor set. Mark it declare name: string so the compiler emits nothing for it.

open as a page

In TypeScript 5, a standard (TC39) method decorator returns a replacement function that is installed once on the prototype. So how do you use a decorator to give each instance its own bound copy of that method, and what exactly does `context.addInitializer` run and when?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Use context.addInitializer. It registers a callback that runs at the start of each instance's construction with this bound to that instance, so the decorator can assign a bound copy of the method as an own property. A returned replacement is shared and cannot do that.

open as a page

In TypeScript 5, given `@outer @inner greet() {}` on a class method, in what order are the two decorator expressions evaluated versus applied, and where do class decorators fall relative to member decorators?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Decorator expressions evaluate top to bottom in source order when the class is defined; the resulting functions are then applied bottom up, so the one nearest the declaration wraps first. Member decorators are applied before class decorators, which see the finished class.

open as a page

In a TypeScript codebase, when is a decorator the right tool for something like dependency wiring, validation, or caching — and what does choosing a decorator cost you compared with a plain function or an explicit configuration object?

level: principalimportance: should knowfreq 38%

basics

~20 s

Decorators pay off for declarative, cross-cutting facts that belong next to the member they describe and are consumed by a generic runtime. They cost hidden control flow, module-load side effects, extra build configuration, harder testing, and type invisibility, since a decorator does not change the declared type.

open as a page

A TypeScript codebase declares every service dependency as a constructor parameter property, as in `constructor(private readonly repo: Repo, private readonly clock: Clock) {}`. As the lead, when do you keep that convention and when do you require explicit field declarations instead?

level: principalimportance: should knowfreq 32%

basics

~20 s

Keep the shorthand where classes are mostly injected dependencies and the constructor does nothing else, since it removes two of the three repetitions per dependency. Require explicit fields when values are derived, validated, or when the build must run on erasable syntax only.

open as a page

Your team's shared library exposes functions that consumers frequently pass around as callbacks. How far can TypeScript's `this` typing guarantee those functions get the receiver they expect, and where does that guarantee stop?

level: principalimportance: should knowfreq 20%

basics

~20 s

TypeScript can reject call sites and assignments it can see, using declared this parameters. It cannot guarantee anything at runtime: the annotations are erased, so any untyped caller, assertion, or third-party invoker can still supply the wrong receiver. Design so the receiver does not matter.

open as a page

In TypeScript, what do the built-in `ThisParameterType<T>` and `OmitThisParameter<T>` utility types produce for a function type, and what are they for?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

ThisParameterType<T> extracts the declared receiver type of a function type, or unknown when it declares none. OmitThisParameter<T> returns the same function type with the receiver requirement removed, which is what a function looks like once a receiver has been supplied.

open as a page

A helper is declared `function register<T>(ctor: new (...args: any[]) => T): void`, and passing your abstract base class to it fails to compile. Why, and how do you type the parameter so it is accepted?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

An abstract class's constructor type carries an abstract construct signature, which is not assignable to a non-abstract one because it cannot be called with new. Type the parameter abstract new (...args: any[]) => T when the function only needs the class, not an instance.

open as a page

In TypeScript, what does `interface Handle extends Connection {}` mean when `Connection` is a class with a private field, and which types can then satisfy `Handle`?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

An interface may extend a class type: it inherits the member types, including private and protected ones, but no implementations. Because private members are compared by declaration site, only that class and its subclasses can produce a type assignable to the interface.

open as a page

In TypeScript, what does the built-in `ThisType<T>` marker do when it appears in the contextual type of an object literal, and what compiler option must be on for it to have any effect?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

ThisType<T> is an empty marker interface. Intersected into the contextual type of an object literal, it tells the checker that this inside that literal's methods is T. It only takes effect when noImplicitThis is enabled, and it emits nothing.

open as a page

showing 31–46 of 46