Non-null assertion (!)

Removes null and undefined from an expression's type without any runtime check.

Signature
expr!
  • Added in TypeScript 2.0

Parameters

ParameterTypeDescription
$exprany expressionThe expression to re-type. The postfix `!` binds tighter than property access, so `x!.y` asserts on `x`, not on `x.y`.

Return value

NonNullable<typeof expr>The same value at runtime, typed with null and undefined excluded.

Errors

Never itself — it emits nothing. The very next operation throws a TypeError if the value really was null or undefined.

Changelog

  • PHP 2.0Introduced alongside --strictNullChecks: "the operation x! produces a value of the type of x with null and undefined excluded", and is simply removed in the emitted JavaScript.
  • PHP 2.7The definite assignment assertion reused the same character in declaration position (`let x!: number`, `field!: string`) to tell the checker a variable is assigned before use. Different feature, same punctuation.
  • PHP 3.7Optional chaining (`?.`) and nullish coalescing (`??`) landed, giving checked alternatives that do survive to runtime.

! is a type assertion with one job: strip null and undefined from the type of the expression it follows. Like every assertion it is erased before the code runs. The compiler stops complaining; nothing else changes.

When to reach for it

  • An API returns a wider type than reality allows — Map.get on a key you just set, a regex match you know succeeded, an array index inside a bounds-checked loop.
  • A validation function narrowed the value, but in a way control-flow analysis cannot follow across a function boundary.

In both cases you are asserting a fact the checker cannot see. If you cannot state that fact in one sentence, you probably want ?., ??, or an explicit if instead — those emit real code and are still there when your assumption turns out to be wrong.

What trips people up

Two different features share the character. Postfix ! on an expression is the non-null assertion. ! in a declaration — url!: string on a class field, or let x!: number — is the definite assignment assertion, which promises the value is set before first use. Neither one initialises anything.

Also watch the binding: a!.b.c asserts only on a. If b can be null too, you need a!.b!.c, which is usually the moment to stop and add a real check.

Example

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

When to use it

  • Reading a Map or Record entry you populated a few lines earlier and know exists.
  • Using a class field that a framework injects after construction, declared with a definite assignment assertion.
  • Accessing a regex capture group after you have already tested that the match succeeded.

Frequently asked questions

What does the exclamation mark after a variable do in TypeScript?

It removes `null` and `undefined` from that expression's type for the checker only. The operator is deleted during compilation, so the emitted JavaScript is identical to the version without it.

Is the non-null assertion the same as the `!` on a class field?

No. `expr!` is the non-null assertion on an expression; `field!: string` on a declaration is the definite assignment assertion added in TypeScript 2.7, which tells the checker the field is assigned before use. Neither assigns a value.

What should I use instead of `!`?

Use optional chaining (`?.`) with nullish coalescing (`??`), or an explicit `if (x)` narrowing — these survive compilation and handle the null case. Reserve `!` for facts you can state precisely and the checker cannot see.

More examples

Map.get and the assertion that was wrong

Prints `coffee: 3.50` then `missing key: TypeError`. Both assertions type-check identically — the checker cannot tell which keys the Map holds — so the only thing separating the safe line from the crash is whether your assumption happened to be true.

Example · typescript
const prices = new Map<string, number>([
  ["coffee", 3.5],
  ["tea", 2.25],
]);

// get() is typed number | undefined even for a key you just wrote.
const coffee = prices.get("coffee")!;
console.log("coffee:", coffee.toFixed(2));

// Identical code, missing key. Compiles clean, fails at runtime.
try {
  const juice = prices.get("juice")!;
  console.log(juice.toFixed(2));
} catch (err) {
  console.log("missing key:", (err as Error).name);
}

Definite assignment, and the checked alternatives

Prints `before open: undefined`, then `after open: wss://example.test`, then `no value`. The first line shows what the declaration `!` actually bought you — nothing at runtime. The last line shows `?.` and `??`, which do emit code and handle the null case instead of asserting it away.

Example · typescript
class Connection {
  // Definite assignment assertion: same character, different feature.
  // Promises tsc the field is assigned before it is read.
  url!: string;

  open(url: string) {
    this.url = url;
  }
}

const c = new Connection();
console.log("before open:", c.url);

c.open("wss://example.test");
console.log("after open:", c.url);

// Prefer these when the value might genuinely be absent:
const maybe: string | null = null;
console.log(maybe?.length ?? "no value");

Discussion

  • Be the first to comment on this lesson.