skip to content

Object-Oriented Features

Dart's object model: constructors from factory to primary, extends versus implements, mixins, class modifiers, extension types, equality and enums. Interviewers probe reuse without inheritance.

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

explore

questions

page 1 of 2

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, what do an enum value's index and name return, and how do you list all values and look one up from a string?

level: juniorimportance: must knowfreq 55%

basics

~20 s

index is the value's zero-based position in the declaration and name is its source identifier as a String. The generated static values list holds every value in order; values.byName('x') finds one by name and throws ArgumentError when none matches.

open as a page

In Dart, why does [1, 2] == [1, 2] evaluate to false, and how do you compare two lists by their contents?

level: juniorimportance: must knowfreq 58%

basics

~20 s

Dart's List keeps identity equality: == is true only for the same list object, and every non-const literal creates a new one. Compare contents with Flutter's listEquals, package:collection's ListEquality, or a length check plus element loop.

open as a page

In Dart, what is an extension method, how do you declare one with `extension ... on`, and what may it contain?

level: juniorimportance: must knowfreq 60%

basics

~20 s

An extension method adds a member to an existing type, including types you do not own, via extension Name on Type { ... }. It may declare methods, getters, setters, operators and static members, but no instance fields, constructors or abstract members.

open as a page

In Dart, what is the difference between extends and implements, and what must a class that implements another class re-declare?

level: juniorimportance: must knowfreq 74%

basics

~20 s

extends makes a subclass that inherits the superclass's implementation, and a class has exactly one superclass. implements only adopts the implicit interface of each listed class, so the class must itself implement every instance member, fields included, but not constructors or statics.

open as a page

In Dart, what is a mixin, how do you declare one and apply it with `with`, and what can it contain?

level: juniorimportance: must knowfreq 65%

basics

~20 s

A Dart mixin is a mixin declaration whose fields and methods are added to a class listed after with. It cannot be instantiated or declare a constructor, and one class can apply several mixins alongside its single superclass.

open as a page

In Dart, how do you declare an enhanced enum, such as an order status with a label and a colour, and what rules apply?

level: middleimportance: must knowfreq 50%

basics

~20 s

List each value with constructor arguments, end the list with a semicolon, then declare final fields and a const constructor, plus any getters or methods. Enhanced enums, since Dart 2.17, cannot extend classes or override index, == or hashCode.

open as a page

In Dart, how do you override operator == and hashCode so a map Coordinate class deduplicates in a Set, and what breaks with only ==?

level: middleimportance: must knowfreq 68%

basics

~20 s

Override both: operator ==(Object other) checks that other is a Coordinate and compares the fields, and hashCode returns Object.hash over those same fields. Override only == and equal coordinates usually land in different hash buckets, so a Set keeps duplicates.

open as a page

In Dart, why does calling an extension method on a `dynamic` variable fail, and what does static resolution mean for overriding?

level: middleimportance: must knowfreq 50%

basics

~20 s

Dart picks an extension member from the receiver's static type at compile time. A dynamic receiver has no static type to match, so the call is dispatched at runtime and throws NoSuchMethodError. For the same reason extensions never override and cannot be overridden.

open as a page

In Dart, what does a mixin's `on` clause do, and when do you need it rather than an abstract member?

level: middleimportance: must knowfreq 48%

basics

~20 s

An on T clause lets a mixin make super calls resolved against T, and allows applying it only to classes whose superclass is a subtype of T. Use it to wrap an inherited method; an abstract member suffices when you only call the host.

open as a page

In Dart 3, what do the `base`, `interface`, `final` and `sealed` class modifiers each allow code in another library to do?

level: middleimportance: must knowfreq 55%

basics

~20 s

Outside the declaring library, base allows extending but not implementing, interface allows implementing but not extending, final allows neither, and sealed allows neither and is implicitly abstract so its same-library subtypes form a closed set.

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 makes a class abstract, how do you declare an abstract member, and what must a concrete subclass do with it?

level: juniorimportance: should knowfreq 60%

basics

~20 s

A class marked abstract cannot be instantiated and may declare members with no body, written as a signature ending in a semicolon. A concrete subclass must implement every inherited abstract member, or be declared abstract itself.

open as a page

In Dart 3, why can you extend a `final` class from the same file but not from another file in the same package?

level: juniorimportance: should knowfreq 35%

basics

~20 s

Class modifiers restrict other libraries, and in Dart a library is one file plus its part files, not a package. A second file in the same package is a separate library, so it cannot extend or implement a final class.

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, when should you use an enhanced enum instead of a class with static const instances, and when is neither the right fit?

level: middleimportance: should knowfreq 42%

basics

~20 s

Use an enhanced enum for a fixed set of same-shaped values: you get values, byName, a closed type and exhaustive switches. Keep a class of constants when the set must stay open; use a sealed hierarchy when cases carry different data.

open as a page

In Dart, how does the identical() function differ from ==, and when do you need identical() or an identity-based Set or Map?

level: middleimportance: should knowfreq 40%

basics

~20 s

== calls the left operand's overridable operator ==; identical(a, b) is a top-level function that only tests whether both references are the same object. Use identity for fast paths, visited-sets and Set.identity or Map.identity collections.

open as a page

In Dart, when two imported extensions both define the same member for a type, how is the conflict resolved?

level: middleimportance: should knowfreq 30%

basics

~20 s

If both extensions apply, Dart picks the one whose on type is more specific; if neither is, the implicit call is a compile-time ambiguity error. Fix it by applying one explicitly, NumberParsing('42').parseInt(), by limiting an import, or with an import prefix when the extension names also clash.

open as a page

In Dart, how do fields, getters and setters relate in a class's interface, and why can a field become a getter without breaking callers?

level: middleimportance: should knowfreq 38%

basics

~20 s

Every Dart field induces an implicit getter, plus a setter unless it is final, and interfaces contain those accessors rather than storage. So a field and a getter are interchangeable to callers, and either can implement or override the other.

open as a page

In Dart, what rules must an overriding member follow, what does the @override annotation actually check, and what does a super call invoke?

level: middleimportance: should knowfreq 45%

basics

~20 s

An override may return a subtype and accept supertypes of the original parameters, with the same positional parameter count. @override is an optional annotation the analyzer checks, reporting it if nothing is overridden. super.m() calls the superclass chain's implementation of m.

open as a page

In Dart, when should you share behaviour across several API client classes with a mixin rather than `extends` or an extension method?

level: middleimportance: should knowfreq 45%

basics

~20 s

Use a mixin when unrelated clients need the same members, possibly with state or overrides, without sharing a superclass. Use extends for a true single is-a hierarchy, and an extension method for stateless helpers on a type you cannot or should not change.

open as a page

In Dart, when should a payment-method hierarchy be `sealed` rather than `final`, and what does each cost when you later add a method?

level: middleimportance: should knowfreq 45%

basics

~20 s

Use sealed when PaymentMethod is abstract with a fixed set of same-library subtypes and callers should switch over them exhaustively; adding a subtype is then breaking. Use final when the type must be constructible or new subtypes must be non-breaking.

open as a page

After a teammate inserted a new value into a Dart OrderStatus enum, orders saved on devices reloaded with the wrong status; what went wrong, and how should enums be persisted?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The app stored index, which is only the declaration position, so inserting a value shifted every later index. Persist a stable identifier instead, such as name or an explicit code field, and map unknown or missing codes to a fallback value.

open as a page

A Dart Set of map Coordinate objects stops finding elements and shows duplicates after coordinates are edited in place; what is wrong, and how do you fix it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The Set stored each Coordinate under the hashCode it had when added; mutating a field that feeds hashCode changes the hash, so lookups probe the wrong bucket. Make the class immutable and replace elements with remove then add.

open as a page

In Dart, how would you use an extension type so user ids and order ids, both plain ints, cannot be mixed up, and what does it cost at runtime?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Declare extension type UserId(int value) {} and extension type OrderId(int value) {}. Each is a distinct static type over int, so passing a UserId where an OrderId or int is expected is a compile error, yet at runtime both are just int: no wrapper object.

open as a page

In Dart, when `class UserClient extends BaseClient with Logging, Retry` and both mixins override `send` and call `super.send`, which runs first and why?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Retry.send runs first: Dart applies mixins left to right, each creating a new superclass layer, so the last mixin is nearest the class. Its super.send reaches Logging.send, whose super.send reaches BaseClient.send.

open as a page

In Dart 3, if `PaymentMethod` is a `base` class, why must every subclass be `base`, `final` or `sealed`, and does the same apply under `interface` or `sealed`?

level: seniorimportance: should knowfreq 30%

basics

~20 s

base guarantees every instance inherits the real implementation, so a subclass that allowed implements would reopen the hole; hence subtypes of base or final must be base, final or sealed. interface and sealed do not propagate.

open as a page

In Dart, which operators can a class define, and how would you declare + and [] on a coordinate value type?

level: middleimportance: nice to knowfreq 28%

basics

~10 s

Arithmetic, bitwise, relational, ==, [] and []= are declared as methods with the operator keyword, such as Coordinate operator +(Coordinate other). !=, &&, ||, ?? and ! are built in, and += reuses +.

open as a page

In Dart 3, why can you no longer mix in an ordinary class, and when would you declare a `mixin class` instead of a `mixin`?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

Since Dart 3.0, a class in a library at language version 3.0 or later can be applied with with only if declared mixin or mixin class. Use mixin class only when the same type must work both as a superclass and as a mixin.

open as a page

showing 1–30 of 35