Skip to main content

Type System and Null Safety

Dart's type system, and what changes when you treat it as a design tool rather than as paperwork. Sound null safety means the compiler can prove a value is not null — but only if you let it, and most codebases spend that guarantee on ! within a month.

Why it matters. Every state your types allow but your product forbids is a bug waiting for the right user. A model where "delivered" and "no delivery date" can both be true will eventually have both.

What to understand​

  • What the type is promising, and whether the promise is true
  • Why a value is nullable — genuinely optional, or not loaded yet, or lazily built
  • Where ! is a proof you have and the compiler does not, and where it is a hope
  • Whether an illegal combination of fields is representable at all
  • What dynamic costs, and where it sneaks in uninvited

Core topics​

The type system​

  • Sound typing, and what soundness buys you
  • Generics, and writing code that is generic because it must be
  • Variance in one sentence: why a List<Dog> is a List<Animal> in Dart, and the runtime check that costs
  • dynamic versus Object? — the second one makes you look

Null safety​

  • Nullable and non-nullable types, and late as a promise with a runtime cost
  • The null-aware operators, and which of them hide problems
  • ! — the four honest uses and the many dishonest ones
  • Migrating a codebase, and why the ugly intermediate state is temporary

Modelling with types​

  • Sealed classes and exhaustive switch
  • Records for values that travel together without deserving a class
  • Pattern matching and destructuring
  • Enums with members, replacing scattered if chains
  • Making illegal states unrepresentable, rather than validating them everywhere

Levels​

LevelWhat it looks like
JuniorWrites correct type annotations. Resolves null errors, sometimes with !.
MiddleUses generics and sealed classes deliberately. Can explain why a field is nullable, or removes the nullability.
SeniorDesigns models where wrong states do not compile. Uses exhaustiveness so adding a case breaks the code that must change.

Practice​

Starting out​

  • Remove the bangs Take a file with several ! and restructure so most of them are unnecessary.

  • Justify each nullable Go through a model and, for every nullable field, write why. Fix the ones with no answer.

  • Replace a string with an enum Find a status held as a String and make it an enum, then follow the compiler.

Going deeper​

  • Model a state machine Replace a class with isLoading, error and data fields with a sealed class where only valid combinations exist.

  • Use exhaustiveness on purpose Add a case to a sealed type and let the compiler show you everywhere that must handle it.

  • Write a real generic Extract a repeated pattern into a generic function or class with a meaningful bound.

Check yourself​

  • Which nullable fields in your project are nullable only because they are loaded later?
  • When you write !, what do you know that the compiler does not?
  • What combinations of fields does your model allow that the product forbids?
  • Where has dynamic entered your code without you choosing it?
  • How do you find every place that must change when you add a case?
  • When is a generic making code reusable, and when is it making it unreadable?

Resources​

  • The Dart type system — how soundness works, including the parts about generics and inference that most people never read and then misdiagnose.
  • Sound null safety — the official explanation, including flow analysis and what late actually costs at runtime.
  • Patterns — destructuring, switch expressions and exhaustiveness. The feature that makes sealed-class modelling practical rather than verbose.
  • Effective Dart: Design — the language team's own guidance on types in public APIs. Terse, and every entry has a reason attached.
  • Making illegal states unrepresentable — written for F#, but it is the clearest statement of the idea, and it transfers directly to sealed classes in Dart.