Watch the video (12 min)
On this page27 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. The Null-Safety Toolkit
  5. 2. Dart Follows Your Local Variables
  6. 3. Storing null Ends the Promotion
  7. 4. Top-Level Variables Are Never Promoted
  8. 5. Why This Lecture's Inputs Live Above main()
  9. 6. Where Does null Come From?
  10. 7. Parsing Input with a Default
  11. 8. An Optional Middle Name
  12. 9. Default Settings with ??
  13. 10. Filling Settings with ??= — and Promotion
  14. 11. ?. and ?? Together
  15. 12. Choosing Between ??, ?., and !
  16. 13. late or Nullable?
  17. 14. Convert Once, Early
  18. 15. A Real-World Example
  19. Code Examples
  20. Common Beginner Mistakes
  21. Best Practices
  22. Mini Challenge
  23. Practice Questions
  24. Practice Answers
  25. Quick Revision
  26. Key Takeaways
  27. What's Next?

Lecture 16 — Null Safety in Real Code

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

You know every null-safety tool one at a time. Real programs need them together — to turn messy input, optional details, and missing settings into values you can safely use.

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–015:

  • variables, var, final, and late (Lectures 004–006)
  • string methods such as trim(), toUpperCase(), replaceAll(), and length (Lecture 008)
  • int.tryParse, double.tryParse, and toStringAsFixed (Lectures 009–010)
  • null, nullable types like String?, and non-nullable types (Lectures 012–013)
  • the null-aware operators ??, ??=, and ?. (Lecture 014)
  • the null assertion operator ! and the crash it can cause (Lecture 015)

Connection With Previous Lectures

Each null-safety lecture so far showed one tool on a small example. A real program is not that tidy. Imagine a sign-up form:

dart
var ageInput = '19';
String? middleName;
String? chosenTheme;

The age is text that may not be a number; the middle name and the theme may never arrive. You have four tools. Which one fits where?

There is also a loose end. Lectures 013 and 014 noted that inside main(), String? name = 'Ali'; print(name.length); is accepted — Dart "can see" the value is not null. This lecture explains how, then shows how to choose the right tool each time.


Learning Objectives

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

  • Explain type promotion and why Dart promotes local variables but not top-level ones
  • Recognize where null comes from in real programs
  • Parse text input safely with tryParse and a default value
  • Handle optional values and missing settings with ?? and ??=
  • Combine ?. and ?? to get a non-nullable result
  • Choose between ??, ?., and ! for a given situation
  • Decide between late and a nullable type
  • Turn nullable values into non-nullable ones as early as possible

1. The Null-Safety Toolkit

Here is everything from Lectures 012–015 in one table:

ToolMeaningResult
String?"this may hold null"a nullable variable
a ?? b"use a, or b if a is null"non-nullable if b is
a ??= b"if a is null, store b in it"a now holds a value
a?.b"use b only if a is not null"nullable
a!"I promise a is not null"non-nullable, or a crash

The rest of this lecture is about which answer each situation deserves — but first, how Dart decides whether you need one at all.


2. Dart Follows Your Local Variables

Inside main(), Dart reads your code line by line, the way it will run. When you store a real value in a nullable variable, Dart remembers that it is not null — until you change it — and lets you use it like a non-nullable one. This is called type promotion: for those lines, the String? is promoted to String.

dart
void main() {
  String? name = 'Ali';
  print(name.length);
  name = 'Sara Khan';
  print(name.length);
}
output
3
9

No ?., no !, no error. Each time, Dart can see that the last value stored was a real string. The work of following values through the code is called flow analysis.


3. Storing null Ends the Promotion

Promotion lasts only as long as Dart is sure. Store null (or anything nullable) and the variable is a plain String? again:

dart
void main() {
  String? name = 'Ali';
  print(name.length);
  name = null;
  // print(name.length); // ✗
}
output
Error: Property 'length' cannot be accessed on 'String?' because it is potentially null.
Try accessing using ?. instead.
Context: Variable 'name' could not be promoted due to an assignment.

The Context: line even tells you why: the assignment name = null removed the promise.

The same goes for values Dart cannot see into. int.tryParse(...) might return null, so its result is never promoted, whatever text you pass in.


4. Top-Level Variables Are Never Promoted

Promotion only works for variables inside main(). A variable declared above main() stays nullable — even if you assign it on the line before:

dart
String? nickname;

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

A top-level variable can be read and changed from anywhere in the program. Once you write your own functions (Lecture 030), another piece of code could set it back to null between any two lines of main(). Dart cannot follow all of that, so it plays safe and never promotes it. A variable inside main() belongs to main() alone, so Dart can track it.


5. Why This Lecture's Inputs Live Above main()

Real nullable values come from outside the program — a form, a file, a setting. If you fake one with a value inside main(), Dart sees straight through it:

dart
void main() {
  String? chosenTheme = 'dark';
  print(chosenTheme ?? 'light');
}
output
dark

It runs, but your editor and dart analyze warn:

output
warning - The left operand can't be null, so the right operand is never executed. Try removing the operator and the right operand.

Dart is right — chosenTheme was promoted, so the ?? can never be used. That is why, just like Lecture 014, the examples in this lecture declare "outside" values above main() or get them from tryParse. Then they behave like real input.

Dart can also promote a variable after you check it with == null. Acting on that check needs if statements — we will see it in Lecture 022.


6. Where Does null Come From?

In real code, null rarely comes from writing null yourself. It comes from three places:

  1. Conversions that can fail — int.tryParse('abc') returns null.
  2. Optional information — not everyone has a middle name or a nickname.
  3. Things not decided yet — a setting the user has not chosen.
dart
String? middleName;
String? chosenTheme;

void main() {
  var age = int.tryParse('abc');
  print(age);
  print(middleName);
  print(chosenTheme);
}
output
null
null
null

All three print null, but they mean different things: "invalid", "not provided", and "not chosen". Each deserves its own handling.


7. Parsing Input with a Default

User input always arrives as text. Here we store it in a String variable and pretend it came from a form. tryParse gives an int? or double?; ?? turns it into a real number:

dart
void main() {
  var quantityInput = 'three';
  var priceInput = '249.99';
  int quantity = int.tryParse(quantityInput) ?? 1;
  double price = double.tryParse(priceInput) ?? 0.0;
  print('Quantity: $quantity');
  print('Price: $price');
}
output
Quantity: 1
Price: 249.99

'three' is not a number, so tryParse returns null and ?? supplies 1. Choose a default that makes sense: 1 for a quantity, 0 for a score, 0.0 for a decimal. An empty string '' also fails to parse, while spaces around a number are fine: int.tryParse(' 25 ') gives 25.


8. An Optional Middle Name

A middle name is genuinely optional, so String? is the honest type. For display, ?? replaces the missing value with something readable. For a full name, replaceAll (Lecture 008) removes the double space an empty middle name would leave:

dart
String? middleName;

void main() {
  String firstName = 'Ali';
  String lastName = 'Khan';
  print('Middle: ${middleName ?? '(none)'}');
  var joined = '$firstName ${middleName ?? ''} $lastName';
  print(joined.replaceAll('  ', ' '));

  middleName = 'Raza';
  joined = '$firstName ${middleName ?? ''} $lastName';
  print(joined.replaceAll('  ', ' '));
}
output
Middle: (none)
Ali Khan
Ali Raza Khan

${...} lets you put the whole ?? expression inside the string. With if statements (Lecture 022), you will be able to add the middle name only when it exists.


9. Default Settings with ??

A setting the user has not chosen should fall back to the app's default. ?? gives you the value to use, without changing the original variable:

dart
String? chosenTheme;
String? chosenLanguage = 'Urdu';

void main() {
  var theme = chosenTheme ?? 'light';
  var language = chosenLanguage ?? 'English';
  print('Theme: $theme');
  print('Language: $language');
}
output
Theme: light
Language: Urdu

The user picked a language, so it wins. They did not pick a theme, so the default is used.


10. Filling Settings with ??= — and Promotion

When you want a variable itself to hold the default from now on, use ??=. Copy the saved setting into a local variable first, and Dart's flow analysis helps you:

dart
int? savedFontSize;
int? savedVolume = 30;

void main() {
  int? fontSize = savedFontSize;
  int? volume = savedVolume;
  fontSize ??= 14;
  volume ??= 50;
  print('Font size: $fontSize');
  print('Volume: $volume');
  print('Bigger font: ${fontSize + 2}');
}
output
Font size: 14
Volume: 30
Bigger font: 16

volume already had a value, so ??= left it alone. And look at the last line: after fontSize ??= 14;, the local fontSize holds a value either way, so Dart promotes it and fontSize + 2 is allowed. ! does the same for the lines after it (Lecture 015). On a top-level variable, neither would promote.


11. ?. and ?? Together

?. keeps you safe, but its result is still nullable. Add ?? at the end to get a normal value:

dart
String? nickname;

void main() {
  int nicknameLength = nickname?.length ?? 0;
  String shout = nickname?.toUpperCase() ?? 'NO NICKNAME';
  print(nicknameLength);
  print(shout);
}
output
0
NO NICKNAME

Read nickname?.length ?? 0 from left to right: "the length, if there is a nickname — otherwise 0". This pair is the most common null-safety pattern in real Dart code.


12. Choosing Between ??, ?., and !

Ask one question: "What should happen if the value is missing?"

If the value is missing…UseExample
use a sensible replacement??int.tryParse(input) ?? 0
store a default for later??=fontSize ??= 14
skip the operation?.nickname?.toUpperCase()
skip, then use a replacement?. + ??nickname?.length ?? 0
it can't be — I guarantee it!int.tryParse('40')!
dart
String? nickname = 'Zee';

void main() {
  var ageInput = 'unknown';
  int age = int.tryParse(ageInput) ?? 0;
  String tag = nickname?.toUpperCase() ?? 'GUEST';
  int year = int.tryParse('2026')!;
  print('$age, $tag, $year');
}
output
0, ZEE, 2026

'2026' is written in the code and always parses, so ! is a true promise. With text a user typed, ! would be a gamble — 'abc' would crash the program with Null check operator used on a null value. When in doubt, choose ??. It can never crash.


13. late or Nullable?

Both late and String? let a variable start without a value, but they make opposite claims:

late String name;String? name;
Claim"a value will arrive before anyone reads it""a value may never arrive"
Reading it emptystops the program (Lecture 006)gives null
Using itlike a normal Stringneeds ??, ?., or !
dart
late String welcomeMessage;
String? middleName;

void main() {
  welcomeMessage = 'Welcome, Sara';
  print(welcomeMessage.length);
  print(middleName?.length ?? 0);
}
output
13
0

A welcome message is always set before use, so late fits. A middle name might never exist, so it must be nullable. If "missing" is a normal situation, choose String?.


14. Convert Once, Early

The cleanest programs deal with null as soon as a value enters the program, then work with non-nullable variables everywhere else:

dart
void main() {
  var scoreInput = '85';
  final int score = int.tryParse(scoreInput) ?? 0;

  var bonus = 10;
  var total = score + bonus;
  print('Score: $score');
  print('Total: $total');
}
output
Score: 85
Total: 95

After the first final line, score is a plain int. Nothing below it needs to think about null at all.


15. A Real-World Example

A sign-up form for Sara. The form's values live above main(). Every one is handled once, at the top of main(), and the rest of the program uses safe values:

dart
// Raw form input
var nameInput = '  Sara  ';
var ageInput = '19';
String? middleName;
String? nickname = 'sar';
String? chosenTheme;

void main() {
  // Deal with null once, early
  final name = nameInput.trim();
  final int age = int.tryParse(ageInput) ?? 0;
  final middleText = middleName ?? '(none)';
  final displayNick = nickname?.toUpperCase() ?? 'NONE';
  final theme = chosenTheme ?? 'light';

  // From here on, everything is non-nullable
  print('Name: $name');
  print('Middle name: $middleText');
  print('Age next year: ${age + 1}');
  print('Nickname: $displayNick');
  print('Theme: $theme');
}
output
Name: Sara
Middle name: (none)
Age next year: 20
Nickname: SAR
Theme: light

This is the important idea: decide what "missing" means at the edge of the program, then let the rest of the code work with real values.


Code Examples

Example 1 — Parse with a Default

dart
void main() {
  var input1 = '42';
  var input2 = 'forty-two';
  int a = int.tryParse(input1) ?? 0;
  int b = int.tryParse(input2) ?? 0;
  print(a);
  print(b);
}
output
42
0

Example 2 — Optional Middle Name

dart
String? middleName = 'Ahmed';
String? otherMiddleName;

void main() {
  print(middleName ?? '(none)');
  print(otherMiddleName ?? '(none)');
}
output
Ahmed
(none)

Example 3 — Length of a Nullable String

dart
String? city = 'Lahore';
String? village;

void main() {
  print(city?.length ?? 0);
  print(village?.length ?? 0);
}
output
6
0

Example 4 — Filling Default Settings

dart
String? language;
bool? notifications = false;

void main() {
  language ??= 'English';
  notifications ??= true;
  print('Language: $language');
  print('Notifications: $notifications');
}
output
Language: English
Notifications: false

notifications was false, not null — and false is a real value, so ??= keeps it.

Example 5 — Convert Once, Then Calculate

dart
void main() {
  var widthInput = '12.5';
  var heightInput = '4';
  final double width = double.tryParse(widthInput) ?? 0.0;
  final double height = double.tryParse(heightInput) ?? 0.0;
  print('Area: ${width * height}');
}
output
Area: 50.0

Example 6 — late Beside Nullable

dart
late String greeting;
String? nickname;

void main() {
  greeting = 'Hello, Zayan';
  print(greeting.toUpperCase());
  print(nickname?.toUpperCase() ?? 'no nickname');
}
output
HELLO, ZAYAN
no nickname

Example 7 — Student Marks from a Form

dart
void main() {
  var mathInput = '80';
  var scienceInput = '75';
  var englishInput = 'absent';

  int math = int.tryParse(mathInput) ?? 0;
  int science = int.tryParse(scienceInput) ?? 0;
  int english = int.tryParse(englishInput) ?? 0;

  int total = math + science + english;
  print('Math: $math');
  print('Science: $science');
  print('English: $english');
  print('Total: $total');
}
output
Math: 80
Science: 75
English: 0
Total: 155

How it works

  1. Each mark arrives as text, as it would from a form.
  2. 'absent' is not a number, so tryParse returns null and ?? 0 counts it as zero.
  3. After the three conversions, every mark is a plain int, so adding them is allowed.

Example 8 — Shopping Total with an Optional Coupon

dart
double? couponDiscount;

void main() {
  var priceInput = '250.5';
  var quantityInput = '2';

  double price = double.tryParse(priceInput) ?? 0.0;
  int quantity = int.tryParse(quantityInput) ?? 1;
  double subtotal = price * quantity;
  double total = subtotal - (couponDiscount ?? 0.0);

  print('Subtotal: ${subtotal.toStringAsFixed(2)}');
  print('Total: ${total.toStringAsFixed(2)}');
}
output
Subtotal: 501.00
Total: 501.00

How it works

  1. Price and quantity are parsed with sensible defaults: 0.0 and 1.
  2. No coupon was entered, so couponDiscount ?? 0.0 treats it as "no discount". The brackets make Dart work out the discount before subtracting.
  3. toStringAsFixed(2) formats the money for display (Lecture 009).

Example 9 — Bank Deposit

dart
void main() {
  int balance = 1000;
  var depositInput = 'five hundred';
  var withdrawInput = '200';

  balance = balance + (int.tryParse(depositInput) ?? 0);
  balance = balance - (int.tryParse(withdrawInput) ?? 0);
  print('Balance: $balance');
}
output
Balance: 800

How it works

  1. balance starts at 1000 and is never nullable.
  2. 'five hundred' is not a number, so the deposit counts as 0 — a bad input changes nothing instead of crashing the program.
  3. The withdrawal '200' parses correctly, so 200 is subtracted.

Example 10 — Temperature with a Default Unit

dart
String? unitSetting;

void main() {
  var reading = '30';

  double celsius = double.tryParse(reading) ?? 20.0;
  unitSetting ??= 'F';
  double fahrenheit = celsius * 9 / 5 + 32;

  print('Reading: $celsius C');
  print('Show in: $unitSetting');
  print('Converted: $fahrenheit $unitSetting');
}
output
Reading: 30.0 C
Show in: F
Converted: 86.0 F

How it works

  1. double.tryParse('30') gives 30.0; with bad input it would fall back to 20.0.
  2. The user never chose a unit, so ??= stores 'F' in unitSetting.
  3. 30.0 * 9 / 5 + 32 is 86.0.

Common Beginner Mistakes

1. Storing a tryParse result in a non-nullable variable

dart
void main() {
  var input = '25';
  // int age = int.tryParse(input); // ✗
}
output
Error: A value of type 'int?' can't be assigned to a variable of type 'int'.

tryParse can return null. Add a default: int.tryParse(input) ?? 0.

2. Using ! on user input

dart
void main() {
  var input = 'abc';
  // int age = int.tryParse(input)!; // ✗ crashes while running
}
output
Unhandled exception:
Null check operator used on a null value

The code compiles, then crashes the moment someone types something unexpected. Use ??.

3. Calling a property on a nullable value

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

Write middleName?.length ?? 0.

4. Forgetting the ?? after ?.

dart
void main() {
  String? nickname;
  // var size = nickname?.length + 1; // ✗
}
output
Error: Operator '+' cannot be called on 'int?' because it is potentially null.

nickname?.length is still an int?. Write (nickname?.length ?? 0) + 1.

5. Passing a nullable string to tryParse

dart
void main() {
  String? couponInput;
  // var discount = double.tryParse(couponInput); // ✗
}
output
Error: The argument type 'String?' can't be assigned to the parameter type 'String'.

tryParse only accepts a real String. Give it one: double.tryParse(couponInput ?? '').

6. Using late for a value that may never arrive

dart
void main() {
  late String welcome;
  // print(welcome); // ✗
}
output
Error: Late variable 'welcome' without initializer is definitely unassigned.

Here Dart catches it before running. At the top level it often cannot, and you get a LateInitializationError while the program runs. If "missing" is possible, use String?.

7. Expecting a top-level variable to be promoted

dart
String? nickname;

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

Only variables inside main() are promoted. Use nickname?.length ?? 0, or copy the value into a local variable with a default: var name = nickname ?? 'Guest';.


Best Practices

  • Handle null once, where the value enters the program, then use non-nullable variables.
  • Prefer ?? with a sensible default for anything a user typed.
  • Pair ?. with ?? whenever you need a normal value at the end.
  • Use ??= when a missing setting should be filled in permanently.
  • Reserve ! for values you control and can guarantee — never for user input.
  • Use String? when "missing" is normal; use late only when a value always arrives first.
  • Declare "outside" nullable values above main() or get them from tryParse; copy them into a non-nullable local with ?? before working with them.
  • Choose defaults that make sense for the meaning: 1 item, 0 points, 'light' theme.

Mini Challenge

Zayan fills in a profile form. Store:

  • nameInput = ' Zayan '
  • ageInput = 'six'
  • above main(): String? city with no value, and String? favoriteColor = 'blue'

Clean the name, parse the age with a default of 6, show 'Unknown' for a missing city, and print the favorite colour in capitals (or 'NONE' if missing).

Your program should print exactly:

output
Name: Zayan
Age: 6
City: Unknown
Favorite color: BLUE

Try it yourself before opening the solution.

Show solution
dart
String? city;
String? favoriteColor = 'blue';

void main() {
  var nameInput = '  Zayan  ';
  var ageInput = 'six';

  final name = nameInput.trim();
  final int age = int.tryParse(ageInput) ?? 6;
  final cityText = city ?? 'Unknown';
  final colorText = favoriteColor?.toUpperCase() ?? 'NONE';

  print('Name: $name');
  print('Age: $age');
  print('City: $cityText');
  print('Favorite color: $colorText');
}
output
Name: Zayan
Age: 6
City: Unknown
Favorite color: BLUE

How it works

  1. trim() removes the spaces around the name.
  2. 'six' is not a number, so ?? 6 supplies the default.
  3. city is null, so ?? shows 'Unknown'.
  4. favoriteColor exists, so ?.toUpperCase() runs and the ?? is not needed.

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.

Beginner

  1. Parse '75' with int.tryParse, defaulting to 0. Add 5 and print the result.
  2. Above main(), create String? middleName with no value. Print Middle: followed by the middle name, or none if it is missing.
  3. Above main(), create int? volume with no value. Use ??= to set it to 50, then print it.

Application

  1. A shop form gives quantityInput = 'twelve'. Parse it with a default of 1, multiply by a price of 300, and print Total: ….

  2. Above main(), create String? nickname = 'zee'. Print it in capitals, or NO NICKNAME if missing. Then set nickname = null and print the same expression again.

  3. Above main(), create String? city with no value. Print the number of characters in it, using 0 if it is missing.

  4. Above main(), create String? theme with no value and String? language = 'Urdu'. Print Theme: … and Language: …, using the defaults 'light' and 'English'.

  5. This program does not compile. Fix it so it prints Age: 0, without using !:

    dart
    void main() {
      var ageInput = 'abc';
      // int age = int.tryParse(ageInput);
      // print('Age: $age');
    }

Challenge

  1. Convert two temperature readings, '30.5' and '', from Celsius to Fahrenheit. Any reading that is not a number counts as 20.0. Print each result as … C = … F.
  2. Write an order program. Store quantityInput = '4' and priceInput = '120.5', and above main() store String? couponInput = '15' — a discount amount, as text, that may be missing. Calculate the subtotal, the discount (0 if the coupon is missing or invalid), and the total. Print all three with two decimal places, labelled Subtotal:, Discount:, and Total:. Hint: tryParse needs a real String, so use ?? before parsing.

Practice Answers

Question 1 — Solution

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

Question 2 — Solution

dart
String? middleName;

void main() {
  print('Middle: ${middleName ?? 'none'}');
}
output
Middle: none

Question 3 — Solution

dart
int? volume;

void main() {
  volume ??= 50;
  print(volume);
}
output
50

Question 4 — Solution

dart
void main() {
  var quantityInput = 'twelve';
  int quantity = int.tryParse(quantityInput) ?? 1;
  int total = quantity * 300;
  print('Total: $total');
}
output
Total: 300

'twelve' is written in words, so tryParse returns null and the quantity becomes 1.

Question 5 — Solution

dart
String? nickname = 'zee';

void main() {
  print(nickname?.toUpperCase() ?? 'NO NICKNAME');
  nickname = null;
  print(nickname?.toUpperCase() ?? 'NO NICKNAME');
}
output
ZEE
NO NICKNAME

Question 6 — Solution

dart
String? city;

void main() {
  print(city?.length ?? 0);
}
output
0

Question 7 — Solution

dart
String? theme;
String? language = 'Urdu';

void main() {
  print('Theme: ${theme ?? 'light'}');
  print('Language: ${language ?? 'English'}');
}
output
Theme: light
Language: Urdu

Question 8 — Solution

dart
void main() {
  var ageInput = 'abc';
  int age = int.tryParse(ageInput) ?? 0;
  print('Age: $age');
}
output
Age: 0

?? 0 turns the int? from tryParse into an int. Using ! would compile too, but it would crash on 'abc'.

Question 9 — Solution

dart
void main() {
  var reading1 = '30.5';
  var reading2 = '';

  double c1 = double.tryParse(reading1) ?? 20.0;
  double c2 = double.tryParse(reading2) ?? 20.0;

  print('$c1 C = ${c1 * 9 / 5 + 32} F');
  print('$c2 C = ${c2 * 9 / 5 + 32} F');
}
output
30.5 C = 86.9 F
20.0 C = 68.0 F

The empty reading is not a number, so it falls back to 20.0.

Question 10 — Solution

dart
String? couponInput = '15';

void main() {
  var quantityInput = '4';
  var priceInput = '120.5';

  final int quantity = int.tryParse(quantityInput) ?? 1;
  final double price = double.tryParse(priceInput) ?? 0.0;
  final double discount = double.tryParse(couponInput ?? '') ?? 0.0;

  final subtotal = price * quantity;
  final total = subtotal - discount;

  print('Subtotal: ${subtotal.toStringAsFixed(2)}');
  print('Discount: ${discount.toStringAsFixed(2)}');
  print('Total: ${total.toStringAsFixed(2)}');
}
output
Subtotal: 482.00
Discount: 15.00
Total: 467.00

couponInput ?? '' gives tryParse a real String. If the coupon were missing, the empty string would fail to parse, and the second ?? 0.0 would make the discount zero. Both cases are handled in a single line.


Quick Revision

  1. Name three places null comes from in real programs.
  2. What does int.tryParse(input) ?? 0 give you, and what type is it?
  3. What is the difference between ?? and ??=?
  4. Why does nickname?.length still need ?? before you add to it?
  5. When is ! a safe choice? When is it a bad one?
  6. When should you choose String? instead of late String?
  7. Why handle null once, early, instead of everywhere?
  8. What is type promotion, and what ends it?
  9. Why does Dart never promote a top-level variable?
  10. After fontSize ??= 14; on a local variable, why can you write fontSize + 2?

Key Takeaways

  • Type promotion: inside main(), Dart follows your code and treats a nullable variable as non-nullable while it can see the value is not null — after a real value, ??=, or !.
  • Storing null ends promotion, and top-level variables are never promoted — which is why this lecture's "outside" values live above main().
  • null comes from failed conversions, optional information, and choices not made yet.
  • tryParse(...) ?? default is the standard way to read numbers from text input.
  • ?? gives a value to use now; ??= stores a default in the variable itself.
  • ?. keeps you safe but stays nullable — finish with ?? to get a normal value.
  • ! is for values you control; for user input it is a crash waiting to happen.
  • Use a nullable type when "missing" is normal, and late only when a value always arrives before it is read.
  • Handle null once, at the edge of the program, then work with non-nullable variables.
  • Promotion after a == null check needs if statements — that comes in Lecture 022.

What's Next?

Lecture 17 — Arithmetic & Assignment Operators. This lecture closes Null Safety. Along the way you used +, -, *, / and even one special assignment operator, ??=. Next we start Category 4 — Operators, and look properly at Dart's arithmetic operators and the shortcut assignment operators such as += that update a variable in one step.


Video lecture

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