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
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.
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.
Fix port setting in Next.js
Change the default Next.js port when it collides with another process. Step‑by‑step fix with code, verification, and prevention tips.
Browse by Topic
Find stories that matter to you.
