skip to content

In Dart, what must be true of a class for it to have a const constructor, and does calling it always produce a constant?

level: middleimportance: should knowfreq 48%

answer

  1. every field final
  2. no body block
  3. potentially constant initializers
  4. const super constructor
  5. const keyword or constant context

basics

~20 s

A const constructor needs every instance field final, no body, constant-evaluable initializers and a const superclass constructor. It yields a compile-time constant only when invoked with const or in a constant context; otherwise it creates an ordinary new object.

solid answer

~40 s

A `const` constructor lets instances be built at compile time. The class must be deeply suitable for that: every instance field `final` (and none `late`), no constructor body, every initializer and initializer-list expression potentially constant — built only from constants and the constructor's parameters — and any superclass constructor it calls must itself be `const`. Asserts in the initializer list are allowed. Declaring the constructor `const` does not make every call constant: `const Money(0, 'EUR')` or a use inside a constant context creates a constant, while `Money(0, 'EUR')` in ordinary code allocates a normal instance. In Flutter this is why lints ask for `const` at widget call sites — the constructor makes it possible, the call site decides.

code

dart · 12 lines
dart
void main() {
  const a = Money(0, 'EUR');
  final b = Money(0, 'EUR');
  const c = Money(0, 'EUR');

  print(identical(a, c)); // true: both are the same constant
  print(identical(a, b)); // false: b was created at runtime

  final cents = DateTime.now().second;
  final d = Money(cents, 'EUR'); // fine; `const Money(cents, 'EUR')` would not compile
  print(d.cents >= 0); // true
}

go deeper

for a junior

Recall that a const constructor needs final fields and no body, and that const at the call site creates the constant.

for a middle

Explain the full requirement list, potentially constant initializers and const supers, and why Money(0, 'EUR') without const is an ordinary allocation.

for a senior

Decide deliberately whether a public value type is const-capable, knowing that adding const is easy and removing it breaks callers.

for a principal

Weigh const-compatibility of shared types against future flexibility such as caching, lazy fields or runtime validation.

## What a const constructor is for A **constant** in Dart is a value computed at compile time. A class can take part by declaring a **const constructor**, which allows expressions like `const Money(0, 'EUR')` to be evaluated by the compiler rather than at runtime. ```dart class Money { const Money(this.cents, this.currency) : assert(cents >= 0), assert(currency.length == 3); final int cents; final String currency; static const zeroEuro = Money(0, 'EUR'); } ``` ## Requirements the analyzer enforces 1. **All instance fields are `final`.** A single mutable field is `const_constructor_with_non_final_field`. 2. **No `late` fields.** A `late final` field conflicts with a generative const constructor (`late_final_field_with_const_constructor`). 3. **No body.** Even an empty block is `const_constructor_with_body`; a const constructor ends with `;` after its initializer list. 4. **Constant-evaluable initializers.** Field initializers at the declaration must be constants, and initializer-list expressions must be *potentially constant* — built from constants, the constructor's parameters and the operations allowed in constant expressions (`const_constructor_with_field_initialized_by_non_const`). 5. **A const super constructor.** If the class extends another, the superclass constructor it calls must be `const` (`const_constructor_with_non_const_super`); a mixin that adds a field blocks it too. 6. **Redirects stay const.** A const redirecting constructor must target a const constructor. `assert`s are permitted in the initializer list and are checked when the constant is evaluated. ## `const` at the call site decides This is the part candidates miss. The constructor makes constant creation **possible**; the call site **chooses** it: | Call | Result | |---|---| | `const Money(0, 'EUR')` | compile-time constant | | `static const zero = Money(0, 'EUR');` | constant — the declaration is a constant context | | `const [Money(1, 'EUR')]` | constant — inside a constant context `const` is implied | | `Money(0, 'EUR')` in ordinary code | a new, non-constant instance | | `Money(cents, 'EUR')` with a runtime `cents` | non-constant; `const` here would not compile | So `var a = Money(0, 'EUR');` allocates a fresh object every time, exactly like a non-const constructor. Two constant expressions with equal arguments evaluate to the same canonical instance, a rule covered with constant variables. ## Consequences for design - A const constructor is a **public promise**. Removing `const` later breaks every caller that wrote `const Money(...)` — Effective Dart warns about exactly this. - `const` forbids runtime work: no caching maps, no normalisation with instance methods, no I/O. Validation is limited to `assert`s over constant-evaluable expressions. - An abstract class can offer a const constructor that yields a concrete type through a **const redirecting factory**, `const factory Shape.unit() = _UnitSquare;`. ## Why Flutter developers meet this daily Flutter widgets are immutable configuration objects, so most widget classes declare `const` constructors — `const Text('Hi')`, `const SizedBox(height: 8)`. The analyzer's `prefer_const_constructors` lint then asks call sites to add `const` where every argument is constant, and `prefer_const_constructors_in_immutables` asks `@immutable` classes to declare their constructors `const`. Whether that saves rebuild work is a performance topic of its own; the language rule is the one above: the class qualifies, and the call site opts in.

  • In Dart, why is removing const from a published constructor a breaking change?
    Callers may have written `const Money(...)` or used the constructor inside constant contexts such as `static const` fields, default parameter values or `const` lists. Once the constructor is no longer `const`, all of those stop compiling. Effective Dart therefore treats `const` on a public constructor as a commitment to keep the class constant-compatible.
  • In Dart, can a const constructor validate its arguments?
    Only with `assert`s in the initializer list, whose conditions must be potentially constant expressions over the parameters, such as `assert(cents >= 0)`. There is no body, so no statements, loops or throws. For richer validation, keep a const constructor for known-good values and add a factory or static method that validates runtime input.

saying these in an interview costs you the question

  • Declaring a const constructor makes every call produce a constant
  • A const constructor may have an empty body block
  • A class can have a const constructor while one field is mutable
  • const constructors may run arbitrary code as long as it has no side effects
  • A const constructor can call any superclass constructor