skip to content

Variables & Finality

var, final and const declare Dart's variables, with type inference, compile-time constants canonicalised to one instance, and lazy top-level fields. Interviewers probe final versus const.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In Dart, what is the difference between declaring a variable final and declaring it const?

level: juniorimportance: must knowfreq 82%

answer

  1. set once versus known at compile time
  2. const is implicitly final
  3. DateTime.now() fits only one
  4. class level: static const
  5. instance fields: final, never const

basics

~20 s

In Dart, final means a variable can be assigned only once, with a value that may be computed at runtime; const means its value is a compile-time constant, deeply immutable and canonicalized. Every const variable is implicitly final.

solid answer

~40 s

`final` is a single-assignment rule on the variable: `final now = DateTime.now();` is fine because the value is computed when the code runs, but assigning `now` again is a compile error. `const` is stronger: the value must be a **compile-time constant**, such as a literal, another constant or arithmetic on constant numbers, so `const now = DateTime.now();` does not compile. A const variable is implicitly final, and the constant object itself is deeply immutable and canonicalized, meaning equal constants share one instance. In a class, instance fields can be `final` but never `const`; a class-level constant is written `static const`. In a unit-conversion library, `const metersPerMile = 1609.344;` is a natural const, while a converter's `final String locale;` field is final because it differs per instance.

code

dart · 17 lines
dart
const metersPerMile = 1609.344; // compile-time constant
const metersPerFoot = 0.3048;
const feetPerMile = metersPerMile / metersPerFoot; // const arithmetic

class Converter {
  static const metersPerInch = 0.0254; // class-level constant
  final String locale; // per-instance, set once
  Converter(this.locale);
}

void main() {
  final loadedAt = DateTime.now(); // runtime value, set once
  // loadedAt = DateTime.now(); // error: can only be set once
  // const now = DateTime.now(); // error: not a constant expression
  print('$feetPerMile ft per mile, loaded at $loadedAt');
  print(Converter('en').locale);
}

go deeper

for a junior

Recall that final is assign-once at runtime and const is a compile-time constant, and that const implies final.

for a middle

Explain why instance fields cannot be const, what counts as a constant expression, and that final restricts only the variable.

for a senior

Choose const for definitional data and final for computed values in library code, and use lints to keep the choice consistent.

for a principal

Set team conventions on const, final and var so APIs signal intent clearly without slowing reviews over style.

## Two keywords, two different promises Dart has three ways to declare a variable: `var` (or a type) for a variable you may reassign, `final` for one you assign once, and `const` for one whose value is fixed when the program is compiled. `final` and `const` are both used when you never intend to change a variable, and dart.dev notes that **const variables are implicitly final**. The difference is *when* the value is known and *how much* is frozen. | | `final` | `const` | |---|---|---| | Assigned | Exactly once | Exactly once, at the declaration | | Value known | At runtime | At compile time | | Example value | `DateTime.now()`, a parsed config | `1609.344`, `'km'`, `2 * metersPerFoot` | | Object mutability | The referenced object may be mutable | The constant object is deeply immutable | | Instances | A new object each time the code runs | Equal constants share one canonical instance | | Instance field | Allowed | Not allowed; use `static const` | ## final: a single-assignment variable A `final` variable can be set only once. The value can come from anything: a function call, user input, the current time. After that, assigning again is a compile-time error (the analyzer reports `assignment_to_final_local` for a local). A final local does not even need its initializer on the same line, as long as Dart's flow analysis can see it is assigned exactly once before use. Key point: `final` restricts the **variable**, not the object. A `final` list can still have items added; only the variable cannot be pointed at a different list. ## const: a compile-time constant `const` requires the initializer to be a **constant expression**, one the compiler can evaluate: - number, string, boolean and `null` literals; - other `const` variables; - arithmetic and comparisons on constant numbers, like `1.01325 * bar`; - `is` and `as`, collection `if` and spreads inside constant collections; - calls to `const` constructors with constant arguments. A function call such as `DateTime.now()` or `double.parse('3.2')` is not a constant expression, so `const x = DateTime.now();` fails with `const_initialized_with_non_constant_value`. Because the value is computed once by the compiler, the resulting object is **deeply immutable** and **canonicalized**: two occurrences of the same constant value refer to a single instance. ## const in classes 1. An **instance field** cannot be `const`; the analyzer reports `const_instance_field` ("Only static fields can be declared as const"). Each instance may hold a different value, which contradicts a compile-time constant. 2. A constant that belongs to the class is written **`static const`**, for example `static const metersPerInch = 0.0254;`. 3. Instance fields that never change after construction are `final`. ## Choosing between them For a unit-conversion library the rule falls out naturally: - Conversion factors known by definition (`1609.344` meters per mile, `0.3048` meters per foot) are `const`, at top level or as `static const`. - Values computed at runtime, such as the time a table was loaded or a locale passed to a constructor, are `final`. - Loop counters and accumulators that change are `var`. Effective Dart's `prefer_const_declarations` lint nudges `final` declarations that could be `const` towards `const`, and `prefer_final_locals` nudges never-reassigned locals from `var` to `final`. Neither lint is in the default recommended set, but both show the intended direction. ## A checklist for the interview answer 1. Say **when** the value is known: runtime for `final`, compile time for `const`. 2. Say that `const` **implies** `final`, never the reverse. 3. Say **how deep** each goes: `final` freezes the variable, `const` freezes the whole object graph. 4. Say where each is **allowed**: `const` at top level, as a local, or as `static const`; `final` anywhere, including instance fields. 5. Mention **canonicalization** as the bonus: equal constants are one shared object. Covering these five points in two or three sentences is what separates a memorized definition from an understanding of the model. ## Common confusions - "`final` makes the object immutable": no, only the binding. - "`const` and `final` are interchangeable for fields": no, instance fields cannot be `const`. - "A `const` value can be computed at startup": no, it must be computable by the compiler.

  • Why can't a Dart instance field be const?
    A const value is fixed at compile time and shared, while an instance field can hold a different value per object. Dart therefore allows only `static const` fields; the analyzer reports `const_instance_field` otherwise. Per-instance values that never change are declared `final` and set in the constructor.
  • Can a Dart final local be declared without an initializer?
    Yes. `final String unit;` is valid if flow analysis can prove it is assigned exactly once before it is read, for example once in each branch of an if/else. Assigning it twice, or reading it before assignment, is a compile error.

saying these in an interview costs you the question

  • final makes the referenced object immutable, so a final list cannot grow
  • const values can be computed at startup, e.g. from DateTime.now()
  • Instance fields can be declared const if they never change
  • final and const behave identically; const is just a stricter style
  • A const variable can be reassigned if it has no explicit type
open as a page

In Dart, how does var infer a local variable's type, and what happens when a var declaration has no initializer?

level: juniorimportance: should knowfreq 60%

basics

~20 s

In Dart, var takes its static type from the initializer, once: var miles = 3 makes miles an int for good, so assigning 2.5 later is a compile error. A var with no initializer gets dynamic, which switches off type checking.

open as a page

In Dart, what does it mean that const values are canonicalized, and which expressions may appear in a const initializer?

level: middleimportance: should knowfreq 45%

basics

~20 s

Dart evaluates const expressions at compile time and keeps one instance per distinct constant value, so equal constants are identical(). A const initializer may use literals, other constants, arithmetic on constant numbers, is/as, collection if, spreads and const constructor calls, not arbitrary function calls.

open as a page

In Dart, why can you add items to a list held in a final variable, and what changes if the list is const?

level: middleimportance: should knowfreq 58%

basics

~20 s

final freezes only the variable binding, so the list it references stays mutable and add() works. A const list is a deeply immutable constant: add() still compiles but throws UnsupportedError at runtime, and everything inside a constant is constant too.

open as a page

A Dart service's first unit conversion after startup is slow and sometimes throws from a top-level final lookup table. Why, and how would you fix it?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Dart initializes top-level and static variables lazily, the first time they are read, so the table's costly or failing initializer runs inside the first request, not at startup. Make the table const if it is fixed data, or build it explicitly during startup.

open as a page

In Dart 3.7 and later, what does naming a local variable or parameter _ do, and what convention did it replace?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Since Dart language version 3.7, a local variable or parameter named _ is a non-binding wildcard: any initializer still runs, but the value cannot be read, and several _ may share one scope. It replaces the old _, __, ___ naming convention.

open as a page