Skip to content
TypeScript ts assertions 4 min read

Non-null Assertion (!)

The non-null assertion operator is a postfix ! that tells the compiler “this value is definitely not null or undefined, even though its type says it could be.” It removes null and undefined from a value’s type at the point of use, unlocking properties and method calls the compiler would otherwise reject. Like the as assertion, it is purely a compile-time instruction with no runtime effect — it does not check anything, so if you are wrong the program crashes exactly as untyped JavaScript would. It is most useful when you know an invariant the compiler cannot see.

The basic syntax

Append ! to any expression whose type includes null or undefined. The result has those types stripped away.

function getLength(text: string | null) {
  // ❌ Error: 'text' is possibly 'null'.
  // return text.length;

  return text!.length; // ✅ asserts text is not null
}

The ! only matters under strictNullChecks (part of strict). Without that flag, null and undefined are already assignable everywhere and the operator is redundant. Always develop with strict mode on, which makes the operator meaningful and its use a conscious decision.

A common, legitimate use: the DOM

document.querySelector returns Element | null because the selector might match nothing. When you control the HTML and know the element exists, ! is concise and reasonable.

const root = document.querySelector("#app")!;
root.classList.add("ready"); // ✅ no null check needed

If you are wrong — the element isn’t there — root is null at runtime and .classList throws a TypeError. The assertion buys brevity at the cost of safety, so reserve it for cases where the invariant is genuinely guaranteed.

Definite assignment assertions

The ! also appears on declarations to promise a property or variable will be assigned before it is read, even though the compiler cannot prove it. This is the “definite assignment assertion.”

class UserService {
  // initialized by a framework/DI container, not in the constructor
  private repository!: Repository;

  init(repo: Repository) {
    this.repository = repo;
  }
}

let token!: string; // assigned later by setup code

Without the ! on repository, strict mode raises Property 'repository' has no initializer. The assertion suppresses that, but the burden is on you to ensure assignment really happens first.

Assertion vs optional chaining vs guard

The non-null assertion is one of several ways to deal with possibly-absent values, but it is the only one that provides no safety. Compare the options before choosing it.

ApproachSyntaxIf value is null/undefinedSafety
Non-null assertionvalue!.propThrows at runtimeNone
Optional chainingvalue?.propShort-circuits to undefinedSafe
Nullish coalescingvalue ?? fallbackUses fallbackSafe
Explicit guardif (value) { ... }Skips the blockSafe, narrows type
// Safe alternatives that survive a null value:
const len1 = text?.length;        // number | undefined
const len2 = text?.length ?? 0;   // number
if (text) {
  const len3 = text.length;       // number, narrowed by the guard
}

Prefer optional chaining, nullish coalescing, or an explicit guard whenever the value could realistically be absent. Save ! for invariants you can guarantee but the compiler cannot express.

Why ! can hide real bugs

Because the operator silences the compiler instead of handling the absence, it can mask logic errors that later surface as runtime crashes far from their cause.

interface Config {
  apiUrl?: string;
}

function connect(config: Config) {
  // Compiles, but throws if apiUrl was never set
  return fetch(config.apiUrl!);
}

// Safer: handle the missing case explicitly
function connectSafely(config: Config) {
  if (!config.apiUrl) {
    throw new Error("apiUrl is required");
  }
  return fetch(config.apiUrl); // narrowed to string, no ! needed
}

The connectSafely version turns a silent assertion into a clear, intentional error with a useful message — almost always the better trade.

Disallowing it in code review

Teams that want to ban or flag the operator can enable the ESLint rule @typescript-eslint/no-non-null-assertion. It surfaces every ! so each use is a deliberate, reviewed exception rather than a reflex.

// .eslintrc.json
{
  "rules": {
    "@typescript-eslint/no-non-null-assertion": "warn"
  }
}

Best Practices

  • Use ! only when you can guarantee the value is present and the compiler simply cannot see it.
  • Prefer ?., ??, or an explicit if guard whenever absence is genuinely possible.
  • Replace value! with an explicit throw when missing data should be a real, debuggable error.
  • Reserve definite assignment assertions (prop!: T) for DI/framework-initialized fields.
  • Develop with strictNullChecks on so the operator is meaningful and every use is intentional.
  • Consider the no-non-null-assertion ESLint rule to keep each usage under review.
  • Remember ! is erased at compile time — it never checks, so being wrong means a runtime crash.
Last updated June 29, 2026
Was this helpful?