Question #197EasyAdvanced Concepts

What is immutable and mutable ? Difference between them ?

#immutable#mutable#final#const#state-management#dart#best-practices

Answer

Definitions

Immutable

An immutable object is one whose state cannot be changed after it has been created. Once initialized with a value, it cannot be modified. Any operation that appears to modify an immutable object actually creates a new object.

Mutable

A mutable object is one whose internal state can be changed after creation. You can modify its properties, add/remove elements, or update values without creating a new object.

Comparison Table

AspectImmutableMutable
ModificationCannot be changed after creationCan be modified after creation
MemoryCreates new object on changeModifies existing object
Thread SafetyInherently thread-safeRequires synchronization
PerformanceMemory overhead for new objectsBetter for frequent updates
PredictabilityEasier to reason aboutCan have unexpected side effects
State ManagementPreferred in Flutter/BlocRequires careful handling
Examples (Dart)String, int, double, boolList, Map, Set

Immutable Types in Dart

Built-in Immutable Types

dart
// String - Immutable
String name = 'John';
name = name.toUpperCase(); // Creates NEW string, doesn't modify original
print(name); // Output: JOHN

// Numbers - Immutable
int age = 25;
age = age + 1; // Creates new int value
print(age); // Output: 26

// Booleans - Immutable
bool isActive = true;
isActive = !isActive; // Creates new boolean value
print(isActive); // Output: false

Creating Immutable Classes

dart
class ImmutableUser {
  final String name;
  final int age;
  final String email;

  // All fields are final - cannot be changed after construction
  const ImmutableUser({
    required this.name,
    required this.age,
    required this.email,
  });

  // To "modify", create a new instance with updated values
  ImmutableUser copyWith({
    String? name,
    int? age,
    String? email,
  }) {
    return ImmutableUser(
      name: name ?? this.name,
      age: age ?? this.age,
      email: email ?? this.email,
    );
  }
}

// Usage
void main() {
  final user1 = ImmutableUser(
    name: 'Alice',
    age: 30,
    email: 'alice@example.com',
  );

  // Cannot do: user1.name = 'Bob'; // Error: final field

  // Instead, create new instance
  final user2 = user1.copyWith(name: 'Bob');
  
  print(user1.name); // Output: Alice (unchanged)
  print(user2.name); // Output: Bob (new object)
}

Using @immutable Annotation

dart
import 'package:meta/meta.dart';


class Product {
  final String id;
  final String name;
  final double price;

  const Product({
    required this.id,
    required this.name,
    required this.price,
  });
}

// Compiler warning if you try to add mutable fields:

class InvalidProduct {
  String name; // Warning: This class is marked immutable but has mutable fields
  
  InvalidProduct(this.name);
}

Mutable Types in Dart

Built-in Mutable Types

dart
// List - Mutable
List<String> fruits = ['apple', 'banana'];
fruits.add('orange');        // Modifies the same list
fruits[0] = 'mango';         // Modifies existing element
fruits.remove('banana');     // Removes element
print(fruits); // Output: [mango, orange]

// Map - Mutable
Map<String, int> scores = {'Alice': 90, 'Bob': 85};
scores['Alice'] = 95;        // Modifies existing value
scores['Charlie'] = 88;      // Adds new entry
scores.remove('Bob');        // Removes entry
print(scores); // Output: {Alice: 95, Charlie: 88}

// Set - Mutable
Set<int> numbers = {1, 2, 3};
numbers.add(4);              // Modifies the set
numbers.remove(1);           // Removes element
print(numbers); // Output: {2, 3, 4}

Creating Mutable Classes

dart
class MutableCounter {
  int count = 0;              // Not final - can be changed
  
  void increment() {
    count++;                  // Modifies the same object
  }
  
  void decrement() {
    count--;                  // Modifies the same object
  }
  
  void reset() {
    count = 0;                // Modifies the same object
  }
}

// Usage
void main() {
  final counter = MutableCounter();
  print(counter.count);       // Output: 0
  
  counter.increment();
  print(counter.count);       // Output: 1
  
  counter.increment();
  print(counter.count);       // Output: 2
  
  // Same object, different state
}

final vs const Keywords

final - Single Assignment

dart
// final: Value set at runtime, cannot be reassigned
final currentTime = DateTime.now(); // Evaluated at runtime
// currentTime = DateTime.now(); // Error: cannot reassign

final List<int> numbers = [1, 2, 3];
numbers.add(4);           // ✅ OK: List itself is mutable
// numbers = [5, 6];      // ❌ Error: Cannot reassign the variable

const - Compile-Time Constant

dart
// const: Value must be known at compile time, deeply immutable
const pi = 3.14159;         // Compile-time constant
// const now = DateTime.now(); // Error: Not a compile-time constant

const List<int> primes = [2, 3, 5, 7];
// primes.add(11);          // Error: Cannot modify const list
// primes = [2, 3, 5];      // Error: Cannot reassign

Comparison

dart
// final: Runtime constant, shallow immutability
final list1 = [1, 2, 3];
list1.add(4);               // ✅ Allowed

// const: Compile-time constant, deep immutability
const list2 = [1, 2, 3];
// list2.add(4);            // ❌ Error: Cannot modify

// Mixed usage
final list3 = const [1, 2, 3]; // final reference to const list
// list3.add(4);            // ❌ Error: List is const
list3 = [4, 5, 6];          // ✅ Can reassign the variable... wait, no!
// Actually, this is an error because list3 is final

Immutability in Flutter State Management

Why Immutability Matters in Flutter

Flutter rebuilds widgets when object references change, not when object contents change. Immutable state makes this detection trivial.

BLoC Pattern with Immutable State

dart
import 'package:flutter_bloc/flutter_bloc.dart';

// Immutable state class
class CounterState {
  final int count;
  
  const CounterState(this.count);
  
  // Create new state with updated value
  CounterState copyWith({int? count}) {
    return CounterState(count ?? this.count);
  }
}

// Events
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}

// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) {
      // Emit NEW state, don't modify existing
      emit(CounterState(state.count + 1));
    });
    
    on<DecrementEvent>((event, emit) {
      emit(CounterState(state.count - 1));
    });
  }
}

Provider with Immutable Data

dart
import 'package:flutter/foundation.dart';

class TodoState {
  final List<String> todos;
  
  const TodoState(this.todos);
  
  // Return new state with updated list
  TodoState addTodo(String todo) {
    return TodoState([...todos, todo]); // New list
  }
  
  TodoState removeTodo(int index) {
    final newTodos = List<String>.from(todos);
    newTodos.removeAt(index);
    return TodoState(newTodos); // New state
  }
}

class TodoProvider extends ChangeNotifier {
  TodoState _state = const TodoState([]);
  
  TodoState get state => _state;
  
  void addTodo(String todo) {
    _state = _state.addTodo(todo); // Replace with new state
    notifyListeners();
  }
  
  void removeTodo(int index) {
    _state = _state.removeTodo(index); // Replace with new state
    notifyListeners();
  }
}

Creating Immutable Collections

Unmodifiable List

dart
import 'dart:collection';

// Create unmodifiable list from mutable list
final mutableList = [1, 2, 3];
final immutableList = UnmodifiableListView(mutableList);

// immutableList.add(4); // Error: Unsupported operation
mutableList.add(4);      // ✅ OK: Original list is still mutable
print(immutableList);    // Output: [1, 2, 3, 4] - reflects original

Unmodifiable Map

dart
final mutableMap = {'a': 1, 'b': 2};
final immutableMap = UnmodifiableMapView(mutableMap);

// immutableMap['c'] = 3; // Error: Unsupported operation

Using Freezed for Immutability

dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';


class User with _$User {
  const factory User({
    required String name,
    required int age,
    required String email,
  }) = _User;
}

// Usage - completely immutable with copyWith
void main() {
  final user1 = User(name: 'Alice', age: 30, email: 'alice@example.com');
  final user2 = user1.copyWith(age: 31); // New instance
  
  print(user1.age); // 30
  print(user2.age); // 31
  print(user1 == user2); // false (different instances)
}

Performance Considerations

When to Use Immutable

dart
// ✅ Good: Infrequent updates, state management
class AppConfig {
  final String apiUrl;
  final int timeout;
  final bool debugMode;
  
  const AppConfig({
    required this.apiUrl,
    required this.timeout,
    required this.debugMode,
  });
}

When to Use Mutable

dart
// ✅ Good: Frequent updates, large collections
class ChatMessageBuffer {
  final List<Message> messages = [];
  
  void addMessage(Message message) {
    messages.add(message); // Efficient for frequent additions
  }
  
  void clearOldMessages() {
    messages.removeWhere((m) => m.timestamp.isBefore(cutoffTime));
  }
}

Best Practices

Prefer Immutability:

  • Use
    text
    final
    for all class fields by default
  • Use
    text
    const
    constructors when possible
  • Implement
    text
    copyWith()
    for state updates
  • Use immutable state in BLoC/Provider/Riverpod

Use Mutability When:

  • Performance is critical (large, frequently-updated collections)
  • Working with builders or temporary data structures
  • Interfacing with APIs that require mutable objects

Avoid Common Pitfalls:

  • Don't expose mutable collections from immutable classes
  • Don't confuse
    text
    final
    (reference immutability) with deep immutability
  • Don't create unnecessary copies of large objects

Learning Resources

Key Takeaway: In Flutter, prefer immutability for state management. It makes apps more predictable, easier to debug, and naturally supports Flutter's reactive rebuild mechanism. Use

text
final
fields,
text
const
constructors, and code generation tools like Freezed to enforce immutability without boilerplate.