Learn why TypeScript throws TS2564 for uninitialized class properties under strictPropertyInitialization, and how to properly initialize them.
TS2564 fires when you declare a class property with a type annotation but don't give it a value — either at the declaration site or inside the constructor. TypeScript's strictPropertyInitialization check (part of strict mode) wants to guarantee that every property has a value before anyone tries to read it.
Without this check, accessing an uninitialized property would silently return undefined, even if the type says it should be a string or number. TS2564 prevents that entire category of bugs.
// The general shape of the error:
// Property 'email' has no initializer and is not definitely assigned in the constructor.The most straightforward case — you declared a property but forgot to initialize it.
// ❌ Broken
class UserService {
baseUrl: string
//~~~~~~ Error: Property 'baseUrl' has no initializer
// and is not definitely assigned in the constructor.
timeout: number
//~~~~~~~ Same error
async fetchUser(id: string) {
return fetch(`${this.baseUrl}/users/${id}`)
}
}// ✅ Fixed — provide default values
class UserService {
baseUrl: string = "https://api.example.com"
timeout: number = 5000
async fetchUser(id: string) {
return fetch(`${this.baseUrl}/users/${id}`)
}
}TypeScript's analysis only looks at the constructor for initialization. If you set properties in a separate method called from the constructor, TypeScript can't verify it.
// ❌ Broken
class DatabaseConnection {
host: string
port: number
// ~~~~~~ Error: Property 'port' has no initializer
constructor(config: { host: string; port: number }) {
this.initialize(config) // TypeScript can't see into this call
}
private initialize(config: { host: string; port: number }) {
this.host = config.host
this.port = config.port
}
}// ✅ Fixed — initialize directly in the constructor
class DatabaseConnection {
host: string
port: number
constructor(config: { host: string; port: number }) {
this.host = config.host
this.port = config.port
}
}Frameworks like Angular or decorators in libraries like TypeORM set properties outside the constructor, which TypeScript can't track.
// ❌ Broken
class UserComponent {
userId: string
//~~~~~ Error: Property 'userId' has no initializer
// Angular sets this via @Input() before ngOnInit
ngOnInit() {
this.loadUser(this.userId)
}
loadUser(id: string) { /* ... */ }
}// ✅ Fixed — use the definite assignment assertion (appropriate here)
class UserComponent {
userId!: string // '!' tells TypeScript: "I guarantee this will be set"
ngOnInit() {
this.loadUser(this.userId)
}
loadUser(id: string) { /* ... */ }
}Assign a default value at the declaration. This is the safest and most readable approach:
class Config {
retries: number = 3
verbose: boolean = false
}Initialize in the constructor. If the value depends on constructor arguments, assign it there:
class Config {
retries: number
constructor(retries: number) {
this.retries = retries
}
}Make the property optional. If the property genuinely might not have a value, add ?:
class SearchFilter {
query?: string // undefined until the user types something
}Use the definite assignment assertion (!) as a last resort. This tells TypeScript you guarantee the property will be assigned before it's read. Only use this when a framework or external code handles initialization:
class Widget {
element!: HTMLElement // set by the framework after construction
}Don't disable strictPropertyInitialization. Turning off this check in tsconfig.json silences the error everywhere, which defeats the purpose of using strict mode.
TS2564 is triggered by the strictPropertyInitialization compiler option (enabled by default when strict is true). It checks that every class property declared with a type annotation is assigned a value either at the declaration or inside the constructor. If TypeScript can't prove the property will have a value by the time the constructor finishes, it raises this error.
The best fix depends on your situation. If there's a sensible default, assign it directly (count: number = 0). If the value comes from outside, take it as a constructor parameter. If the property is truly optional, add a ? to the declaration. Only use the ! assertion when you're certain an external mechanism (like a framework) will assign the value before it's read.
The ! modifier (name!: string) is a promise to the compiler that you'll handle initialization yourself. It's appropriate for framework-managed properties (Angular's @Input(), TypeORM's @Column()) where the framework guarantees assignment. It's inappropriate as a shortcut to silence the error when you simply forgot to initialize something — in that case, you're hiding a real bug.
Get the latest TypeScript tips, tutorials, and course updates delivered straight to your inbox.