Watch the video (12 min)
On this page26 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is a Null-Aware Operator?
  5. 2. The If-Null Operator ??
  6. 3. ?? Produces a Non-Nullable Value
  7. 4. Chaining Several ??
  8. 5. The If-Null Assignment ??=
  9. 6. ??= Leaves Existing Values Alone
  10. 7. Null-Aware Access ?.
  11. 8. Calling Methods with ?.
  12. 9. The Result of ?. Is Nullable
  13. 10. Chaining After ?.
  14. 11. Combining ?. and ??
  15. 12. Brackets and ??
  16. 13. More Null-Aware Operators — Coming Later
  17. 14. A Real-World Example
  18. Code Examples
  19. Common Beginner Mistakes
  20. Best Practices
  21. Mini Challenge
  22. Practice Questions
  23. Practice Answers
  24. Quick Revision
  25. Key Takeaways
  26. What's Next?

Lecture 14 — Null-Aware Operators

Category3 — Null Safety
Lecture014 of 140
Estimated video length12 min
TargetDart 3.12.2
LevelBeginner

A nullable value cannot be used directly — Dart stops you before you touch "nothing". The null-aware operators ??, ??=, and ?. let you use it anyway, safely, in one short expression.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.


Prerequisites

Students should already understand, from Lectures 004–013:

  • variables, var, final, and string interpolation
  • String methods and properties such as length, trim(), and toUpperCase()
  • int.tryParse and double.tryParse, which return null for invalid text
  • what null is, and why null safety exists
  • nullable types such as String? and int?, and why you cannot call a method on a nullable value

Connection With Previous Lectures

In Lecture 013 you met nullable types. A String? may hold text — or null. Because it might be null, Dart refuses to let you use it like a normal String:

dart
String? nickname = 'Zed';

void main() {
  // print(nickname.length); // ✗
}
output
Error: Property 'length' cannot be accessed on 'String?' because it is potentially null.
Try accessing using ?. instead.

That keeps the program safe, but it leaves us stuck. A missing nickname should not stop the program — we want to say "use 'Guest' instead", or "give me the length, if there is one". Notice the hint in the error: ?.. That is one of the operators this lecture teaches.


Learning Objectives

By the end of this lecture you will be able to:

  • Explain what a null-aware operator does
  • Supply a default value with the if-null operator ??
  • Assign a value only when a variable is null with ??=
  • Read a property or call a method safely with ?.
  • Chain several calls after ?. and predict where the chain stops
  • Combine ?. and ?? to turn a nullable value into a non-nullable one
  • Use brackets to avoid precedence mistakes with ??
  • Recognize the error messages Dart produces for common null-aware mistakes

1. What Is a Null-Aware Operator?

A null-aware operator is an operator that checks for null for you, and does something sensible when it finds it — instead of crashing or refusing to compile.

Analogy — think of a spare key under the doormat. If your main key is in your pocket, you use it. If it is missing, you use the spare. You never stand locked outside.

OperatorNameMeaning
a ?? bif-nulluse a; if a is null, use b
a ??= bif-null assignmentif a is null, store b in it
a?.bnull-aware accessuse b on a; if a is null, the result is null

A note on the examples. Nullable values usually come from outside the program — text a user typed, data from a file. To imitate that, this lecture declares those variables above main(), or gets them from tryParse. Inside main(), Dart can see the value you just stored and would warn that the null check is unnecessary. How Dart tracks this is part of Lecture 016 — Null Safety in Real Code.


2. The If-Null Operator ??

?? gives you a fallback value. If the left side has a value, you get it. If the left side is null, you get the right side.

dart
String? nickname = 'Zed';
String? middleName;

void main() {
  print(nickname ?? 'Guest');
  print(middleName ?? 'none');
}
output
Zed
none

nickname holds 'Zed', so the fallback is ignored. middleName was never given a value, so it is null, and ?? uses 'none' instead.


3. ?? Produces a Non-Nullable Value

When the fallback is not null, the result of ?? can never be null. That means you can store it in a normal, non-nullable variable:

dart
void main() {
  int marks = int.tryParse('abc') ?? 0;
  print(marks + 5);
}
output
5

int.tryParse('abc') returns null, so marks becomes 0. Because marks is a plain int, arithmetic works — no error about a value that is "potentially null".


4. Chaining Several ??

You can line up several fallbacks. Dart checks them from left to right and uses the first one that is not null:

dart
String? nickname;
String? firstName = 'Sara';

void main() {
  print(nickname ?? firstName ?? 'Guest');
}
output
Sara

nickname is null, so Dart moves on. firstName holds 'Sara', so the search stops there and 'Guest' is never reached.


5. The If-Null Assignment ??=

??= combines ?? with assignment. It stores the right side in the variable only if the variable is currently null:

dart
String? city;

void main() {
  print(city);
  city ??= 'Lahore';
  print(city);
}
output
null
Lahore

city ??= 'Lahore' is a short way of saying city = city ?? 'Lahore'. It is perfect for filling in a missing value with a default.


6. ??= Leaves Existing Values Alone

If the variable already holds a value, ??= does nothing:

dart
void main() {
  int? score = int.tryParse('85');
  score ??= 0;
  print(score);
}
output
85

'85' is valid text, so score already holds 85. The 0 is never stored. Compare this with a plain =, which would replace 85 with 0.


7. Null-Aware Access ?.

?. is a careful version of the dot. If the value on the left is not null, it works exactly like .. If the value is null, Dart skips the property and the result is simply null:

dart
String? nickname = 'Zed';
String? middleName;

void main() {
  print(nickname?.length);
  print(middleName?.length);
}
output
3
null

'Zed' has 3 characters. middleName is null, so there is no length to read — the expression gives null instead of an error.


8. Calling Methods with ?.

?. works for methods too, not only properties:

dart
String? nickname = 'zed';
String? middleName;

void main() {
  print(nickname?.toUpperCase());
  print(middleName?.toUpperCase());
}
output
ZED
null

The method runs only when there is a string to call it on.


9. The Result of ?. Is Nullable

Because ?. can produce null, its result is always a nullable type. nickname?.length is an int?, not an int:

dart
String? nickname = 'Zed';

void main() {
  int? letters = nickname?.length;
  print(letters);
}
output
3

Trying to store it in a plain int is rejected — see Common Beginner Mistakes. Section 11 shows how to get a plain int out of it.


10. Chaining After ?.

You can continue a chain after ?.. If the value is null, Dart stops at the ?. and skips the rest of the chain — so the later calls can use an ordinary dot:

dart
String? nickname = '  zed  ';
String? middleName;

void main() {
  print(nickname?.trim().toUpperCase());
  print(middleName?.trim().toUpperCase());
}
output
ZED
null

For nickname, trim() removes the spaces and toUpperCase() runs on the result. For middleName, Dart sees null at ?. and stops; neither trim() nor toUpperCase() runs. This is called short-circuiting. One ?. at the point where null can appear is enough.


11. Combining ?. and ??

The two operators work beautifully together. ?. reads safely and may give null; ?? then replaces that null with a default:

dart
String? nickname = 'Zed';
String? middleName;

void main() {
  int nicknameLength = nickname?.length ?? 0;
  int middleLength = middleName?.length ?? 0;
  print(nicknameLength);
  print(middleLength);
}
output
3
0

Read middleName?.length ?? 0 as: "the length of middleName if there is one, otherwise 0". The result is a plain int.


12. Brackets and ??

?? has a low precedence: Dart works out most other operators, such as +, before it. So 10 + bonus ?? 0 means (10 + bonus) ?? 0 — which tries to add 10 to a value that may be null. Put brackets around the ?? part:

dart
void main() {
  int? bonus = int.tryParse('abc');
  int total = 10 + (bonus ?? 0);
  print(total);
}
output
10

The full precedence table is the topic of Lecture 021 — Operator Precedence & Custom Operators.


13. More Null-Aware Operators — Coming Later

Dart has a few more operators with the same ? idea, each built on a feature you have not met yet:

  • ?[] reads an item from a list or map that may be null — Lists start in Lecture 039.
  • ?.. is the null-aware cascade — Lecture 020 — Increment, Conditional & Cascade Operators.
  • The null-aware spread ...? and null-aware elements fill a collection only with values that exist — Lecture 046 — Collection Features.

When you meet them, you will recognize the pattern: "if it's null, skip it."


14. A Real-World Example

A sign-up form where the user may leave fields empty, or type invalid text:

dart
String? enteredName = '  ali  ';
String? enteredCity;
String enteredAge = 'twenty';

void main() {
  String name = enteredName?.trim().toUpperCase() ?? 'GUEST';
  enteredCity ??= 'Unknown';
  int age = int.tryParse(enteredAge) ?? 0;

  print('Name: $name');
  print('City: $enteredCity');
  print('Age: $age');
}
output
Name: ALI
City: Unknown
Age: 0

?. cleans the name only if it exists, ?? supplies 'GUEST' otherwise, ??= fills in the missing city, and tryParse with ?? turns bad text into a safe 0. Every value printed is non-nullable — the program never crashes.


Code Examples

Example 1 — A Default Name

dart
String? username;

void main() {
  print('Welcome, ${username ?? 'Guest'}!');
}
output
Welcome, Guest!

Example 2 — Filling a Missing Value

dart
String? favouriteColour;

void main() {
  favouriteColour ??= 'Blue';
  favouriteColour ??= 'Green';
  print(favouriteColour);
}
output
Blue

Example 3 — Length of a Nullable String

dart
String? message = 'Hello';
String? emptyNote;

void main() {
  print(message?.length);
  print(emptyNote?.length);
}
output
5
null

Example 4 — A Method Chain

dart
String? email = '  SARA@MAIL.COM ';

void main() {
  print(email?.trim().toLowerCase());
}
output
sara@mail.com

Example 5 — ?. with a Fallback

dart
String? title;

void main() {
  String heading = title?.toUpperCase() ?? 'UNTITLED';
  print(heading);
}
output
UNTITLED

Example 6 — Several Fallbacks

dart
String? phone;
String? email;
String? address = 'Street 5, Karachi';

void main() {
  print('Contact: ${phone ?? email ?? address ?? 'none'}');
}
output
Contact: Street 5, Karachi

How it works

  1. phone and email are null, so Dart keeps moving right.
  2. address holds text, so the search stops there.
  3. 'none' is used only if every earlier value is null.

Example 7 — Student Marks

dart
void main() {
  String student = 'Ali';
  int math = int.tryParse('80') ?? 0;
  int science = int.tryParse('absent') ?? 0;
  int english = int.tryParse('85') ?? 0;
  int total = math + science + english;
  print('Student: $student');
  print('Total: $total');
}
output
Student: Ali
Total: 165

How it works

  1. Each mark arrives as text, the way it would from a form.
  2. 'absent' is not a number, so tryParse returns null and ?? turns it into 0.
  3. All three marks are plain int values, so they can be added: 80 + 0 + 85.

Example 8 — Shopping Total

dart
String? couponCode;

void main() {
  String product = 'Notebook';
  int price = 500;
  int quantity = int.tryParse('') ?? 1;
  int total = price * quantity;
  print('Product: $product');
  print('Quantity: $quantity');
  print('Coupon: ${couponCode?.toUpperCase() ?? 'none'}');
  print('Total: $total');
}
output
Product: Notebook
Quantity: 1
Coupon: none
Total: 500

How it works

  1. The quantity field was left empty. int.tryParse('') returns null, so the quantity defaults to 1.
  2. couponCode is null, so ?. skips toUpperCase() and ?? prints 'none'.
  3. price * quantity is 500 * 1.

Example 9 — Bank Balance

dart
double? savedBalance;

void main() {
  double balance = savedBalance ?? 1000.0;
  double deposit = double.tryParse('250.5') ?? 0;
  double withdrawal = double.tryParse('one hundred') ?? 0;
  balance = balance + deposit - withdrawal;
  print('Balance: $balance');
}
output
Balance: 1250.5

How it works

  1. No balance was saved, so balance starts at the default 1000.0.
  2. '250.5' is valid, so the deposit is 250.5.
  3. 'one hundred' is not a number, so the withdrawal falls back to 0 instead of crashing.
  4. 1000.0 + 250.5 - 0 gives 1250.5.

Example 10 — Temperature Reading

dart
String reading = '31.46';
String? unit;

void main() {
  double celsius = double.tryParse(reading) ?? 0;
  print('Temperature: ${celsius.toStringAsFixed(1)} ${unit ?? 'C'}');
}
output
Temperature: 31.5 C

How it works

  1. double.tryParse('31.46') succeeds, so the fallback 0 is not used.
  2. toStringAsFixed(1) rounds to one decimal place for display: 31.5.
  3. No unit was supplied, so unit ?? 'C' shows C.

Common Beginner Mistakes

1. Using a plain dot on a nullable value

dart
String? nickname = 'Zed';

void main() {
  // print(nickname.length); // ✗
}
output
Error: Property 'length' cannot be accessed on 'String?' because it is potentially null.
Try accessing using ?. instead.

Use nickname?.length, or nickname?.length ?? 0 if you need a plain int.

2. Storing the result of ?. in a non-nullable variable

dart
String? nickname = 'Zed';

void main() {
  // int letters = nickname?.length; // ✗
}
output
Error: A value of type 'int?' can't be assigned to a variable of type 'int'.

?. can give null. Declare int?, or add a fallback: nickname?.length ?? 0.

3. Forgetting the brackets around ??

dart
void main() {
  int? bonus = int.tryParse('abc');
  // int total = 10 + bonus ?? 0; // ✗
}
output
Error: A value of type 'int?' can't be assigned to a variable of type 'num'.
  int total = 10 + bonus ?? 0;
                   ^

+ runs first, on a value that may be null. Write 10 + (bonus ?? 0).

4. A fallback of the wrong type

dart
String? nickname;

void main() {
  // String label = nickname ?? 0; // ✗
}
output
Error: A value of type 'Object' can't be assigned to a variable of type 'String'.

A String or an int gives Object, which is not a String. The fallback must match: nickname ?? 'none'.

5. Using ??= on a final variable

dart
void main() {
  final int? score = int.tryParse('abc');
  // score ??= 0; // ✗
}
output
Error: Can't assign to the final variable 'score'.

??= is an assignment, and a final variable cannot be assigned again. Put the fallback in the declaration instead: final int score = int.tryParse('abc') ?? 0;.

6. Using ?? on a value that can never be null

dart
void main() {
  String name = 'Ali';
  print(name ?? 'Guest');
}
output
Ali

This runs, but dart analyze reports warnings:

output
warning - Dead code. Try removing the code, or fixing the code before it so that it can be reached. - dead_code
warning - The left operand can't be null, so the right operand is never executed. Try removing the operator and the right operand. - dead_null_aware_expression

Null-aware operators belong on nullable values only.

7. Expecting ?? to replace an empty string

dart
String? nickname = '';

void main() {
  print(nickname ?? 'Guest');
}
output

An empty string '' is a real value — it is not null. ?? reacts to null only, so it prints an empty line.


Best Practices

  • Use ?? to give every nullable value a sensible default before you display or calculate with it.
  • Use ??= to fill in a missing value once, instead of repeating the same ?? everywhere.
  • Put a single ?. where null can appear; the rest of the chain is skipped automatically.
  • End a ?. chain with ?? default when you need a non-nullable result.
  • Wrap ?? in brackets whenever it sits next to other operators.
  • Do not add null-aware operators to values that cannot be null — the warning is telling you the code is unnecessary.
  • Choose fallbacks that make sense to the reader: 'Guest', 0, 'Unknown'.

Mini Challenge

Sara's profile has a nullable nickname that was never set, and her age arrives as the text '19'. Print a greeting using the nickname in capital letters, or 'SARA' if there is no nickname, then print her age next year.

Your program should print exactly:

output
Hello, SARA!
Next year: 20

Try it yourself before opening the solution.

Show solution
dart
String? nickname;

void main() {
  String greetingName = nickname?.toUpperCase() ?? 'SARA';
  int age = int.tryParse('19') ?? 0;
  print('Hello, $greetingName!');
  print('Next year: ${age + 1}');
}
output
Hello, SARA!
Next year: 20

How it works

  1. nickname is null, so ?. skips toUpperCase() and ?? supplies 'SARA'.
  2. '19' is valid, so age is 19 — a plain int, ready for arithmetic.
  3. ${age + 1} calculates 20 inside the string.

Practice Questions

Work through these in order — they run from beginner to application to challenge. Every question gives you exact values, so you can compare your output with the answers. Declare the nullable variables above main(), as this lecture does.

Beginner

  1. Declare String? city; and print it using ?? with the fallback 'Unknown'.
  2. Declare String? petName = 'Milo'; and print petName ?? 'No pet'.
  3. Declare String? language;. Use ??= to set it to 'Dart', then print it.

Application

  1. Declare String? word = 'Dart'; and String? missing;. Print the length of each using ?..
  2. Store int quantity = int.tryParse('two') ?? 1; and print quantity * 300.
  3. Declare String? nickname;. Store nickname?.length ?? 0 in a plain int named letters and print it.
  4. Declare String? code = ' ab12 ';. Print it trimmed and in capital letters using one ?. chain.
  5. Store int? extra = int.tryParse('x'); and calculate total = 50 + (extra ?? 0). Print the total.

Challenge

  1. Declare String? first;, String? second;, and String? third = 'Zayan';. Print the first non-null name, or 'Nobody' if all are null, using one expression.
  2. Write a temperature program. The reading arrives as the text 'abc'. Convert it with double.tryParse, defaulting to 20.0, then print Celsius: … and Fahrenheit: … (celsius * 9 / 5 + 32).

Practice Answers

Question 1 — Solution

dart
String? city;

void main() {
  print(city ?? 'Unknown');
}
output
Unknown

Question 2 — Solution

dart
String? petName = 'Milo';

void main() {
  print(petName ?? 'No pet');
}
output
Milo

Question 3 — Solution

dart
String? language;

void main() {
  language ??= 'Dart';
  print(language);
}
output
Dart

Question 4 — Solution

dart
String? word = 'Dart';
String? missing;

void main() {
  print(word?.length);
  print(missing?.length);
}
output
4
null

Question 5 — Solution

dart
void main() {
  int quantity = int.tryParse('two') ?? 1;
  print(quantity * 300);
}
output
300

'two' is not a number in digits, so quantity falls back to 1.

Question 6 — Solution

dart
String? nickname;

void main() {
  int letters = nickname?.length ?? 0;
  print(letters);
}
output
0

Question 7 — Solution

dart
String? code = ' ab12 ';

void main() {
  print(code?.trim().toUpperCase());
}
output
AB12

Question 8 — Solution

dart
void main() {
  int? extra = int.tryParse('x');
  int total = 50 + (extra ?? 0);
  print(total);
}
output
50

Without the brackets, 50 + extra would run first and be rejected.

Question 9 — Solution

dart
String? first;
String? second;
String? third = 'Zayan';

void main() {
  print(first ?? second ?? third ?? 'Nobody');
}
output
Zayan

Question 10 — Solution

dart
void main() {
  double celsius = double.tryParse('abc') ?? 20.0;
  double fahrenheit = celsius * 9 / 5 + 32;
  print('Celsius: $celsius');
  print('Fahrenheit: $fahrenheit');
}
output
Celsius: 20.0
Fahrenheit: 68.0

'abc' cannot be read as a number, so celsius defaults to 20.0. 20.0 * 9 / 5 is 36.0, and 36.0 + 32 is 68.0.


Quick Revision

  1. What does a ?? b give when a is null? And when it is not?
  2. What is the difference between x ??= 5 and x = 5?
  3. What does name?.length give when name is null?
  4. Why is the result of ?. always a nullable type?
  5. In name?.trim().toUpperCase(), why does toUpperCase() not need its own ?.?
  6. How do you turn name?.length into a plain int?
  7. Why should 10 + (bonus ?? 0) have brackets?
  8. Does ?? replace an empty string ''?

Key Takeaways

  • Null-aware operators check for null for you and react safely.
  • a ?? b uses a, or b when a is null; with a non-null fallback, the result is non-nullable.
  • Several ?? can be chained; the first non-null value wins.
  • a ??= b assigns b only when a is null — existing values are left alone.
  • a?.b reads a property or calls a method only when a is not null; otherwise the result is null, so its type is nullable.
  • After ?., a null short-circuits the rest of the chain.
  • ?. followed by ?? turns a nullable value into a non-nullable one.
  • ?? has low precedence — wrap it in brackets next to other operators.

What's Next?

Lecture 015 — Null Assertion Operator !. Every operator in this lecture plans for null. But sometimes you know a value cannot be null, even though its type says it might be. Next we will learn how ! tells Dart "trust me, there is a value here" — and what happens when that promise is wrong.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.