skip to content

In Dart, when does an overridden noSuchMethod actually run, and why can a class with one implement an interface without implementing its members?

level: seniorimportance: nice to knowfreq 18%

answer

  1. default throws NoSuchMethodError
  2. receiver typed dynamic
  3. non-default noSuchMethod plus declared member
  4. compiler-generated forwarders
  5. return value must fit the type

basics

~20 s

noSuchMethod 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

for a junior

Recall that calling a missing member throws NoSuchMethodError, which comes from the default noSuchMethod.

for a middle

Explain the two situations in which noSuchMethod can run and what the Invocation object carries.

for a senior

Explain forwarders, why mocks compile against large interfaces, and why noSuchMethod belongs in test and tooling code rather than production paths.

for a principal

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