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 pageshowhide
explore
- Access Modifiers & #private Fields5 questions
- readonly Members & Field Initialization4 questions
- Parameter Properties4 questions
- Abstract Classes & Members4 questions
- implements vs extends & Interface Gaps5 questions
- this Parameters & noImplicitThis5 questions
- Polymorphic this & Fluent APIs4 questions
- Standard (TC39) Decorators5 questions
- Legacy experimentalDecorators5 questions
- Decorator Metadata & Use Cases5 questions
questions
page 2 of 2A 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.
basics
~20 sThe 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.
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?
basics
~20 sNo. 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.
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?
basics
~20 sAn 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.
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?
basics
~20 sEach 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.
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?
basics
~20 sIt 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".
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?
basics
~20 sThe 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.
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?
basics
~20 sThe 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.
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?
basics
~20 sUse 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.
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?
basics
~20 sDecorator 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.
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?
basics
~20 sDecorators 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.
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?
basics
~20 sKeep 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.
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?
basics
~20 sTypeScript 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.
In TypeScript, what do the built-in `ThisParameterType<T>` and `OmitThisParameter<T>` utility types produce for a function type, and what are they for?
basics
~20 sThisParameterType<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.
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?
basics
~20 sAn 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.
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`?
basics
~20 sAn 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.
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?
basics
~20 sThisType<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.
showing 31–46 of 46