Skip to main content
A final variable in Dart is a single-assignment variable. Once a final variable is initialized, its memory reference is locked and cannot be reassigned to a new value. Unlike compile-time constants (const), final variables are evaluated at runtime. Top-level and static final variables are lazily initialized upon their first read, whereas instance variables are evaluated at instance creation.

Syntax and Type Inference

You can declare a final variable with an explicit type or rely on Dart’s type inference. If the type is omitted, the analyzer infers it from the assigned value.
final String explicitString = 'Dart';
final inferredString = 'Dart'; // Inferred as String

Local Scope Assignment Rules

A final variable must be assigned exactly once before it is read. Within a local scope (such as a function body), a final variable can be declared uninitialized (a “blank final”) as long as the compiler can guarantee it is assigned before its first use.
void main() {
  final int status;

  // Valid: First and only assignment within local scope
  status = 200; 

  // Compile-time error: The final variable 'status' can only be set once.
  // status = 404; 
}

Late Initialization

Due to Dart’s sound null safety, top-level and instance final variables must typically be initialized at declaration or during object construction. To declare an uninitialized top-level or instance final variable that will be assigned exactly once at a later point, you must use the late final modifier.
late final String configuration;

void loadConfig() {
  // Valid: First and only assignment of a late final variable
  configuration = 'Loaded'; 
  
  // Error: Late final variable 'configuration' has already been initialized.
  // configuration = 'Reloaded'; 
}

Runtime Evaluation

Because final variables are evaluated at runtime, they can be assigned values derived from function calls, object instantiations, or other runtime calculations.
// Valid: DateTime.now() is executed at runtime
final DateTime initializationTime = DateTime.now(); 

Shallow Immutability

The final keyword enforces reference immutability, not object immutability. If a final variable holds a reference to a mutable object (like a List or Map), the internal state of that object can still be modified. The restriction only prevents the variable identifier from pointing to a completely different object in memory.
final List<int> sequence = [1, 2, 3];

// Valid: Mutating the internal state of the referenced object
sequence.add(4); 

// Compile-time error: Attempting to reassign the reference to a new List
// sequence = [10, 20]; 

Class Instance Variables

When used as instance variables within a class, final fields must be initialized before the constructor body executes (unless marked late). This can be done at the point of declaration, via initializing formal parameters, or within the constructor’s initializer list.
class NetworkResponse {
  final int statusCode;
  final String body;
  final DateTime timestamp = DateTime.now(); // Initialized at declaration

  // Initialized via formal parameters
  NetworkResponse(this.statusCode, this.body);
  
  // Alternatively, initialized via the initializer list
  NetworkResponse.error(this.statusCode) : body = 'Error occurred';
}
Tired of Poor Dart Skills? Fix That With Deep Grasping!Learn More