In Dart, when does an overridden noSuchMethod actually run, and why can a class with one implement an interface without implementing its members?
answer
- default throws NoSuchMethodError
- receiver typed dynamic
- non-default noSuchMethod plus declared member
- compiler-generated forwarders
- return value must fit the type
basics
~20 snoSuchMethod runs when a call reaches a member the object lacks: via a dynamic receiver, or via a member its static type declares but its class leaves unimplemented while overriding noSuchMethod. Such classes get compiler-generated forwarders, so they compile without implementing the interface.
solid answer
~40 s`Object.noSuchMethod(Invocation)` is called when an invocation reaches a member the object does not implement; the default throws `NoSuchMethodError`. In sound Dart that can only happen two ways: the receiver has static type `dynamic`, or the receiver's static type declares the member — abstract is fine — and the object's class has a `noSuchMethod` different from `Object`'s. In the second case the class may leave interface members unimplemented: the compiler generates forwarders that package the call into an `Invocation` (`memberName`, `positionalArguments`, `namedArguments`, `isGetter`, `isSetter`) and pass it to `noSuchMethod`. Whatever it returns becomes the call's result; a value of the wrong type causes a type error at the call. This is how test doubles such as mocktail's `Mock` can implement large interfaces. The cost is that the compiler no longer tells you what is missing.
go deeper
Recall that calling a missing member throws NoSuchMethodError, which comes from the default noSuchMethod.
Explain the two situations in which noSuchMethod can run and what the Invocation object carries.
Explain forwarders, why mocks compile against large interfaces, and why noSuchMethod belongs in test and tooling code rather than production paths.
Weigh dynamic interception against the static guarantees a team relies on, and set limits on where it is acceptable.
## The default behaviour Every object inherits `dynamic noSuchMethod(Invocation invocation)` from `Object`. It runs when an invocation arrives for a member the object does not implement, and the default implementation **throws a `NoSuchMethodError`**. Most programs never see it, because sound static typing rejects calls to members a type does not declare. ## When it can actually run The Dart documentation lists exactly two situations in which an unimplemented member can be invoked: 1. **The receiver has static type `dynamic`.** `dynamic plan = FloorPlan(); plan.addStairs();` compiles; at runtime, if `FloorPlan` has no `addStairs`, its `noSuchMethod` is called. 2. **The receiver's static type declares the member** (abstract is fine) **and the object's class has a non-default `noSuchMethod`.** Here the class may omit the implementation entirely, and calls through the declared type are routed to `noSuchMethod`. A class that merely *has* a `noSuchMethod` override does not make arbitrary calls legal: `Room().fly()` on a statically typed `Room` still fails to compile. ## Forwarders: why the interface is satisfied ```dart abstract class AreaSource { double areaOf(String roomId); List<String> get roomIds; } class RecordingSource implements AreaSource { final List<Invocation> calls = []; @override dynamic noSuchMethod(Invocation invocation) { calls.add(invocation); if (invocation.isGetter && invocation.memberName == #roomIds) { return <String>['hall']; } if (invocation.isMethod && invocation.memberName == #areaOf) { return 12.5; } return super.noSuchMethod(invocation); // throws NoSuchMethodError } } ``` `RecordingSource` implements neither `areaOf` nor `roomIds`, yet it compiles. Because it overrides `noSuchMethod`, the compiler generates a **forwarder** for each missing member: a real method with the declared signature whose body builds an `Invocation` and calls `noSuchMethod`. The SDK documents this with a `MockList<T> implements List<T>` example that compiles without implementing any `List` method. ## The `Invocation` object | Member | Meaning | |---|---| | `memberName` | a `Symbol` such as `#areaOf` | | `positionalArguments` | the positional argument list | | `namedArguments` | a `Map<Symbol, dynamic>` | | `typeArguments` | generic type arguments, if any | | `isMethod`, `isGetter`, `isSetter`, `isAccessor` | what kind of access it was | ## Return values and types - The value `noSuchMethod` returns **becomes the result** of the original invocation. - If that value does not fit the declared return type — returning a `String` for `double areaOf(...)` — a **type error** occurs at the call site. - Calling `super.noSuchMethod(invocation)` delegates to `Object`'s version, which throws. ## Where it is used, and what it costs Uses: - **Test doubles.** Mocking libraries build on it: mocktail's `Mock` overrides `noSuchMethod`, which is why `class MockAreaSource extends Mock implements AreaSource {}` compiles with an empty body. - **Recording or forwarding proxies** around a large interface during debugging. Costs: - **Lost static checking.** A missing member is no longer a compile error; a forgotten case surfaces as a runtime `NoSuchMethodError` or type error. - **Symbol matching by name** is brittle; renaming a member does not update `#areaOf` strings in `noSuchMethod` logic. - **Private members from another library** cannot be implemented by an implementer at all; calls to them reach a throwing forwarder, one reason Dart 3 added class modifiers. In production code, prefer explicit implementations or delegation; keep `noSuchMethod` for test infrastructure and tooling.
- In Dart, why does `Room().fly()` fail to compile even when Room overrides noSuchMethod?Forwarders are generated only for members the class's interface declares. `fly` is not declared on `Room` or its supertypes, so a call through a `Room`-typed expression is a static error. Only a `dynamic` receiver bypasses that check and lets the call reach `noSuchMethod` at runtime.
- In Dart, what happens if noSuchMethod returns null for a call to `double areaOf(String id)`?The returned value becomes the result of the call, but `null` is not a `double`, so a type error is thrown at the invocation. Under sound null safety a `noSuchMethod` implementation must return a value that fits the declared return type, or delegate to `super.noSuchMethod`, which throws `NoSuchMethodError`.
saying these in an interview costs you the question
- Overriding noSuchMethod lets you call any method name on a statically typed object
- noSuchMethod runs whenever any method throws an exception
- Whatever noSuchMethod returns is accepted regardless of the declared return type
- A class implementing an interface must always define every member, even with noSuchMethod
- The default noSuchMethod silently returns null