Using Dart Const Constructors for Immutable Compile‑Time Data Classes
Create immutable data classes with Dart’s const constructors, enforce compile‑time safety, and benchmark memory savings. Follow this step‑by‑step guide to canonicalize objects, avoid side‑effects, and recover from constraint violations.
03 Oct 2026, 10:48 UTC

Desired Outcome
By the end of this guide you will have a Dart data class that:
- Is fully immutable and thread‑safe.
- Can be instantiated with
constfor compile‑time constant propagation. - Is canonicalized—identical const instances share the same memory reference.
- Shows measurable memory and allocation savings compared to a mutable counterpart.
Prerequisites
- Flutter SDK 3.0+ or Dart SDK 3.0+ installed.
- Basic understanding of Dart classes, final fields, and the
constkeyword. - Access to a terminal with
dartcommand available.
Procedure
- Define a const‑friendly class
All fields must be
finalorconst. Nolateor mutable types are allowed.class Point { final double x; final double y; const Point(this.x, this.y); } - Instantiate with
constUse the
constkeyword at the call site to create a compile‑time constant.const origin = Point(0.0, 0.0); const pointA = Point(1.0, 2.0); - Canonicalization test
Declare the same values twice and verify that they reference the same object.
const pointB = Point(1.0, 2.0); print(identical(pointA, pointB)); // true - Enforce compile‑time safety
Attempt to add a non‑final field; the analyzer will reject the code.
// ❌ Error: Field 'z' must be final or const. class BadPoint { int z; // non‑final const BadPoint(this.z); } - Benchmark allocation cost
Write a simple loop that creates many instances of the const class and a mutable version. Measure allocation time with
dart run --enable-assertsanddart run --releaseflags.void main() { const point = Point(1.0, 2.0); for (var i = 0; i < 1_000_000; i++) { // const instances are reused; no allocation happens. final _ = point; } } - Compile‑time constant usage
Use a const instance as a static final field or as a default parameter.
class Circle { final Point center; final double radius; const Circle(this.center, this.radius); } class ShapeDefaults { static const Circle unitCircle = Circle(Point(0.0, 0.0), 1.0); }
Expected Checks
- Analyzer: Run
dart analyzein the project root. No errors should appear for the const class. - Canonicalization:
identical(a, b)returnstruefor two separately declared const instances with identical values. - Binary size: Compile with
dart compile exe bin/main.dartand comparemain.exesize to a non‑const version. Expect a measurable reduction. - Runtime safety: Attempting to modify a const field (e.g.,
point.x = 5;) results in a runtime error.
Recovery Options
- If the analyzer reports a non‑final field, convert it to
finalor remove it from the const constructor. - For fields that depend on runtime data, replace the const constructor with a regular one and provide a
factorythat returns a const instance when possible. - When canonicalization is not desired (e.g., you need distinct objects), omit the
constkeyword at instantiation time.
Performance Comparison
Below is a simplified table showing the expected differences between const and mutable versions when creating 1,000,000 instances:
| Version | Allocation Time | Binary Size |
|---|---|---|
| Mutable Point | ~150 ms | ~1.2 MB |
| Const Point | ~5 ms (no allocations) | ~1.0 MB |
These numbers are illustrative; actual results depend on the environment and compiler optimizations.
Common Pitfalls
- Using
lateordynamicfields in a const class – compile‑time error. - Calling a non‑const method from within a const constructor – compile‑time error.
- Referencing
thisbefore all fields are initialized in the initializer list – compile‑time error. - Mutating a const instance at runtime – runtime exception.
Always run dart analyze after modifications to catch these issues early.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.