You enable strict (or upgrade a project that just turned it on) and TypeScript stops the build:
Property 'name' has no initializer and is not definitely assigned
in the constructor. (2564)
This is TS2564, produced by the strictPropertyInitialization compiler flag. It is not noise — it is TypeScript refusing to let a class field be silently undefined. The right fix depends on why the field is unset, and choosing wrong trades a compile error for a runtime bug.
{ name: "TypeScript", version: "2.7+" },
{ name: "strict", version: "enabled" },
]} />
What triggers it
strictPropertyInitialization (introduced in TypeScript 2.7, and part of the strict family) checks that "each instance property of a class gets initialized in the constructor body, or by a property initializer." If neither happens and the type is not optional, you get TS2564.
Because it ships inside strict, flipping "strict": true can surface TS2564 in code that compiled fine before — the 2.7 release notes warn about exactly this.
Fix 1 — Initialize at declaration
If the field has a sensible default, set it inline:
class Account {
accountType = "user"; // OK — initialized by a property initializer
retries = 0;
}
Fix 2 — Initialize in the constructor
class GoodGreeter {
name: string;
constructor() {
this.name = "hello"; // OK — assigned in the constructor body
}
}
This is the #1 confusion with TS2564. From the TypeScript Classes handbook: the field must be initialized in the constructor itself. TypeScript does not analyze methods you invoke from the constructor, "because a derived class might override those methods and fail to initialize the members."
class Bad {
name: string; // TS2564 — still flagged
constructor() { this.init(); }
init() { this.name = "x"; } // not analyzed
}
Fix 3 — Definite assignment assertion !
When something outside the constructor reliably assigns the property — a DI container, a framework, a lifecycle hook — tell the compiler with !:
class Service {
private repo!: Repository; // "!" = definite assignment assertion
constructor() { this.wire(); }
wire() { this.repo = makeRepo(); }
}
The TypeScript 2.7 notes describe ! as relaying "that a variable is indeed assigned for all intents and purposes, even if TypeScript's analyses cannot detect so" — and cite dependency injection as the canonical reason it exists.
wrong="profile!: UserProfile // slapped on just to make the red squiggle disappear"
right="profile?: UserProfile // honest: it may be undefined until loaded — now the compiler forces you to check"
/>
The risk: ! is unchecked. If the property is never actually assigned, you get a silent runtime undefined and zero compile-time warning. Use it only when assignment is genuinely guaranteed.
Fix 4 — Mark it optional or union with undefined
If the property may legitimately be absent, say so — and let the compiler force you to handle the undefined case:
class Form {
email?: string; // optional
address: string | undefined; // explicit union, equivalent intent
}
The handbook's guidance: "if we truly meant for [it] to potentially be undefined, we should have declared it" that way.
Fix 5 — Constructor parameter properties
The public/private/protected/readonly prefix on a constructor parameter declares and assigns the field in one step, so it satisfies the check with no body:
class Point {
constructor(public readonly x: number, private y: number) {}
// x and y are declared and assigned — no TS2564
}
Framework note: Angular @Input
Angular treats @Input properties as optional by design. The idiomatic options are a default value, ?, or — for @Input({ required: true }) — the definite assignment assertion:
@Input() id = 'default_id'; // default
@Input() movie?: Movie; // optional
@Input({ required: true }) data!: Data; // required + definite assignment
Last resort — disable the flag
You can turn it off, but it removes the guarantee for every class in the project:
// tsconfig.json
{ "compilerOptions": { "strictPropertyInitialization": false } }
Disabling it reintroduces the "uninitialized field is silently undefined" bug class everywhere — strictly worse than a targeted ! or ? on the one property that needs it.
- You're on TypeScript before 2.7 — the flag and the
prop!: Tsyntax don't exist there. - A commonly-repeated claim is that this check only applies when
strictNullChecksis also on. The official pages I cite don't state that dependency — don't rely on it without verifying against your own compiler version.
Which fix to use
| Situation | Fix |
|---|---|
| There's a sensible default | Initialize at declaration |
| Always set during construction | Assign in the constructor body |
| Assigned by DI / framework / lifecycle | Definite assignment prop!: T
|
| May genuinely be missing | Optional prop?: T
|
| Passed into the constructor | Parameter property |
Official references: strictPropertyInitialization, TypeScript 2.7 release notes, Classes handbook.
Related Articles
- TypeScript Migration Guide: Moving a JS Codebase in 2026
- Supabase Auth Error Codes in TypeScript: A Typed Handler
- Interfaces vs Types in TypeScript: 2026 Best Practices
Frequently Asked Questions
What does "has no initializer and is not definitely assigned" mean?
It is TS2564 from strictPropertyInitialization. You declared a non-optional class property, but TypeScript can't prove it gets a value before use. Give it a default, assign it in the constructor, or mark it ! or ?.
Why does assigning the property in a method not fix TS2564?
TypeScript only analyzes the constructor body, not methods you call from it — a subclass could override that method and skip the assignment. Assign in the constructor directly, or use prop!: T if a framework truly sets it.
Is the definite assignment assertion (!) safe?
Only when assignment is actually guaranteed (e.g. by DI). It's unchecked: if the property is never assigned, you get a silent runtime undefined. Use it deliberately.
Originally published at https://www.iloveblogs.blog
Top comments (0)