On this page23 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What super Means
- 2. Extending a Method with super.method()
- 3. Before or After?
- 4. super in a Getter
- 5. super.toString()
- 6. Calling the Parent Constructor with : super(...)
- 7. When You Need : super(...)
- 8. Named Parent Constructors: : super.named(...)
- 9. Constructor Call Order
- 10. When the Parent Has No Unnamed Constructor
- 11. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 61 — super
| Category | 10 — Advanced OOP |
| Lecture | 061 of 140 |
| Estimated video length | 8 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
Overriding a method throws the parent's version away. With
super, a child class can reuse the parent's work and add to it — in methods, getters,toString()and constructors.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 052–060:
- classes, objects, fields, and methods
- constructors, initializer lists, and named constructors
this, getters, and settersextendsand super parameters (super.name)- overriding methods with
@override, includingtoString()
Connection With Previous Lectures
In Lecture 060, a child class overrode its parent's method:
class Account {
double balance = 1000;
void deposit(double amount) {
balance += amount;
print('Deposited $amount');
}
}
class SavingsAccount extends Account {
@override
void deposit(double amount) {
print('Saved! Balance is now $balance');
}
}
void main() {
SavingsAccount().deposit(500);
}Saved! Balance is now 1000.0The balance never changed. The override replaced deposit, so the parent's code — the line
that actually adds the money — no longer runs. Copying that line into the child would work
until someone changes the parent. We need a way to say "do what the parent does, then a
little more".
Learning Objectives
By the end of this lecture you will be able to:
- Explain what the keyword
superrefers to - Call a parent method with
super.method()to extend behaviour instead of replacing it - Decide whether the
supercall goes before or after the child's own code - Use
superinside overridden getters andtoString() - Call a parent constructor with
: super(...)and compare it with super parameters - Call a named parent constructor with
: super.named(...) - Predict the order in which parent and child constructors run
- Fix the errors Dart reports when a parent constructor is not called correctly
1. What super Means
Inside a child class, this is the current object and super is the parent's part of the
same object. super.something means "use the parent's version of something".
Analogy — a new chef inherits a family recipe. Instead of rewriting it, the chef says: "Cook it exactly as grandma did — then add extra chilli."
superis "as grandma did".
2. Extending a Method with super.method()
Call the parent's method from inside the override:
class Account {
double balance = 1000;
void deposit(double amount) {
balance += amount;
print('Deposited $amount');
}
}
class SavingsAccount extends Account {
@override
void deposit(double amount) {
super.deposit(amount);
print('Saved! Balance is now $balance');
}
}
void main() {
SavingsAccount().deposit(500);
}Deposited 500.0
Saved! Balance is now 1500.0super.deposit(amount) runs the parent's code — the balance grows — and then the child adds
its own line. The child extends the behaviour instead of replacing it.
3. Before or After?
The super call is an ordinary statement, so you decide where it goes:
class Report {
void show() {
print('Student: Sara');
}
}
class FramedReport extends Report {
@override
void show() {
print('--- Report ---');
super.show();
print('--- End ---');
}
}
void main() {
FramedReport().show();
}--- Report ---
Student: Sara
--- End ---Put your code before the call to prepare or check something, after it to react to the result. You can even wrap the parent's code on both sides, as here.
4. super in a Getter
Getters are overridden the same way, and super.getterName reads the parent's value:
class Product {
Product(this.name, this.price);
final String name;
final double price;
double get finalPrice => price;
}
class SaleProduct extends Product {
SaleProduct(super.name, super.price);
@override
double get finalPrice => super.finalPrice * 0.8;
}
void main() {
var bag = SaleProduct('Bag', 1500);
print('${bag.name}: ${bag.finalPrice}');
}Bag: 1200.0If Product later adds tax inside finalPrice, the sale price picks it up automatically.
5. super.toString()
A child often wants to print everything the parent prints, plus its own fields:
class Person {
Person(this.name);
final String name;
@override
String toString() => 'Name: $name';
}
class Student extends Person {
Student(super.name, this.marks);
final int marks;
@override
String toString() => '${super.toString()}, Marks: $marks';
}
void main() {
print(Student('Sara', 92));
}Name: Sara, Marks: 926. Calling the Parent Constructor with : super(...)
In Lecture 059, super.name forwarded a parameter to the parent constructor. The longer form
writes the call in the initializer list:
class Person {
Person(this.name);
final String name;
}
class Student extends Person {
Student(String name, this.marks) : super(name);
final int marks;
}
void main() {
var student = Student('Ali', 85);
print('${student.name}: ${student.marks}');
}Ali: 85Student(super.name, this.marks) means exactly the same thing. Prefer super parameters when you
pass a value straight through.
7. When You Need : super(...)
Use the initializer-list form when the parent should receive something other than a parameter as it was given — a fixed value or a changed one:
class Person {
Person(this.name);
final String name;
}
class Guest extends Person {
Guest() : super('Guest');
}
class Shouter extends Person {
Shouter(String name) : super(name.toUpperCase());
}
void main() {
print(Guest().name);
print(Shouter('Zayan').name);
}Guest
ZAYAN8. Named Parent Constructors: : super.named(...)
If the parent has a named constructor, the child calls it by name:
class Account {
Account(this.owner, this.balance);
Account.empty(this.owner) : balance = 0;
final String owner;
double balance;
}
class SavingsAccount extends Account {
SavingsAccount(super.owner, super.balance);
SavingsAccount.empty(String owner) : super.empty(owner);
}
void main() {
var a = SavingsAccount('Ali', 500);
var b = SavingsAccount.empty('Sara');
print('${a.owner}: ${a.balance}');
print('${b.owner}: ${b.balance}');
}Ali: 500.0
Sara: 0.0Super parameters work here too: SavingsAccount.empty(super.owner) : super.empty();.
9. Constructor Call Order
Every child constructor calls a parent constructor — if you write none, Dart quietly inserts
super(). The parent always finishes first:
class Vehicle {
Vehicle() {
print('1. Vehicle built');
}
}
class Car extends Vehicle {
Car() {
print('2. Car built');
}
}
class ElectricCar extends Car {
ElectricCar() {
print('3. ElectricCar built');
}
}
void main() {
ElectricCar();
}1. Vehicle built
2. Car built
3. ElectricCar builtLike a house: foundation, then walls, then roof. By the time the child's body runs, the parent's fields are ready to use.
10. When the Parent Has No Unnamed Constructor
The hidden super() only works if the parent has an unnamed constructor that needs no
arguments. Here the parent has only a named one:
class Account {
Account.opened(this.owner);
final String owner;
}
class SavingsAccount extends Account {
SavingsAccount(String owner);
}Error: The superclass, 'Account', has no unnamed constructor that takes no arguments.
SavingsAccount(String owner);
^^^^^^^^^^^^^^Dart tried to call Account(), which does not exist. Call the right one yourself:
class Account {
Account.opened(this.owner);
final String owner;
}
class SavingsAccount extends Account {
SavingsAccount(String owner) : super.opened(owner);
}
void main() {
print(SavingsAccount('Zayan').owner);
}Zayan11. A Real-World Example
class Account {
Account(this.owner, this.balance) {
print('Account opened for $owner');
}
Account.empty(String owner) : this(owner, 0);
final String owner;
double balance;
void deposit(double amount) {
balance += amount;
}
@override
String toString() => '$owner: $balance';
}
class SavingsAccount extends Account {
SavingsAccount.empty(super.owner, this.rate) : super.empty() {
print('Savings rate set to $rate');
}
final double rate;
@override
void deposit(double amount) {
super.deposit(amount);
super.deposit(amount * rate);
}
@override
String toString() => '${super.toString()} (savings)';
}
void main() {
var account = SavingsAccount.empty('Sara', 0.1);
account.deposit(1000);
print(account);
}Account opened for Sara
Savings rate set to 0.1
Sara: 1100.0 (savings)The named super constructor runs first, then the child's body. deposit reuses the parent's
logic twice — once for the money, once for the bonus — and toString() builds on the parent's
text. (: this(owner, 0) is the redirecting constructor from Lecture 055.)
Code Examples
Example 1 — Extending a Method
class Animal {
void speak() {
print('Some sound');
}
}
class Dog extends Animal {
@override
void speak() {
super.speak();
print('Woof');
}
}
void main() {
Dog().speak();
}Some sound
WoofExample 2 — Child Code First
class Door {
void open() {
print('Door opened');
}
}
class LockedDoor extends Door {
@override
void open() {
print('Unlocking...');
super.open();
}
}
void main() {
LockedDoor().open();
}Unlocking...
Door openedExample 3 — Getter Built on the Parent
class Employee {
double get salary => 50000;
}
class Manager extends Employee {
@override
double get salary => super.salary + 20000;
}
void main() {
print(Manager().salary);
}70000.0Example 4 — super.toString()
class Shape {
@override
String toString() => 'Shape';
}
class Circle extends Shape {
Circle(this.radius);
final double radius;
@override
String toString() => '${super.toString()} -> Circle(r=$radius)';
}
void main() {
print(Circle(2.5));
}Shape -> Circle(r=2.5)Example 5 — Fixed Value with : super(...)
class Pet {
Pet(this.kind);
final String kind;
}
class Cat extends Pet {
Cat() : super('Cat');
}
void main() {
print(Cat().kind);
}CatExample 6 — Constructor Order
class Base {
Base() {
print('Base first');
}
}
class Child extends Base {
Child() {
print('Child second');
}
}
void main() {
Child();
}Base first
Child secondExample 7 — Student Marks
class Student {
Student(this.name, this.marks);
final String name;
final int marks;
void printReport() {
print('$name scored $marks');
}
}
class HonorsStudent extends Student {
HonorsStudent(super.name, super.marks);
@override
void printReport() {
super.printReport();
print('Honors bonus: ${marks + 5}');
}
}
void main() {
HonorsStudent('Ali', 88).printReport();
}Ali scored 88
Honors bonus: 93Example 8 — Shopping Total
class Cart {
Cart(this.subtotal);
final double subtotal;
double get total => subtotal;
}
class DeliveryCart extends Cart {
DeliveryCart(super.subtotal);
@override
double get total => super.total + 200;
}
void main() {
print('Total: ${DeliveryCart(2400).total}');
}Total: 2600.0Example 9 — Bank Balance with a Fee
class Account {
double balance = 1000;
void withdraw(double amount) {
balance -= amount;
}
}
class FeeAccount extends Account {
@override
void withdraw(double amount) {
super.withdraw(amount + 25);
}
}
void main() {
var account = FeeAccount();
account.withdraw(300);
print('Balance: ${account.balance}');
}Balance: 675.0The child changes the argument, not the logic: the parent still does the subtraction.
Example 10 — Temperature Reading
class Thermometer {
Thermometer(this.celsius);
final double celsius;
void report() {
print('Celsius: $celsius');
}
}
class DualThermometer extends Thermometer {
DualThermometer(super.celsius);
@override
void report() {
super.report();
print('Fahrenheit: ${celsius * 9 / 5 + 32}');
}
}
void main() {
DualThermometer(25).report();
}Celsius: 25.0
Fahrenheit: 77.0Common Beginner Mistakes
1. Forgetting super. inside the override
class Dog extends Animal {
@override
void speak() {
// speak(); // ✗ calls Dog.speak again, forever
super.speak(); // ✓
print('Woof');
}
}Unhandled exception:
Stack OverflowWithout super., the method calls itself until the program crashes.
2. Calling super(...) inside the constructor body
class Dog extends Animal {
// Dog(String name) {
// super(name); // ✗
// }
Dog(String name) : super(name); // ✓
}Error: Superclass has no method named 'call'.The parent constructor is called in the initializer list, after :, never in the body.
3. Not passing the arguments the parent needs
class Account {
Account(this.owner);
final String owner;
}
class Savings extends Account {
// Savings(String owner); // ✗
Savings(super.owner); // ✓
}Error: The implicitly called unnamed constructor from 'Account' has required parameters.
Try adding an explicit super initializer with the required arguments.4. Mixing super.name with : super(name)
class Dog extends Animal {
// Dog(super.name) : super(name); // ✗ the name is passed twice
Dog(super.name); // ✓
}Error: Positional super-initializer parameters cannot be used when the super initializer has positional arguments.5. Putting initializers after super(...)
class Dog extends Animal {
// Dog(String name) : super(name), sound = 'Woof'; // ✗
Dog(String name) : sound = 'Woof', super(name); // ✓
final String sound;
}Error: Can't have initializers after 'super'.The super(...) call must be the last item in the initializer list.
Best Practices
- Call
super.method()when the child should add to the parent's work, not repeat it. - Leave out the
supercall only when you truly mean to replace the parent's behaviour. - Prefer super parameters (
super.name); use: super(...)when the value is fixed or changed. - Build
toString()onsuper.toString()so parent fields are never forgotten. - Remember the parent constructor finishes before the child's body runs.
Mini Challenge
Create Player with a name, a score that starts at 0, and addPoints(int points), which
adds the points. Create BonusPlayer whose addPoints adds the points and a bonus of 10
by calling the parent's method. Give both a toString(), the child's built on the parent's.
Your program should print exactly:
Zayan: 60 (bonus player)Try it yourself before opening the solution.
Show solution
class Player {
Player(this.name);
final String name;
int score = 0;
void addPoints(int points) {
score += points;
}
@override
String toString() => '$name: $score';
}
class BonusPlayer extends Player {
BonusPlayer(super.name);
@override
void addPoints(int points) {
super.addPoints(points + 10);
}
@override
String toString() => '${super.toString()} (bonus player)';
}
void main() {
var player = BonusPlayer('Zayan');
player.addPoints(20);
player.addPoints(20);
print(player);
}Zayan: 60 (bonus player)Each call adds 20 + 10, so two calls give 60.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Create
Birdwithmove()printingFlying. CreatePenguinthat overridesmove(), calls the parent's version, then prints...but I prefer swimming. - Create
Greeterwith a gettermessagereturning'Hello'. Override it inLoudGreeterto return the parent's message followed by'!!!'. PrintHello!!!. - Create
Fruit(this.name)andApple() : super(...)so thatprint(Apple().name)printsApple.
Application
-
Create
Person(this.name)withtoString()returning'Person: $name', andTeacher(name, subject)whosetoString()printsPerson: Sara, teaches Math. -
Add three classes
A,B extends A,C extends B, each printing its letter in its constructor. What doesC();print? -
Itemhas a getterpricereturning100.TaxedItemadds 15% usingsuper.price. Print the taxed price. -
This program does not compile. Fix the
Boxconstructor, without changingContainer:class Container { Container.sized(this.size); final int size; } class Box extends Container { Box(int size); }
Challenge
-
Accounthasbalance = 500andwithdraw(double amount).SafeAccountoverrideswithdraw: ifamountis more thanbalanceit printsNot enough money, otherwise it calls the parent. Withdraw800, then200, and printBalance: 300.0. -
Give
Accounta named constructorAccount.vip(this.owner) : balance = 5000;. CreateVipSavings.vip(super.owner) : super.vip();and printAli: 5000.0. -
Predict the output, then explain it in one sentence:
class Lamp { Lamp() { print('Lamp ready'); } } class DeskLamp extends Lamp { DeskLamp() : super() { print('DeskLamp ready'); } } void main() { DeskLamp(); DeskLamp(); }
Practice Answers
Question 1 — Solution
class Bird {
void move() {
print('Flying');
}
}
class Penguin extends Bird {
@override
void move() {
super.move();
print('...but I prefer swimming');
}
}
void main() {
Penguin().move();
}Flying
...but I prefer swimmingQuestion 2 — Solution
class Greeter {
String get message => 'Hello';
}
class LoudGreeter extends Greeter {
@override
String get message => '${super.message}!!!';
}
void main() {
print(LoudGreeter().message);
}Hello!!!Question 3 — Solution
class Fruit {
Fruit(this.name);
final String name;
}
class Apple extends Fruit {
Apple() : super('Apple');
}
void main() {
print(Apple().name);
}AppleQuestion 4 — Solution
class Person {
Person(this.name);
final String name;
@override
String toString() => 'Person: $name';
}
class Teacher extends Person {
Teacher(super.name, this.subject);
final String subject;
@override
String toString() => '${super.toString()}, teaches $subject';
}
void main() {
print(Teacher('Sara', 'Math'));
}Person: Sara, teaches MathQuestion 5 — Solution
class A {
A() {
print('A');
}
}
class B extends A {
B() {
print('B');
}
}
class C extends B {
C() {
print('C');
}
}
void main() {
C();
}A
B
CEach constructor calls its parent's first, so the oldest class prints first.
Question 6 — Solution
class Item {
double get price => 100;
}
class TaxedItem extends Item {
@override
double get price => super.price * 1.15;
}
void main() {
print(TaxedItem().price);
}114.99999999999999The long decimal is normal double rounding (Lecture 009). Use toStringAsFixed(2) to print
115.00.
Question 7 — Solution
Container has no unnamed constructor, so Box must call sized itself:
class Container {
Container.sized(this.size);
final int size;
}
class Box extends Container {
Box(int size) : super.sized(size);
}
void main() {
print(Box(3).size);
}3Question 8 — Solution
class Account {
double balance = 500;
void withdraw(double amount) {
balance -= amount;
}
}
class SafeAccount extends Account {
@override
void withdraw(double amount) {
if (amount > balance) {
print('Not enough money');
} else {
super.withdraw(amount);
}
}
}
void main() {
var account = SafeAccount();
account.withdraw(800);
account.withdraw(200);
print('Balance: ${account.balance}');
}Not enough money
Balance: 300.0Question 9 — Solution
class Account {
Account.vip(this.owner) : balance = 5000;
final String owner;
double balance;
}
class VipSavings extends Account {
VipSavings.vip(super.owner) : super.vip();
}
void main() {
var account = VipSavings.vip('Ali');
print('${account.owner}: ${account.balance}');
}Ali: 5000.0Question 10 — Solution
Lamp ready
DeskLamp ready
Lamp ready
DeskLamp readyEvery new DeskLamp runs the parent constructor first and then its own body; : super() only
writes out the call Dart would insert anyway.
Quick Revision
- What does
superrefer to inside a child class? - What is the difference between overriding with and without
super.method()? - How do you reuse a parent getter's value in an override?
- What do
Student(super.name)andStudent(String name) : super(name)have in common? - When must you use
: super(...)instead of a super parameter? - How do you call a parent's named constructor?
- Which constructor body runs first — parent or child?
- Why does a child fail to compile when the parent has only named constructors?
Key Takeaways
superis the parent's part of the current object.super.method()lets an override extend the parent's behaviour instead of replacing it.- Place the
supercall before, after, or between your own code — the order is yours. superworks in getters andtoString()too.: super(...)calls the parent constructor; super parameters are the shorthand.: super.named(...)calls a named parent constructor.- Parent constructors run first; without an explicit call Dart inserts
super(). - If the parent has no unnamed no-argument constructor, the child must call one explicitly.
What's Next?
Lecture 62 — Abstract Classes. So far every parent class has had a complete version of each
method for super to call. Sometimes a parent can only describe what its children must do —
every shape has an area, but there is no formula for a "shape". Next we will learn how abstract
classes declare methods without bodies and require every child to fill them in.