In Dart, when `class UserClient extends BaseClient with Logging, Retry` and both mixins override `send` and call `super.send`, which runs first and why?
answer
- mixins stack, not merge
- rightmost mixin is outermost
- super walks right to left
- BaseClient & Logging & Retry
- reorder with to change wrapping
basics
~10 sRetry.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 sDart **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 linesclass 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 /usersgo deeper
Remember that mixins listed later sit closer to the class, so the last one wins when two define the same method.
Explain the anonymous intermediate classes that each application creates and trace super calls layer by layer through the chain.
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.
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.