Null Safety

Dart's null safety system — nullable types, safe calls, late initialization, and common patterns

Dart enforces null safety by default. Variables cannot hold null unless explicitly declared nullable.

Nullable types

String name = 'Dart';      // non-nullable — cannot hold null
// name = null;             // compile error

String? nullable = null;   // nullable — can hold null
nullable = 'hello';        // ok

Safe access and operators

String? nullable;

// Null-aware access operator ?.
var length = nullable?.length;       // null if nullable is null

// Null-coalescing operator ??
var value = nullable ?? 'default';  // 'default' if null

// Null-coalescing assignment ??=
nullable ??= 'hello';               // assigns only if null

// Null assertion operator ! (throws if null)
var definite = nullable!;           // asserts non-null, throws if null
var len = nullable!.length;         // use when you know it's not null

// Conditional property access
var upper = nullable?.toUpperCase();  // null if nullable is null

Type promotion

Dart automatically promotes nullable types after null checks:

String? maybeName;

if (maybeName != null) {
  // maybeName is promoted to String here — no ! needed
  print(maybeName.length);   // safe
}

// Also works with type checks
Object? value;
if (value is String) {
  print(value.length);        // promoted to String
}

Late initialization

Use late for variables that will be initialized after declaration but before use:

class Service {
  late Database db;            // will be set before use

  void init() {
    db = Database();
  }

  void query() {
    db.execute('SELECT ...');  // throws if db was never set
  }
}

// Late with initializer — computed lazily on first access
late String description = expensiveCompute();

late vs nullable

  • Use late when you know the variable will be set before use
  • Use ? when null is a valid value at any point
late String name;       // will be set later, null is never valid
String? nickname;       // null is a valid value

Required named parameters

Make named parameters non-nullable with required:

// Without required, name could be null
void greet({String? name}) {
  print(name);
}

// With required, name must be provided and is non-null
void greetRequired({required String name}) {
  print(name);
}

greetRequired(name: 'Ada');   // ok
// greetRequired();            // compile error  name is required

Nullable collections

List<int>? maybeList;           // list itself may be null
List<int?> maybeNullItems;      // list items may be null

var items = [1, null, 3, null, 5];
items.whereType<int>().toList();        // [1, 3, 5] — filter out nulls
items.nonNulls.toList();                // [1, 3, 5]  Dart 3

Null safety in callbacks

// Local variables are promoted in closures only if final
String? name;
// After this check, name is promoted
if (name != null) {
  var length = name.length;  // ok
}

// For callbacks, use ! or null check
var list = ['a', null, 'b', null, 'c'];
var result = list.where((e) => e != null).map((e) => e!.toUpperCase()).toList();
// Or use nonNulls (Dart 3):
var result2 = list.nonNulls.map((e) => e.toUpperCase()).toList();

Common patterns

// Default value
var name = maybeName ?? 'unknown';

// Throw on null
var value = maybeValue ?? throw StateError('value is required');

// Return on null
String process(String? input) {
  if (input == null) return 'empty';
  return input.toUpperCase();
}

// let-style pattern with cascade
maybeObject?..method1()..method2();

// Ensure non-null in constructors
class Config {
  final String host;
  final int port;

  Config({required this.host, required this.port});
}