TS2564: Why Constructor Methods Don't Count as Init
TS2564 fires when a class field is typed but never guaranteed to be set. The fix depends on WHY it's unset: a default value, constructor assignment, the definite-assignment `!`, or an optional `?`. Picking the wrong one hides real bugs.
Photo by Yancy Min on Unsplash
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.
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.
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 assignmentLast 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
- Fix TS2305: Module Has No Exported Member in TypeScript
- TypeScript Getter Setter Errors: TS1056, TS1028, TS2378 Fix
- JSX.Element vs ReactNode vs ReactElement: TS2322 Fix
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
TypeScript Getter Setter Errors: TS1056, TS1028, TS2378 Fix
TypeScript getter/setter errors come from ES target misconfiguration or type mismatches. Fix TS1056, TS1028, and TS2378 in minutes.
Fix "Property does not exist on Window" in TypeScript
Learn how to safely extend the Window interface in TypeScript using declaration merging, type assertions, and bracket notation to avoid compile-time errors.
Fix TS7016: Could Not Find Declaration File for Module
TS7016 fires when you import an untyped JavaScript module under noImplicitAny. The right fix is usually `npm i -D @types/X` — but sometimes the package already ships types, and sometimes you need to write a one-line declaration.
Browse by Topic
Find stories that matter to you.
