skip to content

In Dart, what does a constructor's initializer list do, in what order does construction run with a superclass, and why can't the list use this?

level: middleimportance: should knowfreq 50%

answer

  1. after the colon, before the body
  2. final fields set before the body
  3. super call goes last
  4. this. and super. formals
  5. no this on the right-hand side

basics

~20 s

The initializer list, after the colon, sets fields and runs asserts before any constructor body. Construction runs this class's list, then the superclass constructor, then this class's body. The list cannot use this because the object is not initialised yet.

solid answer

~40 s

An initializer list sits after the constructor's parameter list: `Money(int cents, String code) : cents = cents, currency = code.toUpperCase(), assert(cents >= 0)`. It is where you set `final` and non-nullable fields that need a computed value, run `assert`s, and call a superclass constructor — which must come **last**. Dart runs construction outside-in for initialisation and inside-out for bodies: the subclass's field initializers and initializer list, then the superclass constructor with its own list and body, then the subclass body. The right-hand sides cannot touch `this` — no instance methods, no other fields — because the object is still being initialised; static methods and parameters are fine. `this.x` formals assign a field directly from a parameter, and `super.x` formals (Dart 2.17+) forward a parameter to the superclass constructor.

code

dart · 25 lines
dart
class Base {
  Base() {
    print('Base body');
  }
}

class Child extends Base {
  Child() : tag = _log('Child initializer list'), super() {
    print('Child body');
  }

  final String tag;

  static String _log(String message) {
    print(message);
    return message;
  }
}

void main() {
  Child();
  // Child initializer list
  // Base body
  // Child body
}

go deeper

for a junior

Recall that the initializer list after the colon sets fields and asserts before the body runs, and cannot call instance methods.

for a middle

Explain the full order with a superclass, why the super call is last, and how this. and super. formals reduce boilerplate.

for a senior

Keep constructors free of instance calls during setup, move complex computation to static helpers or factories, and use asserts in the list for invariants.

for a principal

Standardise how value types validate and initialise, so every construction path shares one checked sequence.

## Where fields can be initialised A non-nullable or `final` instance field must have a value before anyone can observe the object. Dart gives you four places to provide it, each earlier than the constructor body: 1. an **initializer at the declaration** — `final List<String> tags = [];` 2. an **initializing formal** — `Money(this.cents, this.currency);` 3. the **initializer list** — `: currency = code.toUpperCase()` 4. a **super formal** — `Price(super.cents, super.currency, this.taxRate);` forwards to the superclass constructor. The constructor **body** runs last, after every field has a value, which is why a `final` field cannot be assigned there. ## The initializer list The list follows a colon and holds comma-separated entries: - **field initializers**: `currency = code.toUpperCase()` - **asserts**: `assert(cents >= 0)`, checked only when assertions are enabled, as in Flutter debug builds - at most one **superclass constructor call**: `super(...)` or `super.named(...)`, which must be the **last** entry (`super_invocation_not_last`) ```dart class Money { Money(int cents, String code) : cents = cents, currency = code.toUpperCase(), assert(cents >= 0), assert(code.length == 3); final int cents; final String currency; } ``` The right-hand side of every entry **cannot access `this`**: no instance methods, no other instance fields, no getters. The same applies to arguments passed to `super(...)`. Static methods, top-level functions and the constructor's parameters are all allowed. That restriction is what lets Dart guarantee the object is never observed half-initialised through its own fields. ## Construction order with a superclass For `new Price(...)` where `Price extends Money`, Dart runs: 1. `Price`'s field initializers, initializing formals and initializer list, in order; 2. the superclass constructor named at the end of the list — or `Money`'s unnamed no-argument constructor implicitly — which runs `Money`'s own initializers and list, then `Money`'s **body**; 3. `Price`'s **body**. So initialisation flows from subclass to superclass, and bodies run from superclass to subclass. One Dart-specific consequence: fields a subclass sets in its initializer list **already hold their values** when the superclass body runs, even if that body calls a method the subclass overrides. ## `this.` and `super.` formals | Parameter form | Effect | Available since | |---|---|---| | `this.cents` | assigns the argument to the field `cents` | long-standing | | `super.cents` | passes the argument to the same-named superclass parameter | Dart 2.17 | | `this._cents` (named) | initialises a private field; callers write `cents:` | Dart 3.12 | Rules worth remembering for super formals: - They work only in **non-redirecting generative** constructors. - A positional `super.x` cannot be combined with **positional arguments** in an explicit `super(...)` call (`positional_super_formal_parameter_with_positional_argument`); named arguments can be split between super formals and the explicit call. - The parameter's type must be a subtype of the superclass parameter's type. ```dart class Price extends Money { Price(super.cents, super.code, this.taxRate) : assert(taxRate >= 0); final double taxRate; } ``` ## When the list is not enough An initializer list is a sequence of expressions — no statements, no loops, no `try`. When computing a field needs real logic, either compute it with a **static helper** called from the list, or use a **factory constructor** that does the work and then calls a private generative constructor with the finished values.

  • In Dart, why may an initializer list call a static method but not an instance method?
    A static method needs no object, so it is safe while the instance is still being initialised. An instance method could read fields that are not yet set, which would break the guarantee that non-nullable and final fields are never observed empty. So the right-hand side of the list, and the arguments to `super(...)`, cannot access `this`.
  • In Dart, when is `: super()` unnecessary in a subclass constructor?
    When the superclass has an unnamed, no-argument generative constructor. Dart then calls it implicitly after the subclass initializer list. You must name a superclass constructor explicitly when the superclass only has named or parameterised generative constructors, or pass the arguments through `super.x` formals.

saying these in an interview costs you the question

  • The superclass constructor body runs before the subclass initializer list
  • An initializer list may call instance methods once the fields above are set
  • A final field can be assigned in the constructor body
  • The super call may appear anywhere in the initializer list
  • super.x parameters can be combined with positional arguments in super(...)