skip to content

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