Introduction
In Dart programming, understanding how to handle immutable values is crucial for writing clean, efficient, and error-free code. Dart provides two primary keywords for immutability: const and final. While both are used to prevent changes to a variable once it has been assigned a value, they have subtle but important differences.
Choosing between const and final is not just a matter of preference—it affects performance, runtime behavior, memory usage, and even how your Flutter app renders widgets. Misusing them can lead to bugs or inefficient code, especially in large-scale applications.
This comprehensive guide will cover everything you need to know about const and final in Dart, including definitions, usage examples, best practices, performance considerations, common mistakes, and real-world Flutter examples. By the end, you will confidently understand how and when to use each.
What is Immutability?
Immutability refers to values that cannot be changed once they are set. In programming, using immutable variables has several advantages:
- Predictable code behavior → You know the value will not change unexpectedly.
- Safer for concurrent programming → No risk of multiple threads modifying the same value.
- Better performance → Allows compiler optimizations and memory sharing.
In Dart, const and final are tools to enforce immutability at compile-time or runtime.
The final Keyword
Definition
A final variable in Dart can only be set once. After it has been assigned, its value cannot be changed. Unlike const, final variables can be initialized at runtime, meaning the value does not have to be known during compilation.
Example of final
void main() {
final currentTime = DateTime.now();
print('Current time: $currentTime');
// Uncommenting below line will cause error
// currentTime = DateTime(2025, 1, 1);
}
Explanation:
currentTimeis assigned once at runtime.- Any attempt to reassign it will result in a compile-time error.
Key Points about final
- Initialized once and cannot be reassigned.
- Can be determined at runtime.
- Ideal for values that do not change after being set, like user data fetched from an API.
- Can be used with objects, but note that the object’s internal properties can still change unless the object itself is immutable.
The const Keyword
Definition
A const variable is a compile-time constant. Its value must be known at compile time and cannot change during program execution.
Example of const
void main() {
const pi = 3.14159;
print('Pi: $pi');
// Uncommenting below line will cause error
// pi = 3.14;
}
Explanation:
piis known at compile time.- Any attempt to change it results in an error.
Key Points about const
- Must be initialized with a compile-time constant.
- Immutable throughout the program.
- Can be used to define constant objects and collections in Flutter, such as widget trees.
- Memory-efficient because
constobjects are canonicalized (shared in memory).
Major Differences Between const and final
| Feature | final | const |
|---|---|---|
| Initialization | Can be initialized at runtime | Must be initialized at compile-time |
| Reassignment | Cannot be reassigned | Cannot be reassigned |
| Immutability | Variable cannot change, but object properties can change | Fully immutable; object and properties cannot change |
| Memory Efficiency | Not necessarily shared | Canonicalized; shared in memory |
| Use Cases | Runtime constants (like user input, API results) | Compile-time constants (like Pi, widget constants) |
| Keyword Example | final name = getUserName(); | const pi = 3.14159; |
Practical Examples
1. Using final with Runtime Values
void main() {
final DateTime now = DateTime.now();
final int randomNumber = DateTime.now().millisecondsSinceEpoch % 100;
print('Time: $now');
print('Random: $randomNumber');
}
Explanation:
- Both
nowandrandomNumberare determined at runtime. - Once assigned, their values cannot change.
2. Using const with Compile-Time Values
void main() {
const double gravity = 9.8;
const String appName = 'MyFlutterApp';
print('Gravity: $gravity');
print('App: $appName');
}
Explanation:
gravityandappNameare compile-time constants.- Perfect for values that will never change, like physics constants or app titles.
3. Using const with Collections
void main() {
const List<String> colors = ['Red', 'Green', 'Blue'];
const Map<String, int> ageMap = {'Ali': 25, 'Sara': 22};
print(colors);
print(ageMap);
}
Explanation:
constensures that the list and map are immutable.- Attempting to add or remove elements will result in an error.
4. Using final with Objects
class User {
String name;
User(this.name);
}
void main() {
final user = User('Ali');
user.name = 'Ahmed'; // ✅ Allowed
// user = User('Sara'); // ❌ Not allowed
}
Explanation:
final usermeans the reference cannot change.- But the object itself is mutable, so properties can be updated.
5. Using const with Objects
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
const point = Point(10, 20);
// point.x = 15; // ❌ Not allowed
}
Explanation:
- Both the reference and the object are immutable.
- Ideal for fixed points, colors, or constants used across the app.
const in Flutter Widgets
In Flutter, const is heavily used to improve performance by avoiding unnecessary rebuilds of widgets.
Example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello, Flutter!'),
),
),
);
}
}
Explanation:
- Using
constforMaterialApp,Scaffold,Center, andTextensures that these widgets are reused in memory. - Reduces rebuild cost and improves app performance.
When to Use final vs const
Use final When:
- The value is known only at runtime.
- The object may contain mutable properties.
- You need a variable to be immutable but runtime-dependent.
Example:
final DateTime now = DateTime.now();
Use const When:
- The value is known at compile time.
- The variable or object will never change.
- You want memory optimization in Flutter widgets or shared constants.
Example:
const double pi = 3.14159;
const List<String> colors = ['Red', 'Green', 'Blue'];
Common Mistakes Beginners Make
- Confusing
finalandconst→ Usingconstfor runtime values. - Attempting to mutate a
constobject → Results in compile-time error. - Using
finalunnecessarily whenconstcould improve performance. - Not using
constin Flutter widgets → Leads to inefficient rebuilds. - Assigning
finalwithout initialization → Must assign before usage.
Best Practices
- Prefer
constoverfinalwhen possible → Especially in Flutter widget trees. - Use
finalfor runtime-dependent values → Like API responses or dynamic calculations. - Declare top-level constants with
const→ Makes them globally immutable. - Use
finalwith object references → Prevent reference change while allowing internal updates. - Combine
constwithfinalin classes wisely → For immutable class instances.
Performance Considerations
constobjects are canonicalized → Only one copy exists in memory.finalobjects are runtime constants → Each instance may consume extra memory.- Using
constin Flutter reduces unnecessary widget rebuilds → Improves app performance.
Real-World Flutter Example
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
const appTitle = 'Constants in Flutter';
final DateTime now = DateTime.now();
return Scaffold(
appBar: AppBar(title: Text(appTitle)),
body: Center(
child: Text('Current Time: $now'),
),
);
}
}
Explanation:
appTitleis aconst→ Compile-time constant for the app bar.nowis afinal→ Runtime constant showing the current time.- Efficient use of memory and proper distinction between
constandfinal.
Leave a Reply