TypeScript reports "Property 'name' has no initializer and is not definitely assigned in the constructor" on a class field. Which compiler check produces that error, and what are the legitimate ways to satisfy it?
answer
- part of the strict family
- needs null checking to mean anything
- the constructor body, nothing deeper
- widen the type or assert it
- declare and abstract are exempt
basics
~20 sThe strictPropertyInitialization check produces it, and it needs strictNullChecks on. Satisfy it by giving the field an initializer, assigning it directly in the constructor body, including undefined in its type, or asserting with a definite-assignment !.
solid answer
~50 sThat is `strictPropertyInitialization`, which is switched on by `strict` and requires `strictNullChecks` to be enabled. It says a declared instance field whose type does not include `undefined` must provably hold a value by the time the constructor finishes. There are four honest ways out: give the field an initializer; assign it directly in the constructor body; widen the type — `string | undefined` or an optional `name?: string` — so `undefined` is legal; or add a definite-assignment assertion, `name!: string`, which silences the check without changing the type. A field marked `declare` or `abstract` is exempt because the class is not the thing that creates it. The gotcha people trip on is that the analysis reads the constructor body only: moving the assignments into an `init()` method the constructor calls does not count, and the error stays.
code
typescript · 26 linesclass Session {
// Error: Property 'token' has no initializer and is not definitely
// assigned in the constructor. The checker analyses the constructor
// body only and does not follow the call into init().
token: string;
constructor() {
this.init();
}
private init(): void {
this.token = "abc123";
}
}
class FixedSession {
token: string;
constructor() {
this.token = FixedSession.makeToken(); // assigned in the constructor body
}
private static makeToken(): string {
return "abc123";
}
}go deeper
Recognise the message and know the two everyday fixes: give the field a default value, or assign it in the constructor. Say which flag family it belongs to.
Explain that the analysis reads only the constructor body, so a helper method does not count, and lay out all the escape routes including the optional type, the definite-assignment assertion, and declare.
Treat the error as a design prompt: argue when a field should be typed as possibly-undefined versus when ! is defensible because a framework or test lifecycle populates it, and say how you would keep that from spreading.
Own the policy across a codebase — whether ! is allowed at all, which injection or lifecycle patterns earn an exemption, and how you would migrate an existing codebase onto this check without a wave of blanket assertions.
## What the check is The error comes from `strictPropertyInitialization`, one of the checks that `strict` turns on. Its rule: every declared instance field whose type does not admit `undefined` must be provably assigned by the end of the constructor. If the compiler cannot prove it, you get "Property 'x' has no initializer and is not definitely assigned in the constructor". It depends on `strictNullChecks`. Without null checking, `undefined` is assignable to `string`, so "the field might be undefined" is not a statement the type system can even make — and the compiler refuses the combination, telling you `strictPropertyInitialization` cannot be specified without `strictNullChecks`. ## Why it exists Without it, this compiles and lies to you: ```ts class User { name: string; // never assigned } new User().name.toUpperCase(); // TypeError at run time ``` The declared type says `string`, the runtime value is `undefined`, and every downstream call site trusts the declaration. The check closes the most common way a class hands out a value that does not match its own annotation. ## The analysis is constructor-body-only This is the part interviews probe. The compiler performs a control-flow analysis over the *constructor body itself*. It does not follow calls. So the extremely natural refactor below still errors: ```ts class Session { token: string; // Error: no initializer, not definitely assigned constructor() { this.init(); } private init() { this.token = "abc123"; } } ``` The assignment genuinely happens, but proving that would require inter-procedural analysis the checker does not attempt — and could not do soundly, since `init` is overridable. The fix is either to inline the assignment into the constructor or to have the helper return the value: `constructor() { this.token = this.makeToken(); }`. ## The ways to satisfy it, and what each costs **Initializer.** `name = "anonymous"` — the simplest and the one to prefer when a sensible default exists. Costs nothing. **Constructor assignment.** `constructor(name: string) { this.name = name }` — the normal shape for a value that comes from the caller. Also satisfied by an assignment inside a branch, as long as every path through the constructor assigns. **Widen the type.** `name: string | undefined` or `name?: string`. This is the honest option when the field really can be absent: it changes the type, so every reader is now forced to narrow before using it. That extra work at the call sites is the *feature*, not the cost — but it is a contract change, so it is the wrong answer when the field is genuinely always set. **Definite assignment assertion.** `name!: string` keeps the type as `string` and tells the compiler to stop asking. Nothing is verified; you have taken on the obligation personally. It is legitimate when something outside the constructor reliably populates the field — a dependency-injection container, a framework lifecycle hook, a test fixture's setup function — and indefensible when it is used to bulk-silence errors, because it produces exactly the lie the check was written to catch. **declare.** `declare name: string` says the property exists but this class does not create it and the compiler should emit nothing for it. Used for fields supplied by a base class or an external mechanism. **abstract.** An `abstract` property is a requirement placed on subclasses, so the abstract class is not expected to initialize it. ## Reading the error as a design signal Most occurrences of this error are the type system asking a real question: is this field always present, or not? If the answer is "always", move the assignment into the constructor. If it is "not until later", say so in the type and let readers deal with it, or model the two states as separate types rather than one type with a hole in it. Reaching for `!` first turns a design question into a suppressed warning, which is why a blanket `!` habit reads badly in review. ## Practical note The check looks at declared fields. A property that only ever appears as `this.something = ...` inside a method, with no declaration at all, produces a different error — the property does not exist on the type — and the fix there is to declare it, at which point this check applies to it too.
- Why doesn't moving the assignments into a method the constructor calls satisfy the check?Because the analysis is limited to the constructor body — it does not follow calls. Nor could it safely: the method may be overridden by a subclass whose version does not assign the field. Return the value from the helper and assign it in the constructor, and the check passes.
- What is the practical difference between fixing it with `name?: string` and fixing it with `name!: string`?`?` changes the type to include `undefined`, so every reader must narrow before use — the risk is pushed to the call sites and checked. `!` leaves the type as `string` and merely suppresses the check, so an unset field flows out as a `string` that is actually `undefined`. `?` is honest; `!` is a promise you have to keep yourself.
- Does the error still appear if the field's declared type already includes undefined?No. The check only applies where `undefined` is not an acceptable value for the declared type, so `name: string | undefined` and the optional form `name?: string` are both exempt — an unassigned field genuinely matches the declared type in those cases.
saying these in an interview costs you the question
- Adds ! to every field to clear the errors
- Thinks the check runs at run time
- Believes assigning in a constructor-called method counts
- Says the flag works without strictNullChecks
- Confuses it with noImplicitAny