Non-null assertion (!)
Removes null and undefined from an expression's type without any runtime check.
expr!- Added in TypeScript 2.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$expr | any expression | The 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.geton 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
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.
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.
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