skip to content

In Dart, how does a late final field differ from a plain final field, and what happens if code assigns it twice?

level: middleimportance: should knowfreq 45%

answer

  1. assign once, at any later time
  2. single assignment checked at runtime
  3. has already been initialized
  4. public late final exposes a setter
  5. no late final with const constructor

basics

~20 s

A plain final field must get its value from an initializer or the constructor; a late final field without an initializer may be assigned once at any later point. The single assignment is checked at runtime, and a second write throws a LateInitializationError.

solid answer

~40 s

A plain `final` field has to be set during construction — at its declaration, by a `this.x` parameter, or in the initializer list — so the compiler can prove it is assigned exactly once. A `late final` field without an initializer lifts the first half: it can be assigned later, for example after an object it depends on is ready. It keeps the second half at runtime — a second assignment throws a `LateInitializationError` whose message says the field `has already been initialized`, and reading it early throws `has not been initialized`. For locals, the analyzer reports a provable second write as `late_final_local_already_assigned`. Two traps: a public `late final` field without an initializer exposes a **public setter**, and a class with a `const` constructor cannot have a `late final` field.

code

dart · 13 lines
dart
class Session {
  late final String _token;
  String get token => _token;

  void start(String t) => _token = t;
}

void main() {
  final s = Session();
  s.start('abc');
  print(s.token); // abc
  s.start('xyz'); // throws: Field '_token' has already been initialized.
}

go deeper

for a junior

Recall that final is set during construction while late final can be set later, but still only once.

for a middle

Explain the runtime single-assignment check, both error messages, and the difference between late final with and without an initializer.

for a senior

Spot the public-setter trap in reviews and prefer private late final with a getter, a factory constructor, or a declaration initializer.

for a principal

Treat write-once runtime slots as part of an API's contract and decide where statically proven construction is worth the extra factory code.

## Plain `final`: assigned once, proven statically A `final` variable can be assigned **only once**. For an instance field, Dart proves that at compile time by requiring the assignment during construction, in one of these places: - an **initializer** at the declaration: `final int retries = 3;` - an **initializing formal**: `Client(this.baseUrl);` - the **initializer list**: `Client(String url) : baseUrl = Uri.parse(url);` Anything else — assigning in the constructor body or in a method — is a compile error, because the field would be observable before it is set. ## `late final`: assigned once, checked at runtime `late final` keeps the **assign-once** rule but moves its enforcement to runtime, the same way `late` moves the initialisation check: ```dart class Session { late final String token; void start(String t) { token = t; // first write: fine } } ``` The runtime tracks two states for the field: 1. **Unassigned** — a read throws `LateInitializationError: Field 'token' has not been initialized.` 2. **Assigned** — a read returns the value; another write throws `LateInitializationError: Field 'token' has already been initialized.` This models values that are **set once, later, and immutable afterwards**: a controller created once the surrounding framework hands you what it needs, a connection opened by an explicit start step, a value computed after the constructor has finished because it needs `this`. For **local** `late final` variables, the analyzer goes further where it can: if flow analysis proves a second assignment will happen after the first, it reports `late_final_local_already_assigned` at compile time. ## The two forms of `late final` | Form | When set | Assignable from outside the initializer? | |---|---|---| | `late final T x;` | once, by any code with access | yes, exactly once | | `late final T x = expr;` | lazily on first read | no — compile error | The second form is a **lazy cached value**; it has no setter. The first form is a **write-once slot** and does have a setter. ## Trap: a public setter you did not intend Effective Dart's guideline **AVOID public `late final` fields without initializers** exists because of that setter. A normal `final` field has only a getter. A `late final` field without an initializer must be assignable after construction, so Dart generates a setter — and if the field is public, **any caller can perform that one assignment**, possibly before your own code does, which then fails with `has already been initialized`. The recommended alternatives: - don't use `late` — compute the value before construction, for example in a **factory constructor**; - initialise the `late` field at its declaration, which removes the setter; - keep the `late final` field **private** and expose a public getter. ## Other restrictions - A class with a generative **`const` constructor** cannot declare a `late final` field (`late_final_field_with_const_constructor`): a constant object must be fully known at compile time. - `late final` does not make the **object** immutable. `late final List<int> ids;` fixes which list the field refers to, not the list's contents. - Like any `late` variable, there is **no API to ask whether it has been set**. ## A familiar Flutter shape In Flutter code the most common `late final` field is a controller created in a `State` subclass once the state object is attached — `late final AnimationController _controller;` assigned exactly once and disposed later. The field is private, assigned in one place, and never reassigned, which is exactly the shape Effective Dart endorses.

  • In Dart, why does Effective Dart advise against public late final fields without initializers?
    Such a field needs a setter so it can be assigned after construction, and a public field means a public setter. Any caller can then make the single allowed assignment, possibly before the class does, which turns the class's own write into a runtime error. Make the field private with a getter, initialise it at declaration, or compute it in a factory constructor.
  • In Dart, does late final make a List field's contents immutable?
    No. `final`, with or without `late`, fixes the reference: the field cannot be pointed at another list. The list itself stays mutable unless it is created unmodifiable, for example with `List.unmodifiable`, or is a `const` list.

saying these in an interview costs you the question

  • A late final field can be assigned as many times as needed until it is read
  • A second assignment to a late final field is silently ignored
  • late final fields have only a getter, like plain final fields
  • late final makes the referenced object deeply immutable
  • A plain final field may be assigned later in any instance method