Question #271EasyDart BasicsImportant

What is difference between using final vs get inside the class where to use which ?

Answer

Overview

Both

text
final
and a
text
get
(getter) inside a class expose a value, but they serve different purposes.
text
final
is a stored value;
text
get
is a computed property.


final -- Stored, Immutable Value

dart
class Circle {
  final double radius; // Stored once, never changes
  final String id;     // Stored once

  Circle({required this.radius}) : id = 'circle_${DateTime.now().millisecondsSinceEpoch}';
}

final c = Circle(radius: 5.0);
print(c.radius); // 5.0 -- stored field
// c.radius = 10; // Error -- final cannot be reassigned

get -- Computed Property

dart
class Circle {
  final double radius;
  Circle(this.radius);

  // Computed on every access -- not stored
  double get area => 3.14159 * radius * radius;
  double get circumference => 2 * 3.14159 * radius;
  bool get isLarge => radius > 50;
  String get description => 'Circle with radius $radius';
}

final c = Circle(radius: 5.0);
print(c.area);   // 78.54 -- computed each time
print(c.isLarge); // false

Key Differences

Feature
text
final
text
get
StorageStored in memoryComputed each access
PerformanceRead once -- fastComputed every call
Can changeNoYes (if depends on mutable state)
Use caseFixed values (id, name)Derived values (area, label, status)
AssignmentSet in constructorNever set -- computed
MemoryAllocatedNone (no extra storage)

Getters with Mutable State

dart
class ShoppingCart {
  final List<CartItem> items = [];

  // Getters -- recomputed when items changes
  double get total => items.fold(0, (sum, item) => sum + item.price);
  int get count => items.length;
  bool get isEmpty => items.isEmpty;
  bool get hasDiscount => total > 1000;

  void add(CartItem item) => items.add(item);
}

final cart = ShoppingCart();
cart.add(CartItem(price: 500));
print(cart.total);  // 500.0 -- computed from mutable list
cart.add(CartItem(price: 600));
print(cart.total);  // 1100.0 -- recomputed

When to Use Which

Use
text
final
Use
text
get
Value set once (id, name, config)Derived/computed value
Value from constructor argumentDependent on other fields
Performance-critical (no recomputation)Changes when dependencies change
API response fieldFormatted text, bool checks
dart
class User {
  final int id;              // final -- stored, never changes
  final String firstName;    // final -- stored
  final String lastName;     // final -- stored

  String get fullName => '$firstName $lastName'; // get -- computed
  String get initials => '${firstName[0]}${lastName[0]}'; // get -- computed
  bool get isAdmin => role == 'admin'; // get -- depends on mutable role

  String role; // mutable
  User({required this.id, required this.firstName, required this.lastName, this.role = 'user'});
}

Rule: Use

text
final
for identity/configuration values set at construction. Use
text
get
(getter) for values derived from other fields -- they stay in sync automatically.


Initialization Timing: When Is the Value Called?

This is a critical difference often overlooked —

text
final
and
text
get
behave very differently in when their values are evaluated.


final — Evaluated Immediately at Object Creation

A

text
final
field is initialized at the moment the object is created — not when you first read it. The value is computed once during construction and stored in memory.

dart
class UserProfile {
  final String id;
  final DateTime createdAt;

  UserProfile(String name)
      : id = 'user_${DateTime.now().millisecondsSinceEpoch}', // runs NOW at construction
        createdAt = DateTime.now() {                           // runs NOW at construction
    print('Constructor body — object is being created');
  }
}

void main() {
  print('Before creating object');
  final user = UserProfile('Alice'); // ← final fields evaluated HERE
  print('After creating object');
  print(user.id);        // just reads stored value — no computation
  print(user.createdAt); // just reads stored value — no computation
}

// Output:
// Before creating object
// Constructor body — object is being created
// After creating object
// user_1234567890  (already stored)
// 2026-03-18 ...   (already stored)

Key Point:

text
final
fields are computed once during construction and stored. Every subsequent read just retrieves the stored value — no re-computation.


get — Evaluated Only When Accessed (On-Demand)

A getter is not called at object creation. It runs only when you explicitly access the property — and it re-runs every time you access it.

dart
class Circle {
  final double radius;

  Circle(this.radius) {
    print('Circle created — getter NOT called yet');
  }

  double get area {
    print('area getter is running now');
    return 3.14159 * radius * radius;
  }
}

void main() {
  print('Before creating Circle');
  final c = Circle(5.0); // getter does NOT run here
  print('After creating Circle');

  print('Accessing area...');
  print(c.area); // getter runs HERE
  print('Accessing area again...');
  print(c.area); // getter runs AGAIN — recomputed
}

// Output:
// Before creating Circle
// Circle created — getter NOT called yet
// After creating Circle
// Accessing area...
// area getter is running now
// 78.53975
// Accessing area again...
// area getter is running now   ← runs every time
// 78.53975

Key Point: A getter runs only when accessed, and re-runs on every access. There is no caching — if you access it 10 times, the code runs 10 times.


Comparison: final vs get — Timing Summary

Scenario
text
final
text
get
When evaluatedAt object creationOnly when accessed
Called at instantiation?✅ Yes — immediately❌ No — deferred
Runs multiple times?❌ No — once only✅ Yes — every access
Stored in memory?✅ Yes❌ No
Re-computed on access?❌ No✅ Yes

late final — Best of Both: Lazy + Cached

If you want deferred initialization (like a getter) but want the value cached after first use (like

text
final
), use
text
late final
.

dart
class HeavyReport {
  final List<int> rawData;

  HeavyReport(this.rawData) {
    print('HeavyReport created — late final NOT evaluated yet');
  }

  // Evaluated only on first access, then cached — never re-runs
  late final int total = _computeTotal();

  int _computeTotal() {
    print('Computing total — this runs only once');
    return rawData.fold(0, (sum, n) => sum + n);
  }
}

void main() {
  final report = HeavyReport([10, 20, 30]); // late final NOT evaluated
  print('Accessing total...');
  print(report.total); // computed HERE for the first time
  print('Accessing total again...');
  print(report.total); // returns cached result — does NOT recompute
}

// Output:
// HeavyReport created — late final NOT evaluated yet
// Accessing total...
// Computing total — this runs only once
// 60
// Accessing total again...
// 60   ← no recomputation

When to Use Which — Timing Perspective

Use
text
final
when...
Use
text
get
when...
Use
text
late final
when...
Value is ready at constructionValue depends on state that changesValue is expensive to compute
Value is cheap to computeValue must always reflect latest stateValue only needed sometimes
Always needed immediatelyRarely or conditionally accessedShould be computed once and cached
dart
class Order {
  final String orderId;          // ✅ final — set once at creation
  final DateTime placedAt;       // ✅ final — set once at creation

  String status;                 // mutable

  // ✅ get — must reflect current status (changes over time)
  bool get isDelivered => status == 'delivered';
  String get statusLabel => 'Order $orderId: $status';

  // ✅ late final — expensive, only needed on demand, cached after first call
  late final String receiptHtml = _generateReceipt();

  String _generateReceipt() {
    // Imagine this is a slow HTML generation
    return '<html>Receipt for $orderId placed at $placedAt</html>';
  }

  Order({required this.orderId, this.status = 'pending'})
      : placedAt = DateTime.now();
}