Explain clearly about immutability vs mutability in flutter with some proper examples

#dart#flutter#immutability#mutability#const#final#state#widgets

Answer

Overview

Immutability means once an object is created, it cannot be changed. Mutability means the object can be modified after creation.

This concept is fundamental to Flutter — it directly affects how widgets rebuild, how state is managed, and how performance is optimized.


Immutable — Cannot Change After Creation

An immutable object's values are fixed forever once created. To "change" it, you must create a new object.

dart
// String is immutable in Dart
String name = 'Alice';
name = 'Bob';  // ❌ This does NOT modify 'Alice'
               // ✅ It creates a NEW String 'Bob' and points name to it

// const makes the variable itself immutable too
const pi = 3.14159;
// pi = 3.15;  // ❌ Compile error — const cannot be reassigned

// final — reference is immutable (assigned once)
final greeting = 'Hello';
// greeting = 'Hi';  // ❌ Error — final can only be set once

Immutable Class Example

dart
class User {
  final String name;   // Cannot change after construction
  final int age;       // Cannot change after construction

  const User({required this.name, required this.age});

  // To "update", create a new object with copyWith
  User copyWith({String? name, int? age}) {
    return User(
      name: name ?? this.name,
      age: age ?? this.age,
    );
  }
}

void main() {
  const user = User(name: 'Alice', age: 25);
  // user.name = 'Bob';  // ❌ Error — final field

  // ✅ Create a NEW User with changed name
  final updatedUser = user.copyWith(name: 'Bob');
  print(user.name);        // Alice (original unchanged)
  print(updatedUser.name); // Bob (new object)
}

Mutable — Can Change After Creation

A mutable object's values can be modified directly without creating a new object.

dart
// List is mutable by default
List<int> numbers = [1, 2, 3];
numbers.add(4);       // ✅ Modifies the SAME list
numbers[0] = 10;      // ✅ Modifies the SAME list
print(numbers);       // [10, 2, 3, 4]

// Map is mutable by default
Map<String, int> scores = {'Alice': 90};
scores['Bob'] = 85;   // ✅ Modifies the SAME map

// var allows reassignment
var count = 0;
count = 5;  // ✅ Allowed — var is mutable

Mutable Class Example

dart
class MutableUser {
  String name;   // No final — can be changed
  int age;       // No final — can be changed

  MutableUser({required this.name, required this.age});
}

void main() {
  final user = MutableUser(name: 'Alice', age: 25);
  user.name = 'Bob';  // ✅ Directly modifies the same object
  user.age = 30;      // ✅ Directly modifies the same object
  print(user.name);   // Bob
}

Note:

text
final user
means the reference is immutable (can't point to a different object), but the object's fields are still mutable if not declared
text
final
.


Immutability in Flutter Widgets

This is where immutability becomes critical. Flutter's entire widget system is built on immutability.

StatelessWidget — Fully Immutable

dart
class GreetingCard extends StatelessWidget {
  final String name;   // All fields MUST be final
  final int age;       // Cannot change after creation

  const GreetingCard({required this.name, required this.age});

  
  Widget build(BuildContext context) {
    return Text('Hello, $name! Age: $age');
  }
}

// Usage
GreetingCard(name: 'Alice', age: 25);
// To change name → Flutter creates a NEW GreetingCard widget
// The old one is discarded and replaced

StatefulWidget — Widget is Immutable, State is Mutable

dart
class Counter extends StatefulWidget {
  final String title;  // ✅ Immutable — widget config never changes

  const Counter({required this.title});

  
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count = 0;  // ✅ Mutable — state CAN change

  void increment() {
    setState(() {
      count++;  // Mutating state triggers rebuild
    });
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('${widget.title}: $count'),
        ElevatedButton(onPressed: increment, child: Text('Add')),
      ],
    );
  }
}

Key Insight: The

text
StatefulWidget
class itself is immutable (all fields are
text
final
). Only the separate
text
State
object is mutable.


Making Collections Immutable

dart
// ❌ Mutable list — can be modified
var mutableList = [1, 2, 3];
mutableList.add(4);  // ✅ Works

// ✅ Immutable list with const
const immutableList = [1, 2, 3];
// immutableList.add(4);  // ❌ Runtime error — cannot modify const list

// ✅ Unmodifiable list (runtime immutability)
final unmodifiable = List.unmodifiable([1, 2, 3]);
// unmodifiable.add(4);  // ❌ Runtime error — Unsupported operation

// ✅ Immutable map
const config = {'theme': 'dark', 'lang': 'en'};
// config['theme'] = 'light';  // ❌ Runtime error

Why Flutter Prefers Immutability

1. Performance —
text
const
Widgets Are Cached

dart
// ✅ Flutter reuses the SAME instance — no rebuild needed
const SizedBox(height: 16);
const Text('Hello World');
const Icon(Icons.star, color: Colors.yellow);

// ❌ Without const — new instance created every build
SizedBox(height: 16);  // New object each time parent rebuilds

2. Predictable State — No Accidental Mutations

dart
// ❌ Dangerous — mutable state shared by reference
class AppState {
  List<String> items = [];  // Mutable
}

final state = AppState();
final reference = state.items;  // Same list!
reference.add('oops');          // Accidentally modified state!
print(state.items);             // ['oops'] — state corrupted

// ✅ Safe — immutable state prevents accidental changes
class SafeState {
  final List<String> items;
  const SafeState({required this.items});

  SafeState addItem(String item) {
    return SafeState(items: [...items, item]);  // New list, new state
  }
}

3. Easier Comparison — Widget Rebuild Optimization

dart
// Flutter compares old widget vs new widget
// Immutable objects with == override make this fast and reliable

class UserState {
  final String name;
  final int age;
  const UserState({required this.name, required this.age});

  
  bool operator ==(Object other) =>
      other is UserState && name == other.name && age == other.age;

  
  int get hashCode => Object.hash(name, age);
}

// BLoC/Riverpod can skip rebuilds when state hasn't changed
// oldState == newState → skip rebuild → better performance

Comparison Table

AspectImmutableMutable
Can change after creation?NoYes
Dart keywords
text
final
,
text
const
text
var
, no modifier
How to "update"Create new object (
text
copyWith
)
Modify directly
Thread safetySafe (no race conditions)Risky (shared mutations)
PerformanceBetter (
text
const
caching)
More memory-efficient for frequent changes
PredictabilityHigh (no surprise changes)Low (hard to track mutations)
Flutter widgetsAll widgets are immutableOnly
text
State
object is mutable
State managementBLoC, Riverpod prefer immutable states
text
setState
mutates directly

Quick Reference

dart
// Immutable variables
const name = 'Alice';       // Compile-time constant
final age = 25;             // Runtime constant (set once)

// Immutable class fields
class User {
  final String name;        // Cannot reassign
  const User(this.name);    // const constructor
}

// Mutable variables
var count = 0;              // Can reassign
int total = 100;            // Can reassign

// Mutable class fields
class Cart {
  List<String> items = [];  // Can modify list contents
  int quantity = 0;         // Can reassign
}

Best Practice: Default to immutable. Use

text
final
for all fields,
text
const
constructors for widgets, and
text
copyWith
for updates. Only make something mutable when you have a clear reason (like
text
State
in
text
StatefulWidget
).