Living with strictNullChecks
Advanced · 20 min read · ▶ live playground · ✦ checkpoint
strictNullChecks means null and undefined are real values you must model before you use them. A string is not secretly nullable; if absence can happen, the type must say string | null, string | undefined, or both.
This course has been strict from the start. Now you collect the daily patterns: optional chaining, nullish coalescing, early returns, default objects, NonNullable, and the one operator that deserves suspicion every time you type it.
Model absence honestly
Use undefined for "not provided" or "missing from an object." Use null for an explicit empty value your domain writes on purpose. If both can appear, write both.
→ ada - never seen - No bio yet
→ Grace Hopper - hidden last seen - debugging pioneer
The break-it line fails because displayName might be null. That is the point of strictNullChecks: the checker refuses to let "usually a string" pretend to be "definitely a string."
Optional properties are part of the same story. bio?: string means the property may be omitted, so reading it gives string | undefined. A required lastSeenAt: string | null | undefined means the property exists, but its value may be a string, null, or undefined. Those are different promises, and strict code keeps them visible.
?. and ?? handle the common path
Optional chaining (?.) stops a property access or method call when the value on its left is null or undefined. Nullish coalescing (??) chooses a fallback only when the value on its left is null or undefined.
Together, they express "read through this maybe-missing path, then use a fallback if the path was missing."
→ acct-1: Mina gets daily email
→ acct-2: (anonymous) gets weekly email
→ acct-3: (anonymous) gets weekly email
?. does not magically make everything present. It gives you a value that may be undefined, which is why ?? is the natural next move. If the whole path is missing, the fallback wins. If the path exists, the real value flows through.
?? is not ||
|| is an old JavaScript fallback operator that treats every falsy value as missing: 0, "", false, null, undefined, and NaN. That is often wrong in TypeScript code because 0, "", and false can be perfectly valid values.
?? is narrower. It falls back only for null and undefined.
→ wrong: pageSize=25, nickname=(anonymous), compactMode=true
→ right: pageSize=0, nickname=, compactMode=false
The break-it line fails because preferences.pageSize is still number | null | undefined. A fallback expression can produce a definite number; the original property is still nullable because the original object has not changed.
! is a promissory note
The non-null assertion operator, value!, tells the checker "this is not null or undefined here." It does not check at runtime. It erases, like the rest of the type layer.
That makes ! honest only when surrounding code has already proved the value is present and the checker cannot see the proof. Used as a squiggle eraser, it moves the crash from compile time to runtime.
→ unsafe crashed: Cannot read properties of undefined (reading 'toUpperCase')
→ none selected
→ LESSON-15
→ OK
alreadyProvedSelectedLabel is the narrow honest case. The function checks selectedId first, then calls a tiny callback whose return type loses the checker's narrowed fact. The ! records a fact the surrounding code already proved.
Most ! uses are not that honest. If deleting the earlier guard would make the assertion lie, keep the guard close and obvious. If there is no guard, do not use !; write the guard.
Eliminate null checks
The best null check is the one that happens once near the boundary and never leaks into the rest of the function. Three patterns do most of the work:
- Early returns stop the missing case before the main path.
- Default objects turn a missing nested object into a real object with safe fields.
NonNullable<T>names the type afternullandundefinedhave been removed.
→ not ready: missing title
→ Living with strictNullChecks by Unassigned (no tags)
→ by Grace
NonNullable<DraftArticle["author"]> is a type-layer cleanup: it removes null and undefined from the author type. It does not validate data by itself. The runtime cleanup happens in const author = draft.author ?? defaultAuthor, which makes the main path deal with an Author, not a maybe-author.
Section 16 starts testing. The nullability habit carries straight into tests: write cases for the missing path once, then keep the success path clean and narrow.
Checkpoint
Answer all three to mark this lesson complete