Skip to main content
The late modifier is a keyword used in Dart’s sound null safety system to declare a variable that is not initialized at the point of declaration. It enforces a contract between the developer and the compiler, asserting that the variable will be assigned a value before it is accessed, thereby bypassing the compiler’s static analysis for definite assignment.

Syntax

The modifier is placed before the type declaration.
// Declaration without immediate initialization (Deferred)
late String description;

// Declaration with initializer (Lazy)
late String derivedValue = _calculate();

Semantic Behaviors

The late modifier induces two distinct behaviors depending on whether an initializer is provided.

1. Deferred Initialization

When a late variable is declared without an assignment, it acts as a placeholder. The compiler suspends static analysis regarding the variable’s initialization status, shifting the check to runtime.
  • Contract: The variable must be written to before it is read.
  • State: The variable exists in an uninitialized state until a value is assigned.
  • Assignment: Can be assigned multiple times unless combined with final.
late int temperature;

void setTemperature() {
  temperature = 25; // Valid assignment transitions state to initialized
}

void readTemperature() {
  print(temperature); // Throws error if setTemperature() was not called
}

2. Lazy Initialization

When a late variable is declared with an assignment, the initialization logic is executed lazily. The expression on the right-hand side is evaluated only when the variable is accessed for the first time.
  • Execution: The initializer runs exactly once (or never, if the variable is never accessed).
  • Scope Access: Because initialization is deferred until runtime usage, late instance variables can access this and other instance members, which is not possible with standard field initializers.
class HeavyComputation {
  // _performCalculation() is not called when the class is instantiated.
  // It is called only when 'result' is first accessed.
  late final int result = _performCalculation();

  int _performCalculation() {
    return 42;
  }
}

late final Semantics

Combining late with final creates a variable that is initialized lazily or deferred, but can only be assigned once.
  • Reassignment: Attempting to reassign a late final variable after it has been initialized results in a runtime error.
  • Unassigned State: A late final variable without an initializer can be assigned exactly one time at runtime.
late final String uniqueId;

void initialize(String id) {
  uniqueId = id; // Allowed: First assignment
}

void update(String id) {
  uniqueId = id; // Throws LateInitializationError: Field 'uniqueId' has already been initialized.
}

Runtime Safety

While late satisfies static analysis, it relies on runtime checks to ensure memory safety.
  • LateInitializationError: If a late variable is read before it has been written to, the Dart runtime throws a LateInitializationError.
  • Nullability: The late modifier can be applied to nullable types (e.g., late String?).
    • Deferred Assignment: Unlike standard nullable variables which implicitly default to null, a late nullable variable starts in an uninitialized state. Reading it before assignment throws an exception rather than returning null.
    • Lazy Evaluation: When used with an initializer (e.g., late String? cache = _load();), late is required to defer the execution of the initializer until access. This allows for the lazy loading of data that may result in a null value.
Master Dart with Deep Grasping Methodology!Learn More