skip to content

Constructors & Initializers

Dart constructors come generative, named, redirecting, factory and const, with initializer lists, super parameters and, since 3.13, primary constructors. Interviewers ask what a factory can do.

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

explore

questions

5

In Dart, what can a factory constructor do that a generative constructor cannot, and what are its limits?

level: juniorimportance: must knowfreq 68%

answer

  1. does not have to allocate
  2. cache or subtype
  3. logic before any instance exists
  4. no this, no initializer list
  5. cannot be a super call target

basics

~20 s

A factory constructor runs arbitrary code and then returns an instance it chooses: a cached object, a subtype, or one built via another constructor. It cannot use this or an initializer list, cannot return null, and cannot be called with super.

solid answer

~40 s

A generative constructor always allocates a fresh instance of exactly its class and initialises it. A `factory` constructor is closer to a static method that callers invoke with constructor syntax: it can run validation or parsing first, return an existing instance from a cache, or return an instance of a subtype, as long as the result is an instance of the class type. The limits follow from that: it has no `this` because no object is being built, no initializer list and no initializing formals, and under null safety it cannot return `null` — invalid input means throwing, or offering a static `tryParse` that returns `Money?`. A subclass cannot call it with `super`, because only generative constructors can be invoked from an initializer list.

code

dart · 14 lines
dart
void main() {
  final a = Money.zero('EUR');
  final b = Money.zero('EUR');
  print(identical(a, b)); // true: the factory returned the cached instance

  final price = Money.parse('4.20 EUR');
  print(price.cents); // 420

  try {
    Money.parse('oops');
  } on FormatException catch (e) {
    print(e.message); // Expected "<amount> <CUR>"
  }
}

go deeper

for a junior

Recall that factory constructors may return cached instances or subtypes and run logic first, but cannot use this or return null.

for a middle

Explain why factories lack initializer lists and this, why they cannot be super targets, and when a static tryParse is the better API.

for a senior

Use private generative constructors plus factories to control how instances are created, and note how that choice affects subclassing and const.

for a principal

Decide which creation APIs a shared value type exposes, weighing caching, subtype freedom and extensibility against the constraints factories impose.

## Two kinds of constructor Dart splits constructors into two families: - A **generative constructor** creates a new instance of its class, runs field initializers, the initializer list, the superclass constructor and finally its body. It always yields a **fresh object of exactly that class**. - A **factory constructor**, marked with `factory`, does not create anything by itself. It is a function body that must **return** an instance of the class type. Callers cannot tell the difference: `Money.parse('4.20 EUR')` looks the same either way. ## What a factory can do The Dart documentation lists the cases for `factory`: 1. **Return an existing instance** — for example, a cached object per key. 2. **Return a subtype** — an abstract class can expose `factory Shape.circle(...)` that returns a private implementation. 3. **Do non-trivial work first** — validate or parse arguments with statements and local variables, which an initializer list cannot express. A money value class shows all three in miniature: ```dart class Money { Money._(this.cents, this.currency); final int cents; final String currency; static final Map<String, Money> _zeros = {}; factory Money.zero(String currency) => _zeros.putIfAbsent(currency, () => Money._(0, currency)); factory Money.parse(String text) { final parts = text.trim().split(' '); if (parts.length != 2) { throw FormatException('Expected "<amount> <CUR>"', text); } final cents = (double.parse(parts[0]) * 100).round(); return Money._(cents, parts[1]); } } ``` `Money.zero` hands back the same object for each currency; `Money.parse` validates input and delegates the real construction to the private generative constructor `Money._`. ## What a factory cannot do | Capability | Generative | Factory | |---|---|---| | Use `this` | in the body | never | | Initializer list, `this.x` formals | yes | no | | Return an existing object | no | yes | | Return a subtype | no | yes | | Return `null` | no | no | | Be called by a subclass via `super` | yes | no | | Be `const` | yes | only as a redirecting factory | The rows that trip candidates up: - **No `null`.** Under sound null safety the factory's result type is the non-nullable class type. For "parse or give up" semantics, add a **static method** such as `static Money? tryParse(String text)` instead. - **No `super` target.** A subclass initializer list may only invoke a *generative* superclass constructor; `super.parse(...)` against a factory is the `non_generative_constructor` error. A class whose only generative constructor is private (`Money._`) therefore cannot be extended from another library. - **No `this`.** Nothing has been allocated when the factory body runs. ## Redirecting factories A factory can also be written as a pure redirect: `factory Shape.circle(double r) = _Circle;`. It forwards the call to another class's constructor without repeating parameters or defaults, and it is the one way to give an abstract class a `const` constructor that produces a concrete implementation. ## Factory or static method? Both run code before an instance exists. Prefer a **factory** when callers should see ordinary construction syntax and always get an instance; prefer a **static method** when the operation can fail softly (`tryParse` returning `Money?`), is asynchronous (a constructor cannot be `async`), or its name reads better as a verb. ## What interviewers listen for - That a factory is **not required to allocate**, and that callers cannot tell a factory from a generative constructor at the call site. - That the missing `this`, initializer list and `super` support all follow from one fact: a factory initialises nothing. - That **null is never a valid result**, so soft failure belongs in a static method. - That the idiom of a private generative constructor behind public factories also controls who may subclass the type.

  • In Dart, how do you offer a constructor-like API that returns null for invalid input?
    Not with a factory: its result type is the non-nullable class type, so it must return an instance or throw. Add a static method such as `static Money? tryParse(String text)` that returns `null` on bad input, and keep `factory Money.parse` for callers who want an exception.
  • In Dart, why can't a subclass of Money call `super.zero('EUR')` in its initializer list?
    A subclass must initialise the superclass part of a new object it is allocating, which only a generative constructor can do. `Money.zero` is a factory that returns some existing or new object rather than initialising the one under construction, so the analyzer reports `non_generative_constructor`. The subclass must call a generative constructor, which it can reach only if one is accessible from its library.

saying these in an interview costs you the question

  • A factory constructor can return null when the input is invalid
  • A factory constructor can use this to set fields
  • A factory constructor always creates a new instance, just with extra logic
  • A subclass can call a superclass factory constructor with super
  • Factory constructors must be static methods named create
open as a page

In Dart, why does a class use named constructors instead of overloaded ones, and what does a redirecting constructor add?

level: juniorimportance: should knowfreq 58%

basics

~20 s

Dart has no overloading, so a second constructor needs its own name, such as Money.euros. A redirecting constructor forwards to another constructor of the same class with : this(...), so all initialisation lives in one place.

open as a page

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%

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.

open as a page

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%

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.

open as a page

In Dart 3.13, what do primary constructors and the concise new and factory constructor forms change, and what breaks when a package adopts language version 3.13?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

Dart 3.13 lets a class header declare fields and the main constructor, as in class Money(final int cents, final String currency);, and lets body constructors use new or factory without the class name. Adopting it makes final on ordinary parameters an error.

open as a page