Mixins
A mixin is a way to share behaviour across several classes without building a hierarchy. Dart has single inheritance (one extends), but you can apply any number of mixins: class Duck extends Animal with Swimmer, Flyer {}.
Declaring them:
mixin Swimmer { ... }— a free-standing mixin, applicable to any class.mixin Swimmer on Animal { ... }— the `on` clause constrains the superclass: this mixin can only be applied to subtypes ofAnimal. In return, the mixin may useAnimal's members (name,move()) and callsuper.move().mixin class Timestamped { ... }— since Dart 3: a type usable both withwithand withextends.
The constraint: a mixin has no constructor. It may declare fields (int strokes = 0;) but cannot take parameters — which is why behaviour that needs configuration calls for composition rather than a mixin.
Linearization — the most-asked part of mixins. When you write class C extends B with M1, M2, Dart builds a straight chain:
B ← B+M1 ← B+M1+M2 ← C
Mixins are applied left to right, each stacked on top of the previous one. The consequences:
- Method lookup runs right to left:
C→M2→M1→B. So the last mixin wins. - A
super.method()call inside a mixin goes leftwards in the chain —M2'ssuperisM1, andM1'ssuperisB. - Changing the order changes the behaviour:
with M1, M2andwith M2, M1give different results. That is the whole point of the whiteboard question.
This chain is exactly what lets mixins express stackable behaviour: logging, validation, retry — each one calls super and adds its own work on top.
| Approach | What it gives | When to pick it |
|---|---|---|
| Inheritance — `extends` | One parent, `super`, constructor chaining, an "is-a" relationship | When there is a real taxonomy (`Cat extends Animal`) and one level is enough |
| Mixin — `with` | Any number of hierarchy-free behaviours; a context requirement via `on` | When the same behaviour repeats across unrelated classes |
| Composition — hold it as a field | Full control, swappable dependency (a fake in tests), configuration | When the behaviour needs configuration or must change at runtime |
| `implements` | Contract only, zero implementation | When you only need type compatibility (fake, adapter) |
When is a mixin the wrong choice? Practical signals:
- The behaviour needs a constructor parameter — a mixin cannot take one.
- You need to swap the behaviour in tests (with a fake) — a mixin is welded to the class and cannot be detached.
- The mixin is pinned by
onto a very specific type and used in exactly one class — that is just a method of that class, no mixin needed. - Name-collision risk: if two mixins declare the same method, the last one silently wins. The compiler does not warn, because that is linearization working as designed.
Where mixins genuinely shine: Comparable-style operation sets, lifecycle add-ons constrained with on, and core-library mixins such as IterableMixin and ListMixin that turn a couple of abstract members into dozens of ready methods.
Interview tip. The classic question: "`class C extends B with M1, M2` — what does `C().describe()` print?" The right answer structure: (1) draw the chain first — B ← B+M1 ← B+M1+M2 ← C; (2) say lookup runs right to left, so M2 wins; (3) show that super goes leftwards. Nail it with one sentence: "mixins are applied left to right, lookup runs right to left, so the last mixin wins".
The most common mistake is describing mixins as multiple inheritance. Dart has no multiple inheritance — mixins are flattened into a linear chain, which is precisely why the diamond problem never arises. Do not skip that sentence; it lands very well.
📚 Sources and documentation
- Mixinsofficialdart.dev