100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Null Safety in Dart

How Dart's sound null safety distinguishes nullable from non-nullable types, and how operators like ?., ??, ??=, and ! plus the late keyword let you work with nullable values safely.

FoundationsIntermediate9 min readJul 10, 2026
Analogies

Null Safety in Dart

Sound null safety, mandatory since Dart 3, means every type in Dart's type system is non-nullable by default. Writing String name = 'Alice'; guarantees name can never be null anywhere in the program — not just 'probably won't be,' but statically, provably cannot be, because the compiler verifies it. To allow null, you must opt in explicitly by adding a ? to the type: String? name; declares a variable that can hold either a String or null. This flips the default from most older languages, where every reference type could silently be null unless you added extra validation, and it eliminates the infamous 'null pointer exception' as a runtime surprise for any variable that wasn't explicitly marked nullable.

🏏

Cricket analogy: A non-nullable String name is like a team sheet that guarantees exactly eleven named players will walk out, no blank slot is ever legal, whereas String? is like a squad list that explicitly allows a 'TBD' twelfth-man slot.

Null-Aware Operators

Dart provides a family of operators for working with nullable values concisely. The null-aware access operator ?. calls a method or reads a property only if the receiver isn't null, short-circuiting to null otherwise — user?.email returns null instead of throwing if user is null. The if-null operator ?? provides a fallback: name ?? 'Guest' evaluates to name if it's non-null, or 'Guest' otherwise. The null-aware assignment operator ??= only assigns if the variable is currently null: count ??= 0; sets count to 0 only the first time, leaving any existing value untouched on subsequent calls. Together these three operators cover the vast majority of everyday null-handling without ever needing an explicit if (x != null) check.

🏏

Cricket analogy: The ?. operator is like a scorer who only logs a boundary if the ball actually crossed the rope, otherwise silently recording nothing rather than crashing the scoreboard trying to log a non-existent boundary.

The Null Assertion Operator (!)

The null assertion operator ! tells the compiler 'trust me, this value is not null right now,' converting a nullable type to its non-nullable counterpart at that point in the code. Writing user!.email asserts that user is definitely non-null and lets you access .email directly; if you're wrong and user actually is null, Dart throws a runtime exception immediately at that line. Because ! bypasses the compiler's static guarantee and replaces it with a runtime promise, it should be used only when you have genuine, verifiable certainty a value is non-null — for example, right after an explicit null check elsewhere in the same function, not as a routine way to silence the analyzer.

🏏

Cricket analogy: Using ! is like a captain reviewing a decision without watching the replay first, betting confidently that the call is correct; if the gamble is wrong, the review is burned immediately and there's no recovery.

The late Keyword

Sometimes a non-nullable field genuinely can't be initialized at declaration time but will definitely be set before it's ever used — for example, a field populated inside initState() in a Flutter StatefulWidget, or a value injected by a dependency-injection framework after construction. The late keyword lets you declare such a field as non-nullable while deferring its initialization: late String userName; compiles fine with no immediate value, and Dart only checks at first access that it has actually been assigned. If you read a late variable before it's been set, Dart throws a LateInitializationError at that point, which is a clear, deliberate signal rather than a silent null — but it does mean late shifts a compile-time guarantee into a runtime one, so it's a deliberate trade-off, not a way to avoid thinking about initialization order.

🏏

Cricket analogy: A late field is like a stadium announcing the player of the match award before the presentation ceremony has actually happened, the slot is reserved and guaranteed to be filled, just not filled yet at the moment it's declared.

dart
class UserProfile {
  String? nickname;              // nullable: may or may not be set
  late String userId;            // non-nullable, but set later

  UserProfile();

  void init(String id) {
    userId = id;                 // must be set before first read
  }

  String displayName() {
    return nickname ?? 'Anonymous';       // ?? fallback
  }
}

void main() {
  UserProfile? profile;          // starts as null
  profile = UserProfile()..init('u-1001');

  print(profile?.displayName());        // ?. safe access
  print(profile!.userId);               // ! assert non-null

  int? retryCount;
  retryCount ??= 0;              // assigns 0 only if currently null
  print('Retries: $retryCount');
}

Overusing ! to silence 'possibly null' analyzer warnings is a common beginner mistake — every ! is a promise the compiler can no longer verify, and a wrong promise throws a runtime exception exactly where you asserted it. Prefer ?., ??, or an explicit if (value != null) check (which Dart's type promotion recognizes) wherever possible.

  • Since Dart 3, sound null safety is mandatory: every type is non-nullable by default unless explicitly suffixed with ?.
  • ?. safely accesses a member only if the receiver is non-null, short-circuiting to null otherwise.
  • ?? supplies a fallback value when the left-hand expression is null; ??= assigns only if the variable is currently null.
  • ! asserts a nullable value is non-null right now, throwing a runtime exception immediately if that assertion is wrong.
  • late declares a non-nullable variable whose initialization is deferred, throwing a LateInitializationError if read before being set.
  • Overusing ! defeats the purpose of null safety by converting a compile-time guarantee back into a runtime risk.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#DartStudyNotes#NullSafetyInDart#Null#Safety#Dart#Aware#StudyNotes#SkillVeris#ExamPrep