Constructors
In Dart a constructor is not just "a function that makes an object" — it is one of the richest parts of the language, and it comes up in almost every interview.
The main forms:
- Default (generative) constructor —
Point(this.x, this.y);. Thethis.xshorthand assigns the parameter straight into the field, with no extra line in the body. - Named constructor —
Point.origin() : x = 0, y = 0;. Dart has no constructor overloading (you cannot declare two constructors with the same name), so every alternative way of building the object gets a name. - Initializer list — the part after
:that runs before the body. - Redirecting constructor —
Point.zero() : this(0, 0);— forwards the work to another constructor of the same class and has no body of its own. - Factory constructor — does not have to create a new object: it may return a cached instance, a subtype, or any suitable instance other than
null. - Const constructor — builds the object at compile time and canonicalises objects with equal values.
When is an initializer list REQUIRED? This is the classic form of the question. Three cases:
- You need to fill a
finalfield with a computed value — you cannot assign to afinalin the body, because by the time the body runs the object counts as fully built. - You need to pass a computed argument to
super(...). - You want to validate input with
assert(...)before the object exists.
The execution order (a list worth memorising):
1. The initializer list, left to right — you may not use this here
2. The superclass constructor
3. Your own body — this is available from here on
| Constructor kind | Creates a new object? | Typical use |
|---|---|---|
| Generative — `Order(this.id)` | Yes, always | Plain data classes and models |
| Named — `Order.draft()` | Yes | Alternative construction scenarios (the overload substitute) |
| Redirecting — `Order.empty() : this('')` | Yes (through another constructor) | Keeping default values in one place |
| Factory — `factory Order.fromJson(...)` | No — may return a cache entry, a singleton, or a subtype | JSON parsing, caching, singletons, choosing a subtype |
| Const — `const Point(this.x, this.y)` | Once at compile time, then reused | Immutable value objects |
Const constructors and canonicalisation. To make a constructor const, every field must be final, the body must be empty, and the initializer list may only use compile-time constants.
After that, writing const Point(1, 2) twice gives you one single object in the program (identical(a, b) == true). That is canonicalisation. Note the flip side: a class with a const constructor can still be called without const, and then you get an ordinary runtime object.
Why this matters in Flutter: when widgets are created with a const constructor, the framework sees on rebuild that the old and the new object are identical and skips rebuilding that subtree. So const is not a style preference — it is a measurable performance win. That is exactly why the prefer_const_constructors lint exists.
Interview tip. The most frequent question here: "what is the difference between a factory constructor and a normal one?" A weak answer is "a factory creates the object". A strong answer has three beats: (1) a generative constructor must create a fresh instance and implicitly returns this; a factory carries a return obligation instead — what it returns is its own business (cache, singleton, subtype); (2) because of that a factory has no this and cannot have an initializer list; (3) a factory cannot be const, so a class whose only constructor is a factory cannot be used in a const context — a real constraint on Flutter widgets. Then give one concrete example: factory Currency(String code) returning a cached instance.
📚 Sources and documentation
- Constructorsofficialdart.dev