Fix TS2564: Property Has No Initializer in TypeScript
Technology

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.

2026-06-18
8 min read
Fix TS2564: Property Has No Initializer in TypeScript

You enable strict (or upgrade a project that just turned it on) and TypeScript stops the build:

text
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:

typescript
class Account {
  accountType = "user";   // OK — initialized by a property initializer
  retries = 0;
}

Fix 2 — Initialize in the constructor#

typescript
class GoodGreeter {
  name: string;
  constructor() {
    this.name = "hello";   // OK — assigned in the constructor body
  }
}
Why a helper method doesn't count

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."

typescript
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 !:

typescript
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.

Don't
profile!: UserProfile // slapped on just to make the red squiggle disappear
Do
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:

typescript
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:

typescript
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:

typescript
@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:

jsonc
// tsconfig.json
{ "compilerOptions": { "strictPropertyInitialization": false } }
Tradeoff

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.

When this won't work
  • You're on TypeScript before 2.7 — the flag and the prop!: T syntax don't exist there.
  • A commonly-repeated claim is that this check only applies when strictNullChecks is 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.

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

|

Have more questions? Contact us

Written by

Mahdi Br
Mahdi Br

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — no fluff

RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.