Definite Assignment Assertions

Promise the compiler a variable or field really does get assigned, just not where it can see.

Under strictNullChecks (and strictPropertyInitialization), TypeScript insists that class fields and variables are assigned before use. Usually that is exactly right. But sometimes assignment happens somewhere the compiler cannot follow β€” a DI framework, a lifecycle hook, a test beforeEach, a late setup call.

The definite assignment assertion is a lone ! after the name, meaning "this will be assigned, take my word for it."

class Widget {
  private el!: HTMLElement; // assigned later in init(), not the constructor

  init(root: HTMLElement) {
    this.el = root;
  }
}

let ready!: boolean; // assigned before first use elsewhere

Like the non-null !, it is a compile-time promise with zero runtime effect. Use it only when you genuinely control the initialization order.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • A class initialises a database connection in a separate init() method rather than the constructor, requiring ! on the field.
  • A module-level variable is populated by a before-hook in a test framework, needing ! to silence the uninitialised error.
  • A lazy-loaded resource field uses ! because the DI container guarantees injection before any method is called.

More examples

Definite assignment on class field

The ! assertion tells TypeScript that connection will be assigned before query() is called, even though the constructor does not set it.

Example Β· ts
class DataService {
  private connection!: DbConnection; // initialised in init(), not constructor

  async init(): Promise<void> {
    this.connection = await DbConnection.open();
  }

  async query(sql: string): Promise<unknown[]> {
    return this.connection.execute(sql);
  }
}

interface DbConnection { execute(sql: string): Promise<unknown[]>; open?(): void; }
declare namespace DbConnection { function open(): Promise<DbConnection>; }

Definite assignment on a variable

Uses ! on a module-level variable to suppress the 'used before assignment' error when a setup function fills it in.

Example Β· ts
let config!: { host: string; port: number };

function loadConfig(): void {
  config = { host: 'localhost', port: 5432 };
}

loadConfig();
console.log(config.host); // safe at runtime

Without ! β€” the compiler error

Contrasts the commented-out field (which errors) with the ! version to clarify exactly when the assertion is needed.

Example Β· ts
class Repo {
  // db: Database; // Error: Property 'db' has no initializer
  db!: Database;   // OK with definite assignment assertion

  constructor(private setup: () => Database) {
    this.db = setup();
  }
}

declare class Database { query(sql: string): unknown[]; }

Discussion

  • Be the first to comment on this lesson.
Definite Assignment Assertions β€” TypeScript | SoundsCode