Non-null Assertion Operator
What value! means in TypeScript, when non-null assertions are justified, and safer patterns that keep strictNullChecks honest.
- typescript
- non-null
The non-null assertion operator is a postfix !. It tells TypeScript: “this value is not null or undefined.” It does not check at runtime.
const el = document.getElementById('app'); // HTMLElement | null
el!.textContent = 'Hello'; // compiles; throws if el is null
Docs: Non-null assertion, strictNullChecks.
Why it exists
Under strictNullChecks, optional and nullable values force handling. Sometimes control flow analysis can’t see a guarantee you believe is true (framework refs after mount, map lookups you just inserted). ! is an escape hatch — narrower than as SomeType, still an assertion.
Prefer real narrowing
const el = document.getElementById('app');
if (!el) throw new Error('#app missing');
el.textContent = 'Hello'; // narrowed
function assertDefined<T>(v: T | null | undefined, msg: string): asserts v is T {
if (v == null) throw new Error(msg);
}
const user = findUser(id);
assertDefined(user, 'user missing');
console.log(user.name);
Runtime + types stay aligned.
Acceptable uses (narrow)
- Tests — fixtures you control:
screen.getByRole('button').closest('form')!when the structure is fixed. - Definite assignment after a guaranteed init pattern the compiler misses.
- Interop with incomplete typings where you add a follow-up issue.
// Map after set
const cache = new Map<string, User>();
cache.set(id, user);
const u = cache.get(id)!; // logically present — still document why
Even here, a small helper may be clearer:
function mustGet<K, V>(map: Map<K, V>, key: K): V {
const v = map.get(key);
if (v === undefined) throw new Error('missing key');
return v;
}
Dangerous uses
// API response
const name = data.user!.name!.trim();
// React ref during first render
inputRef.current!.focus();
Refs are null on the first render. Optional chain or effect:
useEffect(() => {
inputRef.current?.focus();
}, []);
! vs ?. vs ??
| Syntax | Effect |
|---|---|
x! |
Type-only: drop null|undefined |
x?.y |
Runtime short-circuit |
x ?? fallback |
Runtime default for nullish |
They solve different problems. Don’t use ! when you meant ?..
ESLint
Many codebases enable @typescript-eslint/no-non-null-assertion as warn/error. Allowlist tests if needed. A ban forces better guards — usually a win for product code.
Definite assignment assertion (! on properties)
class Box {
value!: string; // assigned later (DI, init method)
constructor() {
this.init();
}
init() {
this.value = 'ok';
}
}
Same family of “trust me” — prefer constructor assignment when possible.
Footguns
- Cascading
a!.b!.c!— hides an entire nullable graph. - Silencing array access:
arr[0]!when the array might be empty. - Assuming
Map.getafter async gaps still has the key.
Interview out-loud answer
“! asserts non-null to the compiler only — no runtime check. I prefer guards, asserts functions, or throws. I’ll use ! sparingly in tests or after a proven invariant, not on network data or refs at render time.”
Lint allowlists
If ESLint bans !, allowlist *.test.ts only when necessary. In app code, prefer assertDefined. Code review: any ! on network data is a reject unless paired with an immediate runtime check above it.
Related on this site
- Type assertions safely
- unknown vs any
- Type narrowing
- tsconfig strict flags
- Type guards typeof and instanceof
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.