skip to content

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%

answer

  1. mixins stack, not merge
  2. rightmost mixin is outermost
  3. super walks right to left
  4. BaseClient & Logging & Retry
  5. reorder with to change wrapping

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.

solid answer

~40 s

Dart **linearizes** mixins. `extends BaseClient with Logging, Retry` builds an anonymous class `BaseClient with Logging`, then applies `Retry` on top of that, and `UserClient` extends the result. Method lookup therefore goes `UserClient`, `Retry`, `Logging`, `BaseClient`, `Object`. A call to `send` hits `Retry.send`; its `super.send` goes one layer down to `Logging.send`, whose `super.send` goes to `BaseClient.send`. So the rightmost mixin wraps the others, and same-named members never raise an ambiguity error — the order you write decides the winner (the later member must still be a valid override of the earlier one). Swapping to `with Retry, Logging` makes logging the outer layer, logging once around all attempts instead of once per attempt.

code

dart · 29 lines
dart
class BaseClient {
  String send(String path) => 'response for $path';
}

mixin Logging on BaseClient {
  @override
  String send(String path) {
    print('Logging: before $path');
    final result = super.send(path);
    print('Logging: after $path');
    return result;
  }
}

mixin Retry on BaseClient {
  @override
  String send(String path) {
    print('Retry: attempt 1');
    return super.send(path);
  }
}

class UserClient extends BaseClient with Logging, Retry {}

void main() => print(UserClient().send('/users'));
// Retry: attempt 1
// Logging: before /users
// Logging: after /users
// response for /users

go deeper

for a junior

Remember that mixins listed later sit closer to the class, so the last one wins when two define the same method.

for a middle

Explain the anonymous intermediate classes that each application creates and trace super calls layer by layer through the chain.

for a senior

Treat with-order as a behavioural decision for wrapper mixins such as retry and logging, and catch wrappers that forget to call super and silently truncate the chain.

for a principal

Decide whether ordering-sensitive mixin stacks are acceptable in shared client code or whether explicit decorators make the wrapping order more visible to reviewers.

## Mixins form a chain, not a set When several Dart mixins define the same member, the language does not report a conflict. Instead it **linearizes**: every mixin application creates a new, anonymous superclass layered on the previous one. The declaration ```dart class UserClient extends BaseClient with Logging, Retry {} ``` is built step by step: 1. Start with the superclass `BaseClient`. 2. Apply `Logging` on it, producing an anonymous class often written `BaseClient&Logging`. 3. Apply `Retry` on that, producing `BaseClient&Logging&Retry`. 4. `UserClient` extends that final layer. The resulting **lookup order** for an instance member is the reverse of the list: `UserClient` → `Retry` → `Logging` → `BaseClient` → `Object`. The first layer that defines the member wins. ## How `super` moves through the layers Inside a mixin, `super` means "the layer directly below me in this particular application". It is not fixed when the mixin is written; it is fixed when the mixin is applied. That is why both mixins below need `on BaseClient`: it guarantees whatever sits below them has a `send`. ```dart class BaseClient { String send(String path) => 'response for $path'; } mixin Logging on BaseClient { @override String send(String path) { print('Logging: before $path'); final result = super.send(path); print('Logging: after $path'); return result; } } mixin Retry on BaseClient { @override String send(String path) { print('Retry: attempt 1'); return super.send(path); } } class UserClient extends BaseClient with Logging, Retry {} void main() => print(UserClient().send('/users')); ``` Output: ```text Retry: attempt 1 Logging: before /users Logging: after /users response for /users ``` Trace it: `UserClient` defines no `send`, so lookup finds `Retry.send`. Its `super.send` goes to the layer below, `Logging.send`. That one's `super.send` goes to `BaseClient.send`, which returns the string. The two `Logging` lines print before the final `print` because `main` prints only the returned value. ## Why the order is a design decision For API clients that combine logging and retry, the order changes behaviour, not just output: | Declaration | Outer layer | Effect | |---|---|---| | `with Logging, Retry` | `Retry` | each attempt passes through `Logging`, so every retry is logged | | `with Retry, Logging` | `Logging` | `Logging` wraps the whole retry loop, so a call is logged once, however many attempts it takes | Neither is wrong; choose deliberately and write the choice down, because a reviewer reading `with A, B` rarely thinks about wrapping order. ## Rules that fall out of linearization - **The rightmost mixin wins** for a member defined by several mixins, unless the class itself overrides it; the later definition must still be a valid override, or the class fails to compile. - **The class's own members win over every mixin**, because the class sits at the top of the chain. - **A mixin that does not call `super` ends the chain** for that member: layers below it are silently skipped. A forgotten `super.send` in `Retry` would mean logging never runs. - **A layer that omits the member is transparent**: lookup passes through it to the next one. - **Instance checks see every layer**: `UserClient() is Logging` and `is Retry` are both true. - **Fields are layered too**: two mixins that declare a field of the same name produce one visible field, the one from the later layer, shadowing the other — a quiet source of bugs. ## Contrast with other models Some languages copy trait members into the class and report a clash as an error you must resolve. Dart's mixins are the other kind: inserted into the superclass chain, so composition order resolves the clash. The general comparison belongs to object-oriented theory; for Dart the practical rule is simply "read `with` right to left to find who runs first". ## Interview tips Interviewers often hand over a snippet like the one above and ask for the output. Say the chain aloud (`UserClient`, `Retry`, `Logging`, `BaseClient`), then trace each `super`. Mention that no ambiguity error exists, that forgetting `super` truncates the chain, and that reordering `with` is how you change which behaviour wraps which.

  • In Dart, if UserClient itself overrides send and calls super.send, where does that call go?
    To the outermost mixin layer, `Retry.send`, because `UserClient`'s superclass is the anonymous `BaseClient with Logging, Retry` class. The chain then continues through `Logging` to `BaseClient` exactly as before.
  • In Dart, what happens if Retry.send forgets to call super.send?
    The chain stops at `Retry`. `Logging.send` and `BaseClient.send` never run for that call, with no compile error or warning, because overriding without calling super is legal. It usually shows up as missing log lines or a request that never reaches the real client.
  • In Dart, how would you log once per call rather than once per retry attempt?
    Put `Logging` outermost by listing it last: `extends BaseClient with Retry, Logging`. Lookup then finds `Logging.send` first, and its single `super.send` enters `Retry`'s loop, so all attempts sit inside one pair of log lines.

saying these in an interview costs you the question

  • Two mixins defining the same method cause a compile-time conflict.
  • The first mixin listed after with takes precedence.
  • super in a mixin always means the declared on type's own method.
  • Mixin order only affects output, never behaviour.
  • The base class method runs first, then the mixins in order.