Nullable Types & Null Operators (??, ?., ??=)
Reviewed & published by Brayan K
"Missing data" is everywhere in real software — a user with no middle name, a setting that was never configured, a field left blank. In this lesson you'll learn how C# represents the absence of a value with null, how to let value types hold it, and the trio of operators (??, ?., ??=) that let you handle null safely without a pile of if checks.
Part of the free C# course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- What null means and which types can hold it
- Make value types nullable with int?, double?, bool?
- The null-coalescing operator ?? for safe defaults
- The null-conditional operator ?. for safe member access
- The null-coalescing assignment operator ??=
- HasValue, Value, and avoiding NullReferenceException
💡 Real-World Analogy
Think of a paper form with a box labelled "Middle name". A box with "James" written in it has a value. A box left blank isn't the same as a box with the word "blank" in it — it's empty. That emptiness is null. The danger is asking "how many letters are in the middle name?" when the box is blank — there's nothing to count, and the program crashes (NullReferenceException). The null operators are your way of saying "if the box is empty, do this instead" without panicking.
1. What Is null, and Nullable Value Types
Reference types (like string) have always been able to be null — they're a pointer to an object, and that pointer can point at nothing. Value types like int normally can't, because they store the number directly. Add a ? after the type to make a nullable value type that can hold null, and you gain two helpers: .HasValue and .Value.
using System;
class Program
{
static void Main()
{
// null means "no value here at all" — an empty box with no object inside.
// Reference types (like string) CAN already be null:
string? middleName = null; // the ? says "this may be null"
Console.WriteLine(middleName == null); // True
// Value types (int, double, bool, DateTime...) normally CANNOT be null.
// int age = null; // ❌ compile error: Cannot convert null to 'int'
// Add ? to a value type to make it NULLABLE — it can now hold null:
int? score = null; // a nullable int, currently empty
Console.WriteLine(score.HasValue); // False (no value stored)
score = 90; // now give it a real value
Console.WriteLine(score.HasValue); // True
Console.WriteLine(score.Value); // 90 (.Value reads the inner number)
}
}
// ✅ Expected output:
// True
// False
// True
// 902. The Null-Coalescing Operator ??
Read a ?? b as "a, unless a is null — then b". It's the single most common way to supply a default value, and it works for both nullable reference and nullable value types. You can even chain several together to pick the first thing that isn't null.
using System;
class Program
{
static void Main()
{
// ?? is the NULL-COALESCING operator: "use the left value,
// but if it's null, use the right value instead."
string? input = null;
string name = input ?? "Guest";
Console.WriteLine(name); // Guest (input was null)
string? input2 = "Maria";
string name2 = input2 ?? "Guest";
Console.WriteLine(name2); // Maria (input2 wasn't null)
// It works for nullable value types too:
int? maybeCount = null;
int count = maybeCount ?? 0; // default to 0 when null
Console.WriteLine($"Count: {count}"); // Count: 0
// You can chain it — the first non-null value wins:
string? a = null, b = null, c = "found";
Console.WriteLine(a ?? b ?? c ?? "none"); // found
}
}
// ✅ Expected output:
// Guest
// Maria
// Count: 0
// foundYour turn — fill in the two blanks so a missing name and age fall back to sensible defaults.
using System;
class Program
{
static void Main()
{
// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
string? displayName = null;
int? userAge = null;
// 1) If displayName is null, fall back to "Anonymous"
string finalName = ___; // 👉 displayName ?? "Anonymous"
// 2) If userAge is null, fall back to 18
int finalAge = ___; // 👉 userAge ?? 18
Console.WriteLine($"{finalName} is {finalAge}");
// ✅ Expected output: Anonymous is 18
}
}3. The Null-Conditional Operator ?.
When you access a member like text.Length and text is null, the program throws a NullReferenceException — the most common crash in all of .NET. The ?. operator says "only do this if the thing on the left isn't null; otherwise the whole expression is just null". Pair it with ?? for the classic "safe access with a default" one-liner.
using System;
class Program
{
static void Main()
{
// ?. is the NULL-CONDITIONAL operator (the "Elvis" / safe-navigation
// operator). It calls a member ONLY if the object isn't null.
string? text = null;
// Without ?. this would throw NullReferenceException:
// int len = text.Length; // 💥 crash if text is null
// With ?. the whole expression just returns null instead of crashing:
int? len = text?.Length;
Console.WriteLine(len.HasValue); // False (text was null, so no Length)
// Combine ?. with ?? for a clean "safe-with-default" pattern:
int safeLen = text?.Length ?? 0;
Console.WriteLine($"Length: {safeLen}"); // Length: 0
string? real = "Hello";
Console.WriteLine($"Length: {real?.Length ?? 0}"); // Length: 5
// ?. also works for indexing collections that might be null:
int[]? numbers = null;
Console.WriteLine(numbers?[0] ?? -1); // -1 (no crash)
}
}
// ✅ Expected output:
// False
// Length: 0
// Length: 5
// -14. The Null-Coalescing Assignment ??=
x ??= y is shorthand for "if x is null, set it to y". It's perfect for lazy initialisation — set something up the first time it's needed, and leave it alone after that. It reads cleaner than the equivalent if (x == null) x = y;.
using System;
class Program
{
static void Main()
{
// ??= is the NULL-COALESCING ASSIGNMENT operator.
// "Assign the right value ONLY if the left is currently null."
string? config = null;
config ??= "default-settings"; // config was null -> it becomes this
Console.WriteLine(config); // default-settings
config ??= "ignored-value"; // config already has a value now
Console.WriteLine(config); // default-settings (unchanged)
// A very common real use: lazy initialisation of a list.
System.Collections.Generic.List<string>? cache = null;
cache ??= new System.Collections.Generic.List<string>();
cache.Add("first item");
Console.WriteLine($"Items: {cache.Count}"); // Items: 1
}
}
// ✅ Expected output:
// default-settings
// default-settings
// Items: 1🧩 Reorder Challenge
These lines build a safe display name, but they're scrambled. Put them in the correct order so the code compiles and prints Hello, Guest! when raw is null.
A. Console.WriteLine($"Hello, {name}!");
B. string? raw = null;
C. string name = raw ?? "Guest";
D. static void Main()
E. {
F. }D. static void Main()
E. {
B. string? raw = null;
C. string name = raw ?? "Guest";
A. Console.WriteLine($"Hello, {name}!");
F. }
// Output: Hello, Guest!The declaration of raw must come before it's used in ??, and the method braces wrap everything.
🧠 Quick Recall
1. What does this print?
int? x = null;
Console.WriteLine(x ?? 99);99 — x is null, so ?? falls back to the right-hand value.
2. What does this print?
string? s = "Hi";
Console.WriteLine(s?.Length ?? -1);2 — s isn't null, so s?.Length is 2 and the ?? default is never used.
3. What does this print?
string? c = "set";
c ??= "other";
Console.WriteLine(c);set — c already has a value, so ??= leaves it untouched.
Putting It Together: an Optional-Field Profile
Real records are full of optional fields. Here all three null operators work together to render a profile safely, no if statements in sight.
using System;
class Program
{
// A user profile where several fields are optional (may be null).
record UserProfile(string Name, string? Nickname, int? Age, string? Bio);
static void Main()
{
var user = new UserProfile("Jordan", null, 28, null);
// Use the operators together to render a safe, friendly profile.
string display = user.Nickname ?? user.Name; // prefer nickname
string age = user.Age?.ToString() ?? "unknown"; // safe + default
int bioLength = user.Bio?.Length ?? 0; // 0 if no bio
Console.WriteLine($"Display name: {display}"); // Jordan
Console.WriteLine($"Age: {age}"); // 28
Console.WriteLine($"Bio length: {bioLength}"); // 0
// Without these operators every optional field needs an if-check —
// three lines of code shrink the noise dramatically.
}
}
// ✅ Expected output:
// Display name: Jordan
// Age: 28
// Bio length: 0Pro Tips
- 💡 Reach for ?? instead of if (x == null) whenever you just need a default value — it's shorter and harder to get wrong.
- 💡 Chain ?. through deep objects: order?.Customer?.Address?.City returns null the instant any link is missing.
- 💡 Avoid .Value unless you've checked .HasValue — prefer x ?? default which can never throw.
- 💡 Enable nullable reference types (<Nullable>enable</Nullable>) so the compiler warns you about possible nulls before users find them.
Common Errors (and the fix)
- "System.NullReferenceException: Object reference not set": you accessed a member on a null object. Use ?. or check for null first.
- "InvalidOperationException: Nullable object must have a value": you called .Value on a null nullable. Use ?? default instead.
- "CS0037: Cannot convert null to 'int'": you tried to put null in a non-nullable value type. Make it int?.
- Confusing ?? with ??=: a ?? b produces a value; a ??= b changes a. Pick based on whether you're reading or assigning.
📋 Quick Reference
| Operator | Means | Example |
|---|---|---|
| T? | Nullable type | int? n = null; |
| ?? | Default if null | n ?? 0 |
| ?. | Safe member access | s?.Length |
| ??= | Assign if null | x ??= 5; |
| .HasValue | Is it set? | n.HasValue |
| .Value | Read inner value | n.Value |
Mini-Challenge: Safe Greeting
Use all three operators together. Read the brief in the comments, build it, and check your output against the example.
using System;
class Program
{
static void Main()
{
// 🎯 MINI-CHALLENGE: Safe greeting
// Given these (possibly null) values:
string? firstName = null;
string? lastName = "Smith";
int? visits = null;
// 1. Build "fullName" preferring firstName, then lastName, then "Friend".
// 2. Build "visitCount" that is visits, or 0 when null.
// 3. Print: "Welcome <fullName>! Visit #<visitCount + 1>"
//
// ✅ Example output:
// Welcome Smith! Visit #1
// your code here
}
}🎉 Lesson Complete
- ✅ null means "no value"; T? lets value types hold it
- ✅ ?? supplies a default when the left side is null
- ✅ ?. safely accesses members and short-circuits to null
- ✅ ??= assigns a fallback only when the variable is null
- ✅ Prefer ?? default over .Value to never crash
Practice quiz
What does the ?? null-coalescing operator do in 'a ?? b'?
- Returns a, unless a is null — then b
- Always returns b
- Throws if a is null
- Returns true if a is null
Answer: Returns a, unless a is null — then b. ?? supplies a default: it yields a, but falls back to b when a is null.
What is 'int?' equivalent to?
- A string
- Nullable<int>
- An int array
- A non-nullable int
Answer: Nullable<int>. int? is shorthand for the struct Nullable<int>, adding .HasValue and .Value and allowing null.
What does the ?. null-conditional operator do when the left side is null?
- Throws NullReferenceException
- Returns the whole expression as null
- Returns 0
- Continues anyway
Answer: Returns the whole expression as null. ?. short-circuits: if the object is null, the expression becomes null instead of throwing.
What does 'x ??= y' do?
- Always sets x to y
- Sets x to y only if x is currently null
- Compares x and y
- Reads x with a default
Answer: Sets x to y only if x is currently null. ??= is null-coalescing assignment: it assigns y to x only when x is null.
Why can value types like int not normally hold null?
- They store the number directly, not a pointer to an object
- They are too large
- The compiler forbids it for performance
- They are always nullable
Answer: They store the number directly, not a pointer to an object. Value types store the value directly; add ? (making int?) to let them hold null.
What happens if you call .Value on a nullable that is currently null?
- Returns 0
- Returns null
- Throws InvalidOperationException
- Returns false
Answer: Throws InvalidOperationException. Calling .Value on a null nullable throws InvalidOperationException; check .HasValue or use ?? instead.
In 'a ?? b ?? c', which value is produced?
- The last value c always
- The first non-null value, left to right
- All three concatenated
- null
Answer: The first non-null value, left to right. Chained ?? returns the first non-null operand from left to right.
What does '.HasValue' return for 'int? score = null;'?
- True
- False
- 0
- It throws
Answer: False. A nullable holding null has HasValue == false.
What is the cleanest 'safe access with a default' one-liner for a possibly-null string s?
- s.Length
- s?.Length ?? 0
- s == null ? throw : s.Length
- s!.Length
Answer: s?.Length ?? 0. Combine ?. with ?? — s?.Length ?? 0 never throws and gives 0 when s is null.
When should you use ?? versus ??=?
- ?? reads a value with a fallback; ??= assigns a fallback into the variable
- They are identical
- ??= reads; ?? assigns
- Both only work on int
Answer: ?? reads a value with a fallback; ??= assigns a fallback into the variable. Use ?? to read a value with a fallback; use ??= to assign a fallback into the variable only if it is null.
Continue this course
- Previous: Checkpoint: C# Basics
- Next: Pattern Matching & Switch Expressions — Modern switch expressions, type/property patterns
- Quick reference: C# cheat sheet › Null Safety
Frequently asked questions
What's the difference between string and string??
In a project with nullable reference types enabled, 'string' means the compiler expects it to never be null and warns you if it might be, while 'string?' explicitly says 'this can be null'. The '?' is a hint to both you and the compiler about your intent.
Is int? the same as Nullable<int>?
Yes — exactly the same. 'int?' is just convenient shorthand for the struct Nullable<int>. It adds .HasValue and .Value members and lets the variable hold null.
When should I use ?? vs ??=?
Use ?? when you want to READ a value with a fallback (string n = input ?? "Guest"). Use ??= when you want to ASSIGN a fallback into the variable itself only if it's currently null (config ??= LoadDefaults()).
Does ?. stop the whole chain on null?
Yes. In a?.b?.c, as soon as any link is null the entire expression short-circuits to null instead of throwing a NullReferenceException. It's the cleanest way to walk a chain of objects that might be missing.
Why did .Value throw an exception?
Calling .Value on a nullable that is currently null throws InvalidOperationException. Always check .HasValue first, or use ?? to provide a default rather than reaching for .Value directly.