In Dart, what rules must an overriding member follow, what does the @override annotation actually check, and what does a super call invoke?
answer
- return type may narrow
- parameter types may widen
- same number of positional parameters
- annotation checked by the analyzer
- super reaches the superclass implementation
basics
~20 sAn override may return a subtype and accept supertypes of the original parameters, with the same positional parameter count. @override is an optional annotation the analyzer checks, reporting it if nothing is overridden. super.m() calls the superclass chain's implementation of m.
solid answer
~40 sDart checks overrides statically. The overriding member's **return type** must be the same as or a subtype of the original's, each **parameter type** must be the same or a supertype, it must accept the same number of positional parameters, and a generic method cannot override a non-generic one or vice versa; narrowing a parameter needs the `covariant` keyword. `@override` is an annotation from `dart:core`, not a keyword: overriding works without it, but when present the analyzer reports `override_on_non_overriding_member` if nothing is overridden, which catches typos and removed base members. The `annotate_overrides` lint, in the recommended and flutter sets, asks you to add it. `super.describe()` calls the implementation inherited from the superclass chain; calling a member that is abstract all the way up is an error.
code
dart · 23 linesclass Rect {
Rect(this.width, this.height);
final double width, height;
String describe() => 'rect ${width}x$height';
Rect scaled(double k) => Rect(width * k, height * k);
}
class Square extends Rect {
Square(double side) : super(side, side);
@override
String describe() => '${super.describe()} (square)';
@override
Square scaled(num k) => Square(width * k);
}
void main() {
Rect r = Square(2);
print(r.describe()); // rect 2.0x2.0 (square)
print(r.scaled(1.5).runtimeType); // Square
}go deeper
Recall that @override marks an intentional override and that super.method() runs the superclass version.
Explain the return-subtype and parameter-supertype rules, the positional-count rule, and what the analyzer does with @override.
Enforce annotate_overrides and @mustCallSuper where base classes rely on them, and treat covariant parameters as deliberate runtime checks.
Set conventions for extensible base classes so that renaming a member in a shared type surfaces every stale override at analysis time.
## What counts as an override A subclass **overrides** an inherited instance member by declaring a member with the same name. Methods, operators, getters and setters can all be overridden. In the floor-plan calculator, `Square extends Rect` might override `describe()` and `scaled()`. ## The signature rules The analyzer checks every override (`invalid_override` when it fails): 1. **Return type**: the same as, or a **subtype** of, the overridden member's return type. `Square scaled(double k)` may override `Rect scaled(double k)`. 2. **Parameter types**: the same as, or a **supertype** of, the original. `set side(num v)` may override `set side(int v)`. 3. **Positional count**: if the original accepts *n* positional parameters, the override must accept *n* too. It may add optional parameters. 4. **Generic-ness**: a generic method cannot override a non-generic one and vice versa. These rules keep calls through the supertype safe: code that holds a `Rect` and calls `scaled(2)` always gets something usable as a `Rect`. Narrowing a parameter type breaks that guarantee and is only allowed with the explicit `covariant` keyword, which moves the check to runtime. | Original in `Rect` | Override in `Square` | Valid? | |---|---|---| | `Rect scaled(double k)` | `Square scaled(double k)` | yes — narrower return | | `Rect scaled(double k)` | `Rect scaled(num k)` | yes — wider parameter | | `Rect scaled(double k)` | `Rect scaled(int k)` | no — narrower parameter | | `Rect scaled(double k)` | `Rect scaled()` | no — fewer positional parameters | | `Rect scaled(double k)` | `Rect scaled(double k, [double? m])` | yes — extra optional parameter | ## What `@override` does and does not do `@override` is a **constant from `dart:core` used as metadata**. It has no runtime effect, and a member overrides its superclass member whether or not it is annotated. What it buys is a static check: - If an annotated member **overrides nothing** — a misspelt name, a base member that was renamed or removed — the analyzer reports `override_on_non_overriding_member`. - The **`annotate_overrides`** lint, part of the `recommended` and `flutter` lint sets, flags overriding members that lack the annotation, so the check covers the whole codebase. A separate annotation, `@mustCallSuper` from `package:meta`, lets a base-class author require overrides to call `super`; an override that does not is reported as `must_call_super`. ## `super` calls ```dart class Square extends Rect { Square(double side) : super(side, side); @override String describe() => '${super.describe()} (square)'; } ``` - `super.describe()` invokes the implementation that `Square` would otherwise have inherited — the nearest one up the superclass chain — not `Square`'s own override. - It is statically resolved to the superclass chain but still runs with `this` bound to the `Square` instance, so any other method `describe()` calls dispatches normally to overrides. - `super` works for getters, setters and operators too: `super.area`, `super[i]`. - If the member is **abstract in every superclass**, `super.member` is an error (`abstract_super_member_reference`). - When mixins are involved, what `super` reaches depends on the mixin application order, a separate topic. ## Overriding fields Because a field is a getter and setter, a subclass can override a field with a getter, or a getter with a field. Overriding a **field with a field** is legal but creates two storage slots, and the `overridden_fields` lint in the recommended set discourages it. ## Checklist for reviews - Every override carries `@override`. - Return types only narrow; parameter types only widen, unless `covariant` is deliberate. - Overrides of lifecycle-style methods call `super` where the base requires it.
- In Dart, why may an override widen a parameter type but not narrow it?Callers holding the supertype may pass any value the original accepted. Widening still accepts all of them; narrowing would reject some, breaking code that is statically valid against the supertype. Dart therefore allows narrowing only with the `covariant` keyword, which inserts a runtime check at the override.
- In Dart, does removing @override from a method stop it from overriding?No. Overriding is determined by the name and signature; `@override` is only metadata. Without it, the analyzer can no longer warn when the base member is renamed or removed, so the method silently becomes a new, unrelated member. The `annotate_overrides` lint keeps the annotation present.
saying these in an interview costs you the question
- Without @override a method with the same name does not override anything
- An override may narrow a parameter type to a subtype without any keyword
- An override must return exactly the same type as the original
- super.describe() calls the current class's own describe recursively
- @override changes how the method is dispatched at runtime