Skip to main content
Dart enforces immutability through two primary keywords: final and const. While both prevent variable reassignment, they differ fundamentally in their initialization timing, memory allocation, and depth of immutability.

The final Keyword

A final variable is a runtime constant. It can only be set once, but the value does not need to be known until the code executes.
  • Initialization: Occurs at runtime.
  • Assignment: Single-assignment. Once initialized, the reference cannot be changed.
  • Mutability: Shallow immutability. While the variable reference is frozen, the internal state of the object it points to may remain mutable (unless the object class itself is immutable).
// Valid: Value determined at runtime
final DateTime current = DateTime.now();

// Valid: Late initialization
final int logicResult;
if (true) {
  logicResult = 10;
}
// logicResult = 20; // Compilation Error: The final variable 'logicResult' can only be set once.

// Shallow Immutability
final List<String> names = ['Alice', 'Bob'];
names.add('Charlie'); // Valid: The list content can change
// names = ['Dave'];  // Error: The reference cannot change

The const Keyword

A const variable is a compile-time constant. The value must be completely resolved and frozen before the program runs. const is implicitly final.
  • Initialization: Occurs at compile-time.
  • Assignment: Must be assigned a constant value (literals, other constants, or results of arithmetic operations on constants) immediately upon declaration.
  • Mutability: Deep (transitive) immutability. Both the reference and the object’s internal state are frozen. Attempting to modify the internal state results in a runtime exception.
// Valid: Value known at compile-time
const double pi = 3.14159;
const double radius = 5;
const double area = pi * radius * radius; // Valid: Computed from other constants

// Invalid: Value relies on runtime execution
// const DateTime now = DateTime.now(); // Compilation Error

// Deep Immutability
const List<int> numbers = [1, 2, 3];
// numbers.add(4); // Runtime Error: Unsupported operation: Cannot add to an unmodifiable list

Canonicalization

Dart canonicalizes const objects. If two const variables are initialized with the exact same value or object construction, they share the same memory address. This does not apply to final variables.
const listA = [1, 2];
const listB = [1, 2];
final listC = [1, 2];

print(identical(listA, listB)); // true (Same instance in memory)
print(identical(listA, listC)); // false (Different instances)

Class-Level Constants

The behavior of constants changes slightly depending on the scope within a class definition.
  • Instance Variables: Can be final but cannot be const.
  • Static Fields: Can be static const or static final.
class Configuration {
  // Runtime constant, unique per instance
  final String environment; 
  
  // Compile-time constant, shared across all instances
  static const int maxRetries = 3; 

  Configuration(this.environment);
}

Constant Contexts

The const keyword can be used on the right side of an assignment to create a constant value, even if the variable holding it is not const.
// 'a' is a mutable variable holding an immutable list
var a = const [1, 2, 3]; 
a = [4, 5]; // Valid: Variable 'a' can be reassigned
// a.add(4); // Error: The list instance [1, 2, 3] is immutable

// 'b' is an immutable variable holding an immutable list
const b = [1, 2, 3]; 
// b = [4, 5]; // Error: 'b' cannot be reassigned
Master Dart with Deep Grasping Methodology!Learn More