flutter
/

Dart Null Safety – A Complete Guide

Last Sync: Today

On this page

15
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Null Safety – A Complete Guide

What is Null Safety?

Null safety is a feature in Dart that helps you avoid null reference errors – those dreaded runtime crashes where you try to use a variable that unexpectedly contains null. With null safety, variables are non‑nullable by default, meaning they cannot contain null unless you explicitly allow it. This moves null checks from runtime to compile time, making your code more robust and predictable.

Nullable vs Non‑Nullable Types

In Dart, every type now has a nullable counterpart. By default, types are non‑nullable. To allow a variable to hold null, add a ? after the type.

DARTRead-only
1
void main() {
  int nonNullable = 42;     // cannot be null
  // nonNullable = null;     // Error
  
  int? nullable = 42;       // can be null
  nullable = null;          // OK
}

The ? Operator (Nullable Type)

Adding ? to a type creates a nullable version. You can assign either a value of that type or null.

DARTRead-only
1
void main() {
  String? name;       // initialised as null
  print(name);         // null
  name = 'Alice';
  print(name.length);  // OK after assignment
}

The ! Operator (Null Assertion)

Use the ! operator when you are absolutely sure that a nullable variable is not null at that point. It asserts that the value is non‑null and returns it as a non‑nullable type. If you're wrong, it throws a runtime error – so use it sparingly.

DARTRead-only
1
void main() {
  String? maybeName = getName();
  int length = maybeName!.length; // throws if maybeName is null
}

String? getName() => 'Alice';

The late Keyword

Sometimes you know a variable will be initialised before it's used, but not immediately at declaration. Use late to tell Dart to trust you. If you fail to initialise it before use, you get a runtime error.

DARTRead-only
1
void main() {
  late String description;
  // print(description); // would throw LateInitializationError
  description = 'Hello';
  print(description); // OK
}

late is also useful for lazy initialisation – expensive computations that should only run when the variable is first accessed.

DARTRead-only
1
class HeavyData {
  late String data = _loadData(); // called only when used
  
  String _loadData() {
    print('Loading...');
    return 'big data';
  }
}

Null‑Aware Operators

Dart provides several operators to handle nullable values safely and concisely.

?. – Safe Member Access

Access a property or method only if the object is not null; otherwise, the expression evaluates to null.

DARTRead-only
1
void main() {
  String? name;
  print(name?.length); // null (no error)
  
  name = 'Dart';
  print(name?.length); // 4
}

?? – If‑Null Operator

Returns the left operand if it's not null; otherwise returns the right operand.

DARTRead-only
1
void main() {
  String? name;
  String displayName = name ?? 'Guest';
  print(displayName); // Guest
}

??= – Null‑Aware Assignment

Assigns a value to a variable only if that variable is currently null. If it's already non‑null, the assignment is ignored.

DARTRead-only
1
void main() {
  String? name;
  name ??= 'Alice';
  print(name); // Alice
  
  name ??= 'Bob';
  print(name); // still Alice (already set)
}

Type Promotion

Dart's flow analysis can promote nullable types to non‑nullable within certain scopes after a null check, allowing you to use them safely without the ! operator.

DARTRead-only
1
void main() {
  String? nullable = 'Hello';
  
  if (nullable != null) {
    // nullable is promoted to String (non‑nullable) here
    print(nullable.length); // no ! needed
  }
}

This also works with is checks and logical operators like && and ||.

Working with Collections

Collections can also be nullable, and their elements can be nullable. Be careful with type arguments.

DARTRead-only
1
void main() {
  List<int?> nullableElements = [1, null, 3];
  List<int>? nullableList = null;
  
  // Safe access
  int? first = nullableElements[0]; // 1
  int? second = nullableElements[1]; // null
}

Migration from Pre‑Null Safety

If you have existing Dart code without null safety, you can migrate using dart migrate. The tool helps you add ?, late, and other null safety features. After migration, run dart pub get and fix any remaining analysis issues.

Best Practices

    • Prefer non‑nullable types – only make a variable nullable if null is a meaningful state.
    • **Use late for non‑nullable variables that are initialised after construction (e.g., dependency injection).
    • Avoid overusing ! – prefer safe access (?.) or explicit null checks with promotion.
    • Use ?? for default values – it's clearer than a ternary with a null check.
    • Take advantage of type promotion – write code that naturally checks for null before use.
    • Be explicit with collection types – decide whether the collection itself can be null or its elements.

Complete Example

DARTRead-only
1
void main() {
  // Nullable variables
  String? userName;
  int? age;
  
  // Simulate fetching data
  userName = fetchUserName();
  age = fetchAge();
  
  // Safe access
  print('Hello, ${userName ?? 'Guest'}!');
  
  // Type promotion after check
  if (age != null) {
    print('You are $age years old.');
  }
  
  // Null‑aware assignment
  String? nickname;
  nickname ??= userName;
  print('Nickname: $nickname');
  
  // late variable
  late String fullName = getFullName();
  print('Full name: $fullName');
}

String? fetchUserName() => 'Alice';
int? fetchAge() => 30;
String getFullName() => 'Alice Smith';

Key Takeaways

    • Null safety eliminates null reference errors at compile time.
    • Types are non‑nullable by default; add ? to allow null.
    • Use late for variables initialised after declaration.
    • Null‑aware operators (?., ??, ??=) provide safe and concise handling.
    • Type promotion lets you use nullable variables as non‑nullable after checks.
    • Migrate existing code with dart migrate.

Try it yourself

void main() {
  String? name = null;
  
  // Try using null-aware operators
  print('Hello, ${name ?? 'World'}!');
  
  // Safe access
  print('Length: ${name?.length}');
  
  // Null-aware assignment
  name ??= 'Dart';
  print('Now name is: $name');
}

Test Your Knowledge

Q1
of 4

How do you declare a nullable integer in Dart?

A
int? number
B
int number?
C
nullable int number
D
int number = null
Q2
of 4

What does the `??` operator do?

A
Returns the left operand if it's not null, otherwise the right operand
B
Returns the right operand if left is null
C
Performs a null check and throws an error
D
Assigns a value only if the variable is null
Q3
of 4

What happens if you access a `late` variable before it's initialised?

A
It returns null
B
It throws a LateInitializationError
C
It returns a default value
D
It compiles but may crash at runtime
Q4
of 4

What is type promotion in Dart?

A
Automatically converting a nullable type to non‑nullable after a null check
B
Manually casting a variable with `as`
C
Using `!` to assert non‑null
D
Declaring a variable with `var`

Frequently Asked Questions

What is null safety and why should I use it?

Null safety is a feature that prevents variables from containing null unless explicitly allowed. It eliminates null reference errors at compile time, making your code more robust and predictable. It also improves performance by enabling optimizations.

How do I make a variable nullable?

Add a ? after the type: int? nullableInt;. This variable can now hold either an integer or null.

What is the difference between `?` and `!`?

? is used to declare a nullable type (e.g., String? name). ! is the null assertion operator used to tell the compiler that you are sure a nullable variable is not null at that point, converting it to a non‑nullable type. If you're wrong, it throws a runtime error.

When should I use `late`?

Use late for non‑nullable variables that are initialized after the constructor runs (e.g., dependency injection, init methods) or for lazy initialization of expensive resources. But be careful: accessing a late variable before it's initialized throws a LateInitializationError.

What are null-aware operators?

Dart provides several null-aware operators: ?. (safe member access), ?? (if‑null operator), and ??= (null‑aware assignment). They allow concise and safe handling of nullable values without explicit if checks.

How does type promotion work with null safety?

After a null check (e.g., if (nullable != null)), Dart promotes the nullable variable to a non‑nullable type within that scope, so you can safely access its members without using !. This also works with &&, ||, and is checks.

How do I migrate existing Dart code to null safety?

Use the dart migrate tool. It analyzes your code and suggests where to add ?, late, required, etc. After migration, run dart pub get and fix any remaining analysis issues. It's recommended to migrate in small, incremental steps.

Can collections have nullable elements?

Yes, you can have collections with nullable element types, e.g., List<int?> can contain int or null. The collection itself can also be nullable: List<int>? means the list reference can be null, but if it's non‑null, its elements are non‑nullable.

What's the difference between `??` and `??=`?

?? returns the left operand if it's not null; otherwise it returns the right operand. It's an expression. ??= assigns the right value to the left operand only if the left operand is null, and it returns the new value. It's a compound assignment operator.

Is null safety optional in Dart?

Since Dart 2.12, null safety is the default. However, you can still use legacy code without null safety, but mixing null‑safe and non‑null‑safe code requires careful migration. It's recommended to migrate all code to null safety.

Previous

dart functions

Next

dart list

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.