Watch the video (10 min)
On this page28 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Two Kinds of Types
  5. 2. Non-Nullable Is the Default
  6. 3. Making a Type Nullable with ?
  7. 4. Nullable Variables Start as null
  8. 5. A Nullable Variable Can Hold Real Values Too
  9. 6. Every Type Has a Nullable Version
  10. 7. Where Nullable Values Come From
  11. 8. Non-Nullable Fits into Nullable
  12. 9. Nullable Does Not Fit into Non-Nullable
  13. 10. What Dart Refuses on a Nullable Value
  14. 11. What You Can Still Do with a Nullable Value
  15. 12. Why the ? Matters: A Crash Dart Prevents
  16. 13. Object, Object?, and dynamic
  17. 14. Nullable or late?
  18. 15. Choosing Nullable or Non-Nullable
  19. 16. A Real-World Example
  20. Code Examples
  21. Common Beginner Mistakes
  22. Best Practices
  23. Mini Challenge
  24. Practice Questions
  25. Practice Answers
  26. Quick Revision
  27. Key Takeaways
  28. What's Next?

Lecture 13 — Nullable & Non-Nullable Types

Category3 — Null Safety
Lecture013 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelBeginner

A plain String can never be null. Add one character, ?, and the variable may also hold "no value" — and Dart tracks that difference in every line you write.

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

  • declaring variables with an explicit type, with var, and with late (Lectures 004–006)
  • why a non-nullable variable must hold a value before it is read (Lecture 004)
  • int.tryParse and double.tryParse, which return null for bad text (Lecture 010)
  • Object, dynamic, and the difference between compile-time and run-time errors (Lecture 011)
  • what null is, why it causes crashes, and that Dart types are non-nullable by default (Lecture 012)

Connection With Previous Lectures

In Lecture 010 we met this error:

dart
void main() {
  var count = int.tryParse('5');
  print(count + 1);
}
output
Error: Operator '+' cannot be called on 'int?' because it is potentially null.
  print(count + 1);
              ^

The text '5' is a perfectly good number, yet Dart refused. The message names a type we have never declared ourselves: int?. Lecture 012 told us why Dart guards against null. This lecture shows how: every type comes in two versions, and Dart always knows which one a variable has.


Learning Objectives

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

  • Explain the difference between a nullable and a non-nullable type
  • Declare nullable variables with ? for any type
  • Predict the value of a nullable variable declared without a value
  • Recognize where nullable values come from, such as tryParse
  • Explain why a non-nullable value fits into a nullable variable, but not the other way round
  • Recognize what Dart allows — and refuses — on a nullable value
  • Compare Object, Object?, and dynamic, and choose between late and a nullable type
  • Decide which variables in a program should be nullable

1. Two Kinds of Types

Every type in Dart has two versions: a non-nullable one, which always holds a real value, and a nullable one, which may also hold null.

Analogy — think of a sign-up form. "Name" is a required field: the form will not go through while it is empty. "Middle name (optional)" may be left blank. A non-nullable type is a required field; a nullable type is an optional one.

Non-nullableNullableCan hold null?
StringString?only the ? version
intint?only the ? version

2. Non-Nullable Is the Default

Every type you have written so far — String, int, double, bool — is non-nullable. Giving one of them null is a compile-time error:

dart
void main() {
  String name = null;
  print(name);
}
output
Error: A value of type 'Null' can't be assigned to a variable of type 'String'.
  String name = null;
                ^

The value null has its own type, Null, and Null does not fit into String. So when you see String, you can be sure there is real text inside.


3. Making a Type Nullable with ?

Write ? straight after the type, with no space. Now null is allowed:

dart
void main() {
  String name = 'Sara';
  String? middleName = null;
  print(name);
  print(middleName);
}
output
Sara
null

Read String? as "a String or null". Printing a null value shows the word null.


4. Nullable Variables Start as null

You rarely need to write = null. A nullable variable declared without a value starts as null automatically — your editor even suggests removing = null as unnecessary:

dart
void main() {
  String? middleName;
  int? score;
  print(middleName);
  print(score);
}
output
null
null

The same works at the top level. Compare this with Lecture 004, where String name; outside a function was an error:

dart
String? nickname;

void main() {
  print(nickname);
}
output
null

Dart demanded a value only because String had no safe starting value. String? has one: null.


5. A Nullable Variable Can Hold Real Values Too

A nullable variable is not stuck at null. It can hold a real value, and go back to null later:

dart
void main() {
  int? score;
  print(score);
  score = 85;
  print(score);
  score = null;
  print(score);
}
output
null
85
null

Type safety still applies: score = 'eighty'; would be rejected, because int? means "an int or null" — nothing else.


6. Every Type Has a Nullable Version

The ? works on any type:

dart
void main() {
  String? city;
  int? age;
  double? temperature;
  bool? isPaid;
  print('$city, $age, $temperature, $isPaid');
}
output
null, null, null, null

num?, Object?, and every type you meet later work the same way.


7. Where Nullable Values Come From

You will not only write ? yourself — Dart's own tools hand you nullable values. In Lecture 010, int.tryParse returned null for bad text. That is why its result type is int?, and why var infers int?:

dart
void main() {
  var good = int.tryParse('42');
  int? bad = int.tryParse('forty-two');
  print(good);
  print(bad);
}
output
42
null

good is an int? even though it holds 42. Dart decides the type from what tryParse might return, not from what it happened to return this time.


8. Non-Nullable Fits into Nullable

A real value always fits where "a value or null" is allowed:

dart
void main() {
  String city = 'Lahore';
  String? lastCity = city;
  print(lastCity);
}
output
Lahore

9. Nullable Does Not Fit into Non-Nullable

The other direction is refused, even when the value is not null:

dart
void main() {
  var count = int.tryParse('5');
  int total = count;
  print(total);
}
output
Error: A value of type 'int?' can't be assigned to a variable of type 'int'.
  int total = count;
              ^

int promises "never null". Dart cannot keep that promise with a value that might be null, so it stops you before the program runs. Ways to get the value out safely are the subject of Lectures 014–016.


10. What Dart Refuses on a Nullable Value

Anything that needs a real value is refused — arithmetic, and properties such as length:

dart
String? nickname = 'Ali';

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

nickname holds 'Ali', but its type is String?, and null has no length. The hint mentions ?. — that operator is the topic of the next lecture.

Note — if you try this with a local variable you have just given a value (String? name = 'Ali'; inside main()), Dart may accept name.length: it can see the value is not null. That cleverness is called type promotion, and we study it in Lecture 016. Values from tryParse and top-level variables, like the ones here, are not promoted.


11. What You Can Still Do with a Nullable Value

Some things work for every value, including null: printing, putting it in a string, toString(), and comparing it with null using ==:

dart
void main() {
  var age = int.tryParse('twenty');
  print(age);
  print('Age: $age');
  print(age.toString());
  print(age == null);
}
output
null
Age: null
null
true

age == null gives a bool answer. Acting on that answer needs if, from Lecture 022; comparisons get their own lecture, Lecture 018.


12. Why the ? Matters: A Crash Dart Prevents

dynamic (Lecture 011) switches the checks off, so it shows what happens without null safety:

dart
void main() {
  dynamic nickname = null;
  print(nickname.length);
}
output
Unhandled exception:
NoSuchMethodError: The getter 'length' was called on null.
Receiver: null
Tried calling: length

That is the classic null crash, and it happens while the program runs. With String?, the same mistake is a compile-time error (section 10); with String, it cannot happen at all.


13. Object, Object?, and dynamic

In Lecture 011 we promised one more difference between Object and dynamic. Here it is: Object is non-nullable, like every type without ?:

dart
void main() {
  Object item = null;
  print(item);
}
output
Error: A value of type 'Null' can't be assigned to a variable of type 'Object'.
 - 'Object' is from 'dart:core'.
  Object item = null;
                ^

Object? holds any value or null. dynamic accepts null too:

dart
void main() {
  Object? item = 'Ali';
  dynamic value = 20;
  item = null;
  value = null;
  print(item);
  print(value);
}
output
null
null
TypeAny value?null?Checked?
ObjectYesNoYes
Object?YesYesYes
dynamicYesYesNo

So Object? is the truly checked version of "anything at all".


14. Nullable or late?

In Lecture 006, late let us declare a String now and assign it later, and we said Dart needs that promise because of null safety. Now you can see why: a String has no safe starting value, so late says "a real value will arrive before anyone reads it."

  • Use late String when the value will arrive — it is only delayed.
  • Use String? when the value may never arrive — it is genuinely optional.

A middle name may never exist: String?. A cashier's name set at the start of main() will exist: late String.


15. Choosing Nullable or Non-Nullable

Ask one question for each variable: can "no value" be a real, correct answer here?

ValueCan it be missing?Type
a student's nameNoString
a middle nameYesString?
a price in a shopNoint or double
an exam score not taken yetYesint?
a number read with tryParseYesint?

Make a variable nullable only when the answer is yes: non-nullable values need no extra care.


16. A Real-World Example

Sara fills in a sign-up form. Name and age are required; the middle name and phone number are optional, and she leaves them blank. The age arrives as text:

dart
void main() {
  String name = 'Sara';
  String? middleName;
  String? phone;
  int? age = int.tryParse('18');

  print('Name: $name');
  print('Middle name: $middleName');
  print('Phone: $phone');
  print('Age: $age');
  print('Name letters: ${name.length}');
  print('Age missing: ${age == null}');
}
output
Name: Sara
Middle name: null
Phone: null
Age: 18
Name letters: 4
Age missing: false

name is non-nullable, so name.length works directly. The blank optional fields are String?. age is int? because tryParse might fail — we can print it and compare it with null, but not calculate with it yet.


Code Examples

Example 1 — Non-Nullable and Nullable Side by Side

dart
void main() {
  String name = 'Ali';
  String? middleName;
  print('Name: $name');
  print('Middle name: $middleName');
}
output
Name: Ali
Middle name: null

Example 2 — A Nullable Variable Changing Value

dart
void main() {
  int? goals;
  print(goals);
  goals = 2;
  print(goals);
  goals = null;
  print(goals);
}
output
null
2
null

Example 3 — Nullable Versions of Every Type

dart
void main() {
  String? city = 'Karachi';
  double? temperature;
  bool? isPaid = true;
  print('City: $city');
  print('Temperature: $temperature');
  print('Paid: $isPaid');
}
output
City: Karachi
Temperature: null
Paid: true

Example 4 — tryParse Returns a Nullable Type

dart
void main() {
  int? quantity = int.tryParse('12');
  double? weight = double.tryParse('heavy');
  print(quantity);
  print(weight);
}
output
12
null

Example 5 — Non-Nullable into Nullable

dart
void main() {
  String winner = 'Zayan';
  String? lastWinner;
  print(lastWinner);
  lastWinner = winner;
  print(lastWinner);
}
output
null
Zayan

Example 6 — Comparing with null

dart
void main() {
  var first = int.tryParse('30');
  var second = int.tryParse('thirty');
  print(first == null);
  print(second == null);
}
output
false
true

How it works

  1. '30' is a valid number, so first holds 30 and is not null.
  2. 'thirty' is not, so second holds null.

Example 7 — Student Marks with a Missing Mark

dart
void main() {
  String student = 'Ali';
  int? math = int.tryParse('80');
  int? science = int.tryParse('');

  print('Student: $student');
  print('Math: $math');
  print('Science: $science');
  print('Science missing: ${science == null}');
}
output
Student: Ali
Math: 80
Science: null
Science missing: true

How it works

  1. The marks arrive as text from a form, so they are read with tryParse.
  2. The science box was left empty: '' is not a number, so science is null.
  3. Adding the marks would be refused, because both are int? — Lecture 014 shows how to handle that.

Example 8 — Shopping Total with an Optional Coupon

dart
void main() {
  String product = 'Notebook';
  int price = 500;
  int quantity = 3;
  String? couponCode;

  int total = price * quantity;
  print('Product: $product');
  print('Total: $total');
  print('Coupon: $couponCode');
}
output
Product: Notebook
Total: 1500
Coupon: null

How it works

  1. Price and quantity are always known, so they are non-nullable — and the multiplication just works.
  2. A coupon is optional, so couponCode is String? and starts as null.

Example 9 — Bank Balance and the Last Deposit

dart
void main() {
  double balance = 1000.0;
  double? lastDeposit;
  print('Balance: $balance, last deposit: $lastDeposit');

  double deposit = 250.5;
  balance = balance + deposit;
  lastDeposit = deposit;
  print('Balance: $balance, last deposit: $lastDeposit');
}
output
Balance: 1000.0, last deposit: null
Balance: 1250.5, last deposit: 250.5

How it works

  1. A new account always has a balance, so balance is double.
  2. Before any deposit there is no "last deposit", so lastDeposit is double?.
  3. The non-nullable deposit fits into lastDeposit (section 8).

Example 10 — Temperature Readings

dart
void main() {
  double? morning = double.tryParse('18.5');
  double? noon = double.tryParse('ERR');
  print('Morning: $morning');
  print('Noon: $noon');
  print('Noon reading failed: ${noon == null}');
}
output
Morning: 18.5
Noon: null
Noon reading failed: true

How it works

  1. A sensor sends readings as text; a broken reading arrives as 'ERR'.
  2. double.tryParse turns the good reading into 18.5 and the broken one into null.

Common Beginner Mistakes

1. Giving null to a non-nullable variable

dart
void main() {
  // String middleName = null; // ✗
}
output
Error: A value of type 'Null' can't be assigned to a variable of type 'String'.

If the value can be missing, write String? middleName;.

2. Copying a nullable value into a non-nullable variable

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

The same goes for count + 1. When the text is certainly a number, use int.parse, which returns int.

3. Expecting var with null to make a nullable type

dart
void main() {
  var score = null;
  score = 'ninety';
  print(score);
}
output
ninety

No error — that is the problem. Like var score; in Lecture 005, var score = null; makes a dynamic variable, which accepts anything. Write the type: int? score;.


Best Practices

  • Keep types non-nullable unless "no value" is a real, correct answer.
  • Write String? name; rather than String? name = null; — null is already the default.
  • Use late for values that will arrive later, and ? for values that may never arrive.
  • Prefer Object? to dynamic when a variable can hold anything, including null.
  • Never use var x = null; — write the nullable type you mean.

Mini Challenge

Zayan joins a reading club. His name is 'Zayan' (required), he has no nickname (optional), his age arrives as the text '6', and his favourite colour is 'blue' (optional). Choose a type for each variable, then print them.

Your program should print exactly:

output
Name: Zayan
Nickname: null
Age: 6
Favourite colour: blue

Try it yourself before opening the solution.

Show solution
dart
void main() {
  String name = 'Zayan';
  String? nickname;
  int? age = int.tryParse('6');
  String? favouriteColour = 'blue';

  print('Name: $name');
  print('Nickname: $nickname');
  print('Age: $age');
  print('Favourite colour: $favouriteColour');
}
output
Name: Zayan
Nickname: null
Age: 6
Favourite colour: blue

How it works

  1. A name is required, so name is String.
  2. Nickname and colour are optional, so both are String?; the nickname starts as null.

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. Declare a nullable String named nickname without a value, and print it.

  2. Declare int? goals;, print it, assign 3, and print it again.

  3. Which of these lines compile? Explain each.

    dart
    int age = null;
    int? year = null;
    double? price = 9.5;
    bool isOpen = null;

Application

  1. Use double.tryParse on '72.5' and on 'hot'. Store each result in a variable with an explicit type, and print both.

  2. This program does not compile. Fix it by changing only the type, so that it prints null:

    dart
    void main() {
      String city = null;
      print(city);
    }
  3. This program does not compile. Fix it by changing only the type of total, so that it prints 7:

    dart
    void main() {
      var count = int.tryParse('7');
      int total = count;
      print(total);
    }
  4. Predict the output without running it, then check:

    dart
    void main() {
      String? a;
      String b = 'Sara';
      print(a);
      a = b;
      print(a);
      a = null;
      print(a);
      print(b);
    }
  5. Sara opens a bank account with a balance of 1500 and has made no withdrawal yet. Choose types for balance and lastWithdrawal, and print Balance: 1500 and Last withdrawal: null.

Challenge

  1. This program does not compile. Explain in one sentence why, then fix it by changing one word so it prints 500:

    dart
    void main() {
      var price = int.tryParse('250');
      print(price * 2);
    }
  2. Write a student record for Ali: name 'Ali', roll number 12, an email address he has not given, and an exam score for an exam he has not taken yet. Choose the right type for each, and print:

    output
    Name: Ali
    Roll number: 12
    Email: null
    Exam score: null

Practice Answers

Question 1 — Solution

dart
void main() {
  String? nickname;
  print(nickname);
}
output
null

Question 2 — Solution

dart
void main() {
  int? goals;
  print(goals);
  goals = 3;
  print(goals);
}
output
null
3

Question 3 — Solution

Only the two nullable lines compile:

  • int age = null; — ✗ int is non-nullable.
  • int? year = null; — ✓ int? allows null (though int? year; is the usual way).
  • double? price = 9.5; — ✓ a nullable variable can hold a real value.
  • bool isOpen = null; — ✗ bool is non-nullable.

Question 4 — Solution

dart
void main() {
  double? first = double.tryParse('72.5');
  double? second = double.tryParse('hot');
  print(first);
  print(second);
}
output
72.5
null

Question 5 — Solution

Add ? so the type allows null:

dart
void main() {
  String? city = null;
  print(city);
}
output
null

Writing String? city; gives the same result and is the preferred style.

Question 6 — Solution

count is an int?, so total must be one too:

dart
void main() {
  var count = int.tryParse('7');
  int? total = count;
  print(total);
}
output
7

Question 7 — Solution

dart
void main() {
  String? a;
  String b = 'Sara';
  print(a);
  a = b;
  print(a);
  a = null;
  print(a);
  print(b);
}
output
null
Sara
null
Sara

Question 8 — Solution

dart
void main() {
  int balance = 1500;
  int? lastWithdrawal;
  print('Balance: $balance');
  print('Last withdrawal: $lastWithdrawal');
}
output
Balance: 1500
Last withdrawal: null

Question 9 — Solution

tryParse returns int?, and Dart refuses arithmetic on a value that might be null. The text is certainly a number, so use parse, which returns a non-nullable int:

dart
void main() {
  var price = int.parse('250');
  print(price * 2);
}
output
500

Question 10 — Solution

dart
void main() {
  String name = 'Ali';
  int rollNumber = 12;
  String? email;
  int? examScore;

  print('Name: $name');
  print('Roll number: $rollNumber');
  print('Email: $email');
  print('Exam score: $examScore');
}
output
Name: Ali
Roll number: 12
Email: null
Exam score: null

Every student has a name and a roll number, so those are non-nullable. The email is optional and the score does not exist yet, so both are nullable.


Quick Revision

  1. What is the difference between String and String??
  2. What value does int? score; start with?
  3. Why is String? nickname; allowed at the top level, when String nickname; is not?
  4. What type does int.tryParse return, and why?
  5. Can you assign a String to a String? variable? And a String? to a String?
  6. Name three things you can do with a nullable value, and one thing you cannot.
  7. How are Object, Object?, and dynamic different?
  8. When do you choose late instead of a nullable type?

Key Takeaways

  • Every type has a non-nullable version (String) and a nullable version (String?).
  • Types are non-nullable by default; only a ? type can hold null.
  • A nullable variable declared without a value starts as null — even at the top level.
  • Tools like tryParse return nullable types, because their result might be missing.
  • A non-nullable value fits into a nullable variable; a nullable value does not fit into a non-nullable one.
  • Dart refuses arithmetic and properties like length on a nullable value, catching null mistakes at compile time instead of run time.
  • Object excludes null; Object? and dynamic allow it, but only Object? stays checked.
  • Use ? for values that may never exist, late for values that arrive later, and keep everything else non-nullable.

What's Next?

Lecture 014 — Null-Aware Operators. Dart now stops us from adding to an int? or reading the length of a String?. But real programs still need to use those values. Next we learn the operators built for exactly that — ?., ??, and ??= — which let us work with a value that might be null, and supply a fallback when it is.


Video lecture

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