Watch the video (8 min)
On this page23 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What super Means
  5. 2. Extending a Method with super.method()
  6. 3. Before or After?
  7. 4. super in a Getter
  8. 5. super.toString()
  9. 6. Calling the Parent Constructor with : super(...)
  10. 7. When You Need : super(...)
  11. 8. Named Parent Constructors: : super.named(...)
  12. 9. Constructor Call Order
  13. 10. When the Parent Has No Unnamed Constructor
  14. 11. A Real-World Example
  15. Code Examples
  16. Common Beginner Mistakes
  17. Best Practices
  18. Mini Challenge
  19. Practice Questions
  20. Practice Answers
  21. Quick Revision
  22. Key Takeaways
  23. What's Next?

Lecture 61 — super

Category10 — Advanced OOP
Lecture061 of 140
Estimated video length8 min
TargetDart 3.12.2
LevelIntermediate

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 setters
  • extends and super parameters (super.name)
  • overriding methods with @override, including toString()

Connection With Previous Lectures

In Lecture 060, a child class overrode its parent's method:

dart
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);
}
output
Saved! Balance is now 1000.0

The 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 super refers to
  • Call a parent method with super.method() to extend behaviour instead of replacing it
  • Decide whether the super call goes before or after the child's own code
  • Use super inside overridden getters and toString()
  • 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." super is "as grandma did".


2. Extending a Method with super.method()

Call the parent's method from inside the override:

dart
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);
}
output
Deposited 500.0
Saved! Balance is now 1500.0

super.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:

dart
class Report {
  void show() {
    print('Student: Sara');
  }
}

class FramedReport extends Report {
  @override
  void show() {
    print('--- Report ---');
    super.show();
    print('--- End ---');
  }
}

void main() {
  FramedReport().show();
}
output
--- 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:

dart
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}');
}
output
Bag: 1200.0

If 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:

dart
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));
}
output
Name: Sara, Marks: 92

6. 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:

dart
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}');
}
output
Ali: 85

Student(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:

dart
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);
}
output
Guest
ZAYAN

8. Named Parent Constructors: : super.named(...)

If the parent has a named constructor, the child calls it by name:

dart
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}');
}
output
Ali: 500.0
Sara: 0.0

Super 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:

dart
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();
}
output
1. Vehicle built
2. Car built
3. ElectricCar built

Like 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:

dart
class Account {
  Account.opened(this.owner);
  final String owner;
}

class SavingsAccount extends Account {
  SavingsAccount(String owner);
}
output
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:

dart
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);
}
output
Zayan

11. A Real-World Example

dart
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);
}
output
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

dart
class Animal {
  void speak() {
    print('Some sound');
  }
}

class Dog extends Animal {
  @override
  void speak() {
    super.speak();
    print('Woof');
  }
}

void main() {
  Dog().speak();
}
output
Some sound
Woof

Example 2 — Child Code First

dart
class Door {
  void open() {
    print('Door opened');
  }
}

class LockedDoor extends Door {
  @override
  void open() {
    print('Unlocking...');
    super.open();
  }
}

void main() {
  LockedDoor().open();
}
output
Unlocking...
Door opened

Example 3 — Getter Built on the Parent

dart
class Employee {
  double get salary => 50000;
}

class Manager extends Employee {
  @override
  double get salary => super.salary + 20000;
}

void main() {
  print(Manager().salary);
}
output
70000.0

Example 4 — super.toString()

dart
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));
}
output
Shape -> Circle(r=2.5)

Example 5 — Fixed Value with : super(...)

dart
class Pet {
  Pet(this.kind);
  final String kind;
}

class Cat extends Pet {
  Cat() : super('Cat');
}

void main() {
  print(Cat().kind);
}
output
Cat

Example 6 — Constructor Order

dart
class Base {
  Base() {
    print('Base first');
  }
}

class Child extends Base {
  Child() {
    print('Child second');
  }
}

void main() {
  Child();
}
output
Base first
Child second

Example 7 — Student Marks

dart
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();
}
output
Ali scored 88
Honors bonus: 93

Example 8 — Shopping Total

dart
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}');
}
output
Total: 2600.0

Example 9 — Bank Balance with a Fee

dart
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}');
}
output
Balance: 675.0

The child changes the argument, not the logic: the parent still does the subtraction.

Example 10 — Temperature Reading

dart
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();
}
output
Celsius: 25.0
Fahrenheit: 77.0

Common Beginner Mistakes

1. Forgetting super. inside the override

dart
class Dog extends Animal {
  @override
  void speak() {
    // speak(); // ✗ calls Dog.speak again, forever
    super.speak(); // ✓
    print('Woof');
  }
}
output
Unhandled exception:
Stack Overflow

Without super., the method calls itself until the program crashes.

2. Calling super(...) inside the constructor body

dart
class Dog extends Animal {
  // Dog(String name) {
  //   super(name); // ✗
  // }
  Dog(String name) : super(name); // ✓
}
output
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

dart
class Account {
  Account(this.owner);
  final String owner;
}

class Savings extends Account {
  // Savings(String owner); // ✗
  Savings(super.owner); // ✓
}
output
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)

dart
class Dog extends Animal {
  // Dog(super.name) : super(name); // ✗ the name is passed twice
  Dog(super.name); // ✓
}
output
Error: Positional super-initializer parameters cannot be used when the super initializer has positional arguments.

5. Putting initializers after super(...)

dart
class Dog extends Animal {
  // Dog(String name) : super(name), sound = 'Woof'; // ✗
  Dog(String name) : sound = 'Woof', super(name); // ✓
  final String sound;
}
output
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 super call 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() on super.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:

output
Zayan: 60 (bonus player)

Try it yourself before opening the solution.

Show solution
dart
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);
}
output
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

  1. Create Bird with move() printing Flying. Create Penguin that overrides move(), calls the parent's version, then prints ...but I prefer swimming.
  2. Create Greeter with a getter message returning 'Hello'. Override it in LoudGreeter to return the parent's message followed by '!!!'. Print Hello!!!.
  3. Create Fruit(this.name) and Apple() : super(...) so that print(Apple().name) prints Apple.

Application

  1. Create Person(this.name) with toString() returning 'Person: $name', and Teacher(name, subject) whose toString() prints Person: Sara, teaches Math.

  2. Add three classes A, B extends A, C extends B, each printing its letter in its constructor. What does C(); print?

  3. Item has a getter price returning 100. TaxedItem adds 15% using super.price. Print the taxed price.

  4. This program does not compile. Fix the Box constructor, without changing Container:

    dart
    class Container {
      Container.sized(this.size);
      final int size;
    }
    
    class Box extends Container {
      Box(int size);
    }

Challenge

  1. Account has balance = 500 and withdraw(double amount). SafeAccount overrides withdraw: if amount is more than balance it prints Not enough money, otherwise it calls the parent. Withdraw 800, then 200, and print Balance: 300.0.

  2. Give Account a named constructor Account.vip(this.owner) : balance = 5000;. Create VipSavings.vip(super.owner) : super.vip(); and print Ali: 5000.0.

  3. Predict the output, then explain it in one sentence:

    dart
    class Lamp {
      Lamp() {
        print('Lamp ready');
      }
    }
    
    class DeskLamp extends Lamp {
      DeskLamp() : super() {
        print('DeskLamp ready');
      }
    }
    
    void main() {
      DeskLamp();
      DeskLamp();
    }

Practice Answers

Question 1 — Solution

dart
class Bird {
  void move() {
    print('Flying');
  }
}

class Penguin extends Bird {
  @override
  void move() {
    super.move();
    print('...but I prefer swimming');
  }
}

void main() {
  Penguin().move();
}
output
Flying
...but I prefer swimming

Question 2 — Solution

dart
class Greeter {
  String get message => 'Hello';
}

class LoudGreeter extends Greeter {
  @override
  String get message => '${super.message}!!!';
}

void main() {
  print(LoudGreeter().message);
}
output
Hello!!!

Question 3 — Solution

dart
class Fruit {
  Fruit(this.name);
  final String name;
}

class Apple extends Fruit {
  Apple() : super('Apple');
}

void main() {
  print(Apple().name);
}
output
Apple

Question 4 — Solution

dart
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'));
}
output
Person: Sara, teaches Math

Question 5 — Solution

dart
class A {
  A() {
    print('A');
  }
}

class B extends A {
  B() {
    print('B');
  }
}

class C extends B {
  C() {
    print('C');
  }
}

void main() {
  C();
}
output
A
B
C

Each constructor calls its parent's first, so the oldest class prints first.

Question 6 — Solution

dart
class Item {
  double get price => 100;
}

class TaxedItem extends Item {
  @override
  double get price => super.price * 1.15;
}

void main() {
  print(TaxedItem().price);
}
output
114.99999999999999

The 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:

dart
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);
}
output
3

Question 8 — Solution

dart
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}');
}
output
Not enough money
Balance: 300.0

Question 9 — Solution

dart
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}');
}
output
Ali: 5000.0

Question 10 — Solution

output
Lamp ready
DeskLamp ready
Lamp ready
DeskLamp ready

Every new DeskLamp runs the parent constructor first and then its own body; : super() only writes out the call Dart would insert anyway.


Quick Revision

  1. What does super refer to inside a child class?
  2. What is the difference between overriding with and without super.method()?
  3. How do you reuse a parent getter's value in an override?
  4. What do Student(super.name) and Student(String name) : super(name) have in common?
  5. When must you use : super(...) instead of a super parameter?
  6. How do you call a parent's named constructor?
  7. Which constructor body runs first — parent or child?
  8. Why does a child fail to compile when the parent has only named constructors?

Key Takeaways

  • super is the parent's part of the current object.
  • super.method() lets an override extend the parent's behaviour instead of replacing it.
  • Place the super call before, after, or between your own code — the order is yours.
  • super works in getters and toString() 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.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.