Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every way to handle null

string?, is null, ?., ??, ??=, the ! operator, int? and returning null on purpose. What each one says about a missing value, and which to reach for.

Start with one question: is "missing" a real answer here, or a bug?

  • It should never be missing (a customer's name, a required setting) → keep the type non-nullable (string) and make sure it gets a value: a constructor, required, or a starting value. If a caller can still pass null against the warnings, a guard clause at the top of the method stops it there.
  • Missing is normal and has a sensible default (no nickname, use the first name) → ??, or ?. followed by ??: user.Nickname ?? user.FirstName, order?.Customer?.Email ?? "(none)".
  • Missing needs its own behaviour (no account for that email) → test it: if (found is null) { ... return; }, or if (input is string text) to test and name it in one go.
  • A number, date or bool that might be absent (a form's optional age, a rating nobody gave yet) → int?, DateOnly?, bool?, read with ??, HasValue or is int n.
  • You're writing a lookup that can fail → return T? so every caller's compiler warns them to check. If callers need to know why it failed, a Try method or a result object says more (see "Every way to handle failure").
  • The compiler is wrong and you can prove it → !, last, with the reason written beside it. A ! that's wrong is a crash waiting to happen.

A nullable warning is the compiler asking you which of these cases you meant. Answer it; don't silence it.

Where null comes from

The first null you meet. Console.ReadLine gives back null when input runs out, and ?? fills in a default.

A reference-type variable can point at no object at all, and using one crashes with NullReferenceException.

How nulls sneak in, and how string versus string? turns a run-time crash into a compile-time warning.

Saying whether null is allowed

T? means "might be null", plain T means "never". Includes CS8618 for properties no constructor sets.

Nullable for a number that might be missing, lifted operators, and why T? means two different things.

A required property must be set when the object is created, so it can stay non-nullable.

Working with a value that might be null

Read through a null, fill in a default, assign only when empty, or write through a maybe-null reference (C# 14).

x is null and x is not null are constant patterns; is string s tests and names in one step.

Formal. With nullable reference types enabled, the compiler tracks each reference as not-null or maybe-null along every code path. It warns when a maybe-null value is dereferenced (CS8602), or stored or passed where a non-nullable type is expected (CS8600, CS8601, CS8604, CS8625). A null check (is not null, != null, a type pattern, an early return) moves the variable's state to not-null, and so does x ??= value; an expression like x ?? "default" produces a not-null value but leaves x itself maybe-null. The null-forgiving ! changes the state to not-null for the compiler only; it emits no run-time check. The analysis produces warnings, never different generated code.

In plain English. The compiler reads your code like a careful reviewer and flags every place a missing value could crash. Checking for null satisfies it. Writing ! only tells the reviewer to stop complaining; it doesn't make the value exist.

Example. string? name = Find(); Console.WriteLine(name.Length); warns CS8602; if (name is not null) Console.WriteLine(name.Length); does not.

Why this rule exists. Null dereferences were among the most common run-time crashes; catching them while you type is far cheaper than in production.

How the compiler decides whether a variable is maybe-null at each line, and what makes it change its mind.

When the compiler lacks information you have. ! silences the warning; it doesn't check anything.

Returning null on purpose

When returning null is the honest answer, and when a Try method or result object says more.

The OrDefault forms return default(T), which is null for reference types, instead of throwing when nothing matches.

Fixing a warning the right way

The five questions to ask, in order, and a table from CS8600 to CS8629 with the usual fix for each.