Null handling
A null that reaches code expecting a value is the most common runtime crash
there is, and the stack trace points at the dereference, not the place the
null was born. The fix is to make absence explicit in the type and to
convert or reject nulls at the edges, so interior code never has to ask.
Method
- Make absence a type, not a value. Use
Optional<T>, T | null
under strict null checks, Rust's Option, Kotlin's T?, or Swift
optionals, and turn on enforcement: TypeScript strictNullChecks,
Kotlin null safety, mypy strict. The type now forces every caller to
handle the empty case.
- Convert at the boundary, once. Parse external input (JSON, DB rows,
env vars) into a type where every field is present or explicitly
optional. After that line, interior code never re-checks, because the
boundary already decided.
- Use a null object where a neutral default is correct. An empty
collection, a
NullLogger, a Guest user with no permissions: callers
run the same path with no branch. Reserve this for cases where doing
nothing is right, not where absence is a genuine error.
- Return empty collections, never null collections.
return [], not
return null, from anything that yields a list, so callers iterate
without guarding. A null list is a guard every caller must remember, and
one eventually will not.
- Unwrap deliberately. Thread through absence with
?. and ?? (or
map, getOrElse, if let); reserve force-unwrap (!, .unwrap(),
.get()) for cases you have already proven non-null, and leave the
comment with the proof.
- Distinguish normal absence from error absence.
findUser returning
empty is routine; a required config key being absent is an error that
deserves a thrown exception naming the key, not a silent None that
surfaces three layers away.
Signals
- Does the type of every field and return state whether null is possible?
- Is there a single place where external data becomes null-safe, or are the
checks scattered through the interior?
- Does any public method return null where an empty list or a null object
would let callers drop a branch?
Boundaries
Languages without a real optional type (older Java, C) lean on annotations
(@Nullable, @NonNull) with tools like the Checker Framework or Infer;
the discipline holds even when the compiler is weaker. Database columns
that are genuinely nullable stay nullable: model the real domain, do not
paper a required field over an optional one.
1---2name: null-handling3description: Make absence explicit in the type and resolve it at the edges so the interior never dereferences a surprise null. Use when designing return types, parsing external input, or chasing null-reference crashes.4---56# Null handling78A null that reaches code expecting a value is the most common runtime crash9there is, and the stack trace points at the dereference, not the place the10null was born. The fix is to make absence explicit in the type and to11convert or reject nulls at the edges, so interior code never has to ask.1213## Method14151. **Make absence a type, not a value.** Use `Optional<T>`, `T | null`16 under strict null checks, Rust's `Option`, Kotlin's `T?`, or Swift17 optionals, and turn on enforcement: TypeScript `strictNullChecks`,18 Kotlin null safety, mypy strict. The type now forces every caller to19 handle the empty case.202. **Convert at the boundary, once.** Parse external input (JSON, DB rows,21 env vars) into a type where every field is present or explicitly22 optional. After that line, interior code never re-checks, because the23 boundary already decided.243. **Use a null object where a neutral default is correct.** An empty25 collection, a `NullLogger`, a `Guest` user with no permissions: callers26 run the same path with no branch. Reserve this for cases where doing27 nothing is right, not where absence is a genuine error.284. **Return empty collections, never null collections.** `return []`, not29 `return null`, from anything that yields a list, so callers iterate30 without guarding. A null list is a guard every caller must remember, and31 one eventually will not.325. **Unwrap deliberately.** Thread through absence with `?.` and `??` (or33 `map`, `getOrElse`, `if let`); reserve force-unwrap (`!`, `.unwrap()`,34 `.get()`) for cases you have already proven non-null, and leave the35 comment with the proof.366. **Distinguish normal absence from error absence.** `findUser` returning37 empty is routine; a required config key being absent is an error that38 deserves a thrown exception naming the key, not a silent `None` that39 surfaces three layers away.4041## Signals4243- Does the type of every field and return state whether null is possible?44- Is there a single place where external data becomes null-safe, or are the45 checks scattered through the interior?46- Does any public method return null where an empty list or a null object47 would let callers drop a branch?4849## Boundaries5051Languages without a real optional type (older Java, C) lean on annotations52(`@Nullable`, `@NonNull`) with tools like the Checker Framework or Infer;53the discipline holds even when the compiler is weaker. Database columns54that are genuinely nullable stay nullable: model the real domain, do not55paper a required field over an optional one.