Dart null safety code shown on a laptop screen

Dart Null Safety for Beginners: Stop Null Errors

You run your Flutter app, tap a button, and the whole screen freezes behind a red error box. You read the message: Null check operator used on a null value. Or maybe it is the older, scarier one, The method ‘length’ was called on null. Somewhere a value you counted on turned out to be nothing, and your app fell over because of it. Dart null safety exists to stop exactly this, and it is one of the best things the language does for you as a beginner. Instead of finding out about a missing value when your app crashes in front of a user, you find out while you are still typing, with a red squiggle in your editor. By the end of this post you will know how Dart decides what can and cannot be null, the three small operators that handle missing values cleanly, and the one keyword that trips people up. Every example below is tiny, and you can paste it straight into DartPad.

What Dart null safety actually changes

Before null safety, any variable in Dart could secretly hold null. A String might be a string, or it might be nothing, and you had no way to tell by looking. Since Dart 2.12 that flipped. Now every type is non-nullable by default, which means a variable has to hold a real value of its type. Watch what happens when you try to break that rule.

void main() {
  String name = null; // the analyzer stops you right here
  print(name);
}

The code does not run at all. The analyzer stops you before you can even press the button:

A value of type 'Null' can't be assigned to a variable of type 'String'.

That red line is the whole idea. A String is now a promise that there is always a real string in there. You cannot park a null in it by accident, so the crash that null would have caused later never gets the chance to happen. Here is the normal, happy version.

void main() {
  String name = 'Ada';   // a real String, guaranteed
  print(name.length);    // 3, always safe to call
}

Run that and you get 3. Because name can never be null, calling .length on it is always safe, and Dart never makes you check.

Making a variable nullable with the ? type

Sometimes a value really is missing, and that is fine. A user might not have set a nickname. A search might find nothing. When null is a legitimate state, you tell Dart by adding a question mark to the type.

void main() {
  String? nickname;   // the ? means this is allowed to be null
  print(nickname);    // null
}

Now Dart knows nickname might be nothing, and it holds you to that. The moment you try to use it as if it were a real string, you get stopped again.

void main() {
  String? nickname;
  print(nickname.length); // not allowed
}

Output:

The property 'length' can't be unconditionally accessed because the receiver can be 'null'.

Read that message slowly, because it tells you exactly what is wrong. The word unconditionally is the key. Dart will let you reach for length, but only if you first deal with the possibility that nickname is null. That is what the next three operators are for.

Editing nullable variables in a Dart project at a home desk
Marking a variable nullable with a question mark is a promise you make to the compiler.

Three operators you will use every day

Null safety would be exhausting if every nullable value meant a big if check. It does not, because Dart gives you three small operators that cover almost every case. Learn these and most of your null handling becomes one or two characters.

The safe access operator ?.

Put a question mark before the dot and Dart only follows the dot if the thing on the left is not null. If it is null, the whole expression quietly becomes null instead of crashing.

String? lookupNickname(String user) {
  final nicknames = {'ada': 'Ace'};
  return nicknames[user]; // a Map returns null for a missing key
}

void main() {
  print(lookupNickname('grace')?.length); // null, no crash
  print(lookupNickname('ada')?.length);   // 3
}

The first lookup misses, so lookupNickname returns null, and ?.length hands back null without ever touching length. The second one finds Ace, so you get 3. No crash either way.

The default value operator ??

Often null is not what you want to show a user. You want a fallback. The ?? operator means use the thing on the left, unless it is null, in which case use the thing on the right.

String? lookupNickname(String user) {
  final nicknames = {'ada': 'Ace'};
  return nicknames[user];
}

void main() {
  String shown = lookupNickname('grace') ?? 'Guest';
  print('Welcome, $shown');                          // Welcome, Guest
  print('Welcome, ${lookupNickname('ada') ?? 'Guest'}'); // Welcome, Ace
}

Grace has no nickname, so shown falls back to Guest. Ada has one, so her real nickname comes through. This is how you turn a maybe-missing value into something safe to display, in a single line.

The assertion operator !

The last one is the sharp tool. A single exclamation mark tells Dart trust me, this is not null, treat it as a real value. It works, right up until you are wrong.

int? findScore(String user) {
  final scores = {'ada': 95};
  return scores[user];
}

void main() {
  int score = findScore('grace')!; // ! swears this is not null
  print(score);
}

Run it and you are back where we started:

Unhandled exception:
Null check operator used on a null value

The score for grace is null, but ! promised it would not be, so Dart took you at your word and then hit reality. Use ! only when you genuinely know more than the analyzer does, and reach for ?. or ?? first almost every time. I see ! cause more beginner crashes than any other piece of null safety, usually because someone added it just to make the red line go away.

Practicing Dart null checks at a tidy laptop workspace
The null-aware operators handle a missing value without crashing your app.

When the value comes later: the late keyword

There is one more situation worth knowing. Sometimes a variable cannot be set the instant you declare it, but you are certain it will have a value before anything reads it. Marking it nullable would mean dealing with null everywhere, even though it is never really null in practice. The late keyword is for this.

class Profile {
  late String username; // set later, but promised before it is read

  void load() {
    username = 'mosh_codes';
  }
}

void main() {
  final p = Profile();
  p.load();
  print(p.username); // mosh_codes
}

username is non-nullable, but late lets you set it after the object is built instead of right away. As long as load runs before anything reads username, everything is fine. The catch is what happens if you break that promise.

class Profile {
  late String username;
}

void main() {
  final p = Profile();
  print(p.username); // read before it was ever set
}

Output:

LateInitializationError: Field 'username' has not been initialized.

Read a late variable before you set it and Dart throws this the moment you touch it. It is a clear, specific error that points right at the field, which beats a vague null crash three screens away. Use late when you have a real reason a value arrives a beat late, not as a way to dodge null safety entirely.

Where to go next with Dart null safety

That is the whole core of Dart null safety. Types are non-nullable until you add a ?, and once a value can be null Dart makes you handle that before you use it. You reach for ?. to skip an operation safely, ?? to supply a fallback, and ! only when you truly know better. And late covers the honest case where a non-null value simply shows up a little later. None of this is busywork. Every red line the analyzer gives you is a crash that will not happen in front of a user.

The best way to lock this in is to break things on purpose. Paste the nickname examples into DartPad, remove a ?, and watch where the analyzer complains. Add a ! to a value you know is null and run it so you see the crash with your own eyes. If you want the ground underneath all of this, the Meet Dart language guide covers the basics, and once you are comfortable here the Dart list methods and Dart async await posts both lean on null safety constantly, since real data is full of values that might not be there. For the official deep version, the Dart team’s sound null safety overview and their understanding null safety article are both friendly reads. Open your editor and try to make a null slip through. The fact that you cannot is the point.

Leave a Comment

Your email address will not be published. Required fields are marked *