Fix TS2564: Property Has No Initializer in TypeScript
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.
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#
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.
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
Fix TS2305: Module Has No Exported Member in TypeScript
TS2305 says the export you're importing doesn't exist under that name. The cause is almost always one of: a typo, default-vs-named confusion, a CJS/ESM interop mismatch, or @types drifting from the runtime package. Each has a precise fix.
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.
JSX.Element vs ReactNode vs ReactElement: TS2322 Fix
Fix TS2322 by understanding when ReactNode, JSX.Element, and ReactElement apply in React + TypeScript component typing.
Browse by Topic
Find stories that matter to you.
