What is difference between using final vs get inside the class where to use which ?
Answer
Overview
Both
text
finaltext
gettext
finaltext
getfinal -- Stored, Immutable Value
dartclass 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
dartclass 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 | text |
|---|---|---|
| Storage | Stored in memory | Computed each access |
| Performance | Read once -- fast | Computed every call |
| Can change | No | Yes (if depends on mutable state) |
| Use case | Fixed values (id, name) | Derived values (area, label, status) |
| Assignment | Set in constructor | Never set -- computed |
| Memory | Allocated | None (no extra storage) |
Getters with Mutable State
dartclass 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 | Use text |
|---|---|
| Value set once (id, name, config) | Derived/computed value |
| Value from constructor argument | Dependent on other fields |
| Performance-critical (no recomputation) | Changes when dependencies change |
| API response field | Formatted text, bool checks |
dartclass 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
for identity/configuration values set at construction. Usetextfinal(getter) for values derived from other fields -- they stay in sync automatically.textget
Initialization Timing: When Is the Value Called?
This is a critical difference often overlooked —
text
finaltext
getfinal — Evaluated Immediately at Object Creation
A
text
finaldartclass 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:
fields are computed once during construction and stored. Every subsequent read just retrieves the stored value — no re-computation.textfinal
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.
dartclass 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 | text |
|---|---|---|
| When evaluated | At object creation | Only 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
finaltext
late finaldartclass 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 | Use text | Use text |
|---|---|---|
| Value is ready at construction | Value depends on state that changes | Value is expensive to compute |
| Value is cheap to compute | Value must always reflect latest state | Value only needed sometimes |
| Always needed immediately | Rarely or conditionally accessed | Should be computed once and cached |
dartclass 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(); }