ESC

Type to search the knowledge base.

Non-null Assertion Operator

What value! means in TypeScript, when non-null assertions are justified, and safer patterns that keep strictNullChecks honest.

intermediate3 min read
  • 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)

  1. Tests — fixtures you control: screen.getByRole('button').closest('form')! when the structure is fixed.
  2. Definite assignment after a guaranteed init pattern the compiler misses.
  3. 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

  1. Cascading a!.b!.c! — hides an entire nullable graph.
  2. Silencing array access: arr[0]! when the array might be empty.
  3. Assuming Map.get after 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.

Further reading

Related guides