Nullability, Non-Null Assertions, and Type Assertions
Introduction
null and undefined represent missing values—but they are a common source of runtime errors. With strictNullChecks, TypeScript forces you to handle absence explicitly. When you know more than the compiler, assertions (as, !) can help—but overuse hides bugs. This chapter covers safe null handling and when assertions are appropriate.
Prerequisites
- Type Narrowing and Type Guards
strict: true(includesstrictNullChecks) intsconfig.json
Nullable Types
let title: string | null = null;
function setTitle(value: string | null): void {
title = value;
}Optional properties are similar:
interface User {
name: string;
nickname?: string;
}
const u: User = { name: "Ada" };
// u.nickname is string | undefinedstrictNullChecks in Practice
Without strict null checks, null could flow anywhere. With strict mode:
function length(name: string): number {
return name.length;
}
const maybe: string | null = null;
// length(maybe); // Error: null not assignable to stringFix by narrowing or branching:
function safeLength(name: string | null): number {
if (name === null) {
return 0;
}
return name.length;
}Optional Chaining and Nullish Coalescing
interface Profile {
user?: {
email?: string;
};
}
function emailHint(profile: Profile): string {
const email = profile.user?.email;
return email ?? "no-email@placeholder.local";
}Code explanation:
?.stops evaluation and returnsundefinedif a step is null/undefined??replaces onlynullorundefined(not0or"")
Non-Null Assertion Operator !
Tell the compiler a value is not null or undefined:
function process(value: string | null): void {
if (value === null) {
return;
}
console.log(value!.length);
}After a null check, ! is usually redundant—prefer the narrowed value without !.
Warning
Do Not Use ! to Silence Errors
document.getElementById("app")! compiles but can crash if the element is missing. Validate or narrow instead.
Legitimate rare use: values guaranteed by framework lifecycle when types are too loose.
Type Assertions with as
Override the compiler’s inferred type when you have proof:
const input = document.getElementById("email") as HTMLInputElement;
const value = input.value;Code explanation:
getElementByIdreturnsHTMLElement | nullin strict DOM typingsas HTMLInputElementasserts element type—you should still check fornull
Assertion vs narrowing
| Approach | Checked by compiler | Runtime safe if wrong |
|---|---|---|
Narrowing (if, guards) | Yes | Safer |
as assertion | No | Can crash |
! non-null assertion | No | Can crash |
Prefer narrowing for user input and external data.
Angle Bracket Syntax (Legacy)
const el = <HTMLInputElement>document.getElementById("email");Equivalent to as in many cases. Avoid in .tsx files—JSX parses < as tags. Use as in React/TSX projects.
Asserting to unknown First
Safer migration from untyped data:
function parseConfig(raw: unknown): { port: number } {
if (typeof raw !== "object" || raw === null) {
throw new Error("Invalid config");
}
const obj = raw as Record<string, unknown>;
if (typeof obj.port !== "number") {
throw new Error("Invalid port");
}
return { port: obj.port };
}Even better: use a schema validator (Zod, etc.) in production apps—outside this chapter’s scope.
definite Assignment Assertion !
For class fields initialized outside the constructor path TypeScript cannot see:
class Logger {
private stream!: NodeJS.WritableStream;
init(): void {
this.stream = process.stdout;
}
write(message: string): void {
this.stream.write(message);
}
}! on the field says “I will assign before use.” Misuse causes runtime errors.
Practical Example: DOM Helper
Runtime check + type predicate—no blind as.
Style Guide
- Model absence with
| null/| undefined/?explicitly. - Narrow at boundaries (API, DOM,
JSON.parse). - Avoid
!in application logic unless documented invariant. - Use
asfor DOM casts and interop; validate when failure is possible. - Never chain
as anyto bypass errors.
FAQ
undefined vs null?
JavaScript uses both. TypeScript treats them separately unless you use ?? or optional types. Pick a team convention (often undefined for optional, null for intentional absence).
Is strictNullChecks required?
Strongly recommended. Disabling it weakens the main benefit of TypeScript for app code.
Can I assert to a union?
Yes: value as string | number—still no runtime check.
What about optional parameters vs null?
name?: string means string | undefined. name: string | null allows explicit null. Be consistent in public APIs.
Does ! affect runtime?
No. It is erased—purely compile-time.
When is satisfies better than as?
When you want inference plus validation against a type (TypeScript 4.9+). Use for config objects; covered in advanced material.