Watch the video (12 min)
On this page24 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is Inheritance?
  5. 2. Inherited Fields Are Real Fields
  6. 3. Adding New Members in the Subclass
  7. 4. The Subclass Constructor Must Call the Superclass Constructor
  8. 5. Super Parameters: super.name
  9. 6. The Superclass Is Built First
  10. 7. The Is-A Test
  11. 8. A Chain of Classes
  12. 9. Single Inheritance Only
  13. 10. is Checks in a Hierarchy
  14. 11. Object Is the Root of Every Class
  15. 12. A Real-World Example
  16. Code Examples
  17. Common Beginner Mistakes
  18. Best Practices
  19. Mini Challenge
  20. Practice Questions
  21. Practice Answers
  22. Quick Revision
  23. Key Takeaways
  24. What's Next?

Lecture 59 — Inheritance

Category10 — Advanced OOP
Lecture059 of 140
Estimated video length12 min
TargetDart 3.12.2
LevelIntermediate

Two classes that share most of their fields used to mean writing everything twice. With extends, a new class starts from an existing one and only adds what is different.

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–058:

  • creating classes with fields and methods, and objects from them
  • constructors, including this.name parameters and named parameters with required
  • this, getters and setters, and private members
  • the type test is and the type Object (Lectures 010–011)

Connection With Previous Lectures

In Category 9 we wrote one class at a time. Now suppose a school program needs a Person and a Student:

dart
class Person {
  String name;
  Person(this.name);
  void greet() => print('Hi, I am $name');
}

class Student {
  String name;
  String school;
  Student(this.name, this.school);
  void greet() => print('Hi, I am $name');
}

Student repeats name and greet() word for word. If we fix a bug in one greet(), we must remember to fix the other. And Dart has no idea that a student is a person at all — to Dart these are two unrelated classes. Inheritance fixes both problems.


Learning Objectives

By the end of this lecture you will be able to:

  • Understand what inheritance is and the words superclass and subclass
  • Create a subclass with extends
  • Use fields and methods a subclass inherits from its superclass
  • Add new fields and methods that only the subclass has
  • Forward constructor values to the superclass with super parameters (super.name)
  • Apply the is-a test to decide when inheritance fits
  • Check types in a class hierarchy with is
  • Explain why every class ultimately extends Object

1. What Is Inheritance?

Inheritance lets one class take over all the fields and methods of another class, then add its own. The existing class is the superclass (or parent); the new one is the subclass (or child).

Analogy — every student is a person. Whatever is true of every person (they have a name, they can say hello) is automatically true of every student. A student just has a few extra things, such as a school.

dart
class Person {
  String name = 'Ali';
  void greet() => print('Hi, I am $name');
}

class Student extends Person {}

void main() {
  Student s = Student();
  s.greet();
}
output
Hi, I am Ali

The syntax is class Subclass extends Superclass { … }. Student has an empty body, yet s.greet() works: the method was inherited from Person.


2. Inherited Fields Are Real Fields

You read and change an inherited field on the subclass object exactly as if it had been written there.

dart
class Person {
  String name = 'Unknown';
  int age = 0;
}

class Student extends Person {}

void main() {
  Student s = Student();
  s.name = 'Sara';
  s.age = 18;
  print('${s.name} is ${s.age}');
}
output
Sara is 18

Each Student object gets its own name and age.


3. Adding New Members in the Subclass

A subclass can add fields and methods the superclass knows nothing about. Its methods can use inherited fields freely.

dart
class Person {
  String name = 'Zayan';
  void greet() => print('Hi, I am $name');
}

class Student extends Person {
  String school = 'City School';
  void study() => print('$name is studying at $school');
}

void main() {
  Student s = Student();
  s.greet();
  s.study();
}
output
Hi, I am Zayan
Zayan is studying at City School

Inheritance only goes downwards. A plain Person does not get school:

dart
// ✗ does not compile
// Person p = Person();
// print(p.school);
output
Error: The getter 'school' isn't defined for the type 'Person'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'school'.
  print(p.school);
          ^^^^^^

4. The Subclass Constructor Must Call the Superclass Constructor

When the superclass has a constructor with required parameters, the subclass must pass those values up. Forgetting fails:

dart
// class Person {
//   String name;
//   Person(this.name);
// }
//
// class Student extends Person {
//   String school;
//   Student(this.school); // ✗ who gives Person its name?
// }
output
Error: The implicitly called unnamed constructor from 'Person' has required parameters.
Try adding an explicit super initializer with the required arguments.
  Student(this.school);
  ^

Every Student is also a Person, so the Person part has to be built too — and Person cannot be built without a name.


5. Super Parameters: super.name

Since Dart 2.17, the fix is a super parameter. Where this.school stores a value in the subclass's own field, super.name hands the value to the superclass constructor.

dart
class Person {
  String name;
  Person(this.name);
}

class Student extends Person {
  String school;
  Student(super.name, this.school);
}

void main() {
  Student s = Student('Ali', 'City School');
  print('${s.name} attends ${s.school}');
}
output
Ali attends City School

The same works with named parameters. Write required super.name and the superclass must take name as a named parameter too:

dart
class Person {
  String name;
  Person({required this.name});
}

class Student extends Person {
  String school;
  Student({required super.name, required this.school});
}

void main() {
  Student s = Student(name: 'Sara', school: 'Green Valley');
  print('${s.name} attends ${s.school}');
}
output
Sara attends Green Valley

Calling the superclass constructor by hand, and the super keyword in general, are the topic of Lecture 061 — super.


6. The Superclass Is Built First

When you create a subclass object, the superclass constructor runs before the subclass constructor's body.

dart
class Person {
  String name;
  Person(this.name) {
    print('Person part ready: $name');
  }
}

class Student extends Person {
  String school;
  Student(super.name, this.school) {
    print('Student part ready: $school');
  }
}

void main() {
  Student('Zayan', 'City School');
}
output
Person part ready: Zayan
Student part ready: City School

This order is why the subclass body can safely use name: the Person part already exists.


7. The Is-A Test

Use inheritance only when the sentence "a B is an A" is true. "A student is a person", "a dog is an animal", "a savings account is a bank account" — all good.

"A car is an engine" is false. A car has an engine, so Engine should be a field inside Car, not its superclass. We will compare these two designs in Lecture 066 — Composition vs Inheritance.


8. A Chain of Classes

A subclass can itself be extended. Each level inherits everything above it.

dart
class Person {
  String name;
  Person(this.name);
}

class Student extends Person {
  String school;
  Student(super.name, this.school);
}

class GraduateStudent extends Student {
  String thesis;
  GraduateStudent(super.name, super.school, this.thesis);
}

void main() {
  var g = GraduateStudent('Sara', 'State University', 'Clean Water');
  print('${g.name}, ${g.school}, thesis: ${g.thesis}');
}
output
Sara, State University, thesis: Clean Water

GraduateStudent passes name and school up to Student, which passes name up to Person. This family of related classes is called a class hierarchy.


9. Single Inheritance Only

A Dart class can have exactly one superclass. Listing two is rejected:

dart
// class Person {}
// class Worker {}
// class Student extends Person, Worker {} // ✗
output
Error: Each class definition can have at most one extends clause.
Try choosing one superclass and define your class to implement (or mix in) the others.
class Student extends Person, Worker {}
                            ^

The alternatives it mentions come in Lecture 063 — Interfaces and Lecture 064 — Mixins.


10. is Checks in a Hierarchy

Because a student is a person, is Person is true for a Student object. The reverse is not true. The variables below are typed Object, so Dart must look at the actual values.

dart
class Person {
  String name;
  Person(this.name);
}

class Student extends Person {
  String school;
  Student(super.name, this.school);
}

void main() {
  Object s = Student('Ali', 'City School');
  Object p = Person('Sara');
  print(s is Student);
  print(s is Person);
  print(p is Student);
}
output
true
true
false

For the same reason, a Student can be stored in a Person variable. Through that variable Dart only allows Person members — until an is check proves more:

dart
class Person {
  String name;
  Person(this.name);
}

class Student extends Person {
  String school;
  Student(super.name, this.school);
}

void main() {
  Person p = Student('Zayan', 'City School');
  print(p.name);
  if (p is Student) {
    print(p.school); // allowed: p is known to be a Student here
  }
}
output
Zayan
City School

11. Object Is the Root of Every Class

A class with no extends still has a superclass: Object. Writing class Person {} means exactly class Person extends Object {}. That is why every object passes is Object and answers to toString() and runtimeType — they are inherited from Object.

dart
class Person {}

class Student extends Person {}

void main() {
  Student s = Student();
  print(s.runtimeType);
  print(s.toString());
}
output
Student
Instance of 'Student'

Instance of 'Student' is Object's default text. Replacing an inherited method with your own version is the next lecture, Lecture 060 — Method Overriding.


12. A Real-World Example

dart
class Account {
  String owner;
  double balance;
  Account(this.owner, this.balance);

  void deposit(double amount) => balance = balance + amount;
  void show() => print('$owner: $balance');
}

class SavingsAccount extends Account {
  double rate;
  SavingsAccount(super.owner, super.balance, this.rate);

  void addInterest() => deposit(balance * rate);
}

void main() {
  List<Account> accounts = [
    Account('Ali', 1000),
    SavingsAccount('Sara', 2000, 0.05),
  ];

  for (var account in accounts) {
    account.deposit(500);
    if (account is SavingsAccount) {
      account.addInterest();
    }
    account.show();
  }
}
output
Ali: 1500.0
Sara: 2625.0

SavingsAccount inherits four members, adds two, and forwards two values with super parameters. One List<Account> holds both kinds, and is decides who earns interest.


Code Examples

Example 1 — An Empty Subclass

dart
class Vehicle {
  int wheels = 4;
  void describe() => print('A vehicle with $wheels wheels');
}

class Car extends Vehicle {}

void main() {
  Car car = Car();
  car.describe();
}
output
A vehicle with 4 wheels

Example 2 — Inherited Plus New

dart
class Animal {
  String name = 'Rex';
  void eat() => print('$name is eating');
}

class Dog extends Animal {
  void bark() => print('$name says Woof!');
}

void main() {
  Dog d = Dog();
  d.eat();
  d.bark();
}
output
Rex is eating
Rex says Woof!

Example 3 — Changing an Inherited Field

dart
class Animal {
  String name = 'Unknown';
}

class Cat extends Animal {}

void main() {
  Cat c = Cat();
  c.name = 'Milo';
  print(c.name);
}
output
Milo

Example 4 — Positional Super Parameter

dart
class Shape {
  String color;
  Shape(this.color);
}

class Square extends Shape {
  double side;
  Square(super.color, this.side);
  double area() => side * side;
}

void main() {
  Square sq = Square('red', 3);
  print('${sq.color} square, area ${sq.area()}');
}
output
red square, area 9.0

Example 5 — Named Super Parameters

dart
class Product {
  String title;
  double price;
  Product({required this.title, required this.price});
}

class Book extends Product {
  String author;
  Book({required super.title, required super.price, required this.author});
}

void main() {
  Book b = Book(title: 'Dart Basics', price: 25, author: 'Sara');
  print('${b.title} by ${b.author}: ${b.price}');
}
output
Dart Basics by Sara: 25.0

Example 6 — Type Checks

dart
class Animal {}

class Dog extends Animal {}

void main() {
  Object a = Animal();
  Object d = Dog();
  print(d is Animal);
  print(a is Dog);
  print(a is Animal);
}
output
true
false
true

Example 7 — Student Marks

dart
class Person {
  String name;
  Person(this.name);
}

class Student extends Person {
  List<int> marks;
  Student(super.name, this.marks);

  int total() => marks.fold(0, (sum, m) => sum + m);
}

void main() {
  Student s = Student('Ali', [80, 75, 85]);
  print('${s.name}: ${s.total()}');
}
output
Ali: 240

How it works

  1. super.name sends 'Ali' to Person; this.marks stores the list in Student.
  2. total() exists only on Student and adds the marks with fold (Lecture 044).
  3. s.name is inherited; s.total() is the subclass's own method.

Example 8 — Shopping Total with a Discounted Product

dart
class Product {
  String title;
  double price;
  Product(this.title, this.price);
}

class DiscountedProduct extends Product {
  double discount;
  DiscountedProduct(super.title, super.price, this.discount);

  double finalPrice() => price - price * discount;
}

void main() {
  Product pen = Product('Pen', 50);
  DiscountedProduct bag = DiscountedProduct('Bag', 1200, 0.25);
  double total = pen.price + bag.finalPrice();
  print('Total: $total');
}
output
Total: 950.0

How it works

  1. bag gets title and price from Product and adds discount.
  2. finalPrice() reads the inherited price: 1200 - 300 = 900.
  3. The pen adds 50, giving 950.0.

Example 9 — Bank Balance

dart
class Account {
  String owner;
  double balance;
  Account(this.owner, this.balance);
  void withdraw(double amount) => balance = balance - amount;
}

class SavingsAccount extends Account {
  double rate;
  SavingsAccount(super.owner, super.balance, this.rate);
  void addInterest() => balance = balance + balance * rate;
}

void main() {
  SavingsAccount acc = SavingsAccount('Zayan', 1000, 0.10);
  acc.withdraw(200);
  acc.addInterest();
  print('${acc.owner}: ${acc.balance}');
}
output
Zayan: 880.0

How it works

  1. withdraw() is inherited and takes the balance from 1000 to 800.
  2. addInterest() is new and adds 10%: 800 + 80 = 880.
  3. Both methods change the same balance field.

Example 10 — Temperature Sensors

dart
class Sensor {
  String location;
  Sensor(this.location);
}

class Thermometer extends Sensor {
  double celsius;
  Thermometer(super.location, this.celsius);
  double fahrenheit() => celsius * 9 / 5 + 32;
}

void main() {
  List<Sensor> sensors = [Sensor('Door'), Thermometer('Kitchen', 25)];
  for (var s in sensors) {
    if (s is Thermometer) {
      print('${s.location}: ${s.fahrenheit()} F');
    } else {
      print('${s.location}: no reading');
    }
  }
}
output
Door: no reading
Kitchen: 77.0 F

How it works

  1. The list's type is List<Sensor>, so it may hold a Thermometer too.
  2. Inside if (s is Thermometer), Dart allows fahrenheit().
  3. The plain Sensor falls into else.

Common Beginner Mistakes

1. Not passing values to the superclass constructor

dart
// class Student extends Person {
//   String school;
//   Student(this.school);
// }
output
Error: The implicitly called unnamed constructor from 'Person' has required parameters.

Forward the superclass's values with super.name.

2. Using this. for an inherited field

dart
// class Student extends Person {
//   String school;
//   Student(this.name, this.school); // ✗ name lives in Person
// }
output
Error: 'name' isn't an instance field of this class.

this. only works for fields declared in the class itself. Use super.name.

3. Mixing positional and named styles

dart
// class Person {
//   String name;
//   Person({required this.name}); // named
// }
//
// class Student extends Person {
//   String school;
//   Student(super.name, this.school); // ✗ positional
// }
output
Error: The super constructor has no corresponding positional parameter.

If the superclass takes name by name, write required super.name inside { }.

4. Expecting the superclass to have subclass members

dart
// Person p = Student('Ali', 'City School');
// print(p.school); // ✗
output
Error: The getter 'school' isn't defined for the type 'Person'.

Check if (p is Student) first, or store it in a Student variable.

5. Putting a superclass object in a subclass variable

dart
// Student s = Person('Ali'); // ✗
output
Error: A value of type 'Person' can't be assigned to a variable of type 'Student'.

Every student is a person, but not every person is a student.

6. Inheriting where "has-a" fits

class Car extends Engine compiles but makes no sense. Use a field instead.


Best Practices

  • Apply the is-a test before writing extends.
  • Put shared fields and methods in the superclass once; put only differences in subclasses.
  • Prefer super parameters (super.name) over repeating a field in the subclass.
  • Keep hierarchies shallow — two or three levels are usually enough.
  • Use is checks sparingly; reach for them only when one kind needs extra handling.

Mini Challenge

Create a class Employee with a name and a salary (double), set through a positional constructor, and a method show() that prints name: salary. Create a subclass Manager with an extra bonus field and a method totalPay() returning salary plus bonus. Build a manager named 'Sara' with salary 5000 and bonus 1500, call show(), then print the total pay.

Your program should print exactly:

output
Sara: 5000.0
Total pay: 6500.0

Try it yourself before opening the solution.

Show solution
dart
class Employee {
  String name;
  double salary;
  Employee(this.name, this.salary);
  void show() => print('$name: $salary');
}

class Manager extends Employee {
  double bonus;
  Manager(super.name, super.salary, this.bonus);
  double totalPay() => salary + bonus;
}

void main() {
  Manager m = Manager('Sara', 5000, 1500);
  m.show();
  print('Total pay: ${m.totalPay()}');
}
output
Sara: 5000.0
Total pay: 6500.0

How it works

  1. Manager forwards name and salary with super parameters and stores bonus itself.
  2. show() is inherited unchanged.
  3. totalPay() is new and reads the inherited salary.

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Create a class Bird with a method fly() that prints Flying. Create an empty subclass Parrot, make a Parrot, and call fly().
  2. Give Animal a field name = 'Leo'. Create Lion extends Animal with a method roar() that prints Leo roars, using the inherited field.
  3. With class Animal {}, class Dog extends Animal {}, Object a = Animal(); and Object d = Dog();, print the results of d is Animal, a is Dog, and d is Dog.

Application

  1. class Person { String name; Person(this.name); } is given. Write Teacher extends Person with a subject field and a constructor using super.name. Print Ali teaches Math.
  2. Rewrite question 4 with named parameters in both classes, creating Teacher(name: 'Sara', subject: 'Science'), and print Sara teaches Science.
  3. Give Person a constructor body that prints Person built and Teacher one that prints Teacher built. Create one Teacher and show the order.
  4. Create Product(title, price) and Food extends Product with an expiry string. Print Milk: 120.0 (expires Friday).
  5. Store a Teacher('Zayan', 'Art') in a Person variable. Print the name, then use an is check to print the subject.

Challenge

  1. Build Vehicle(wheels) → Car(wheels, brand) → ElectricCar(wheels, brand, range) with super parameters. Create ElectricCar(4, 'Volt', 400) and print Volt: 4 wheels, 400 km.
  2. Using Account(owner, balance) with deposit() and SavingsAccount(owner, balance, rate) with addInterest(), put Account('Ali', 500) and SavingsAccount('Sara', 1000, 0.10) in one List<Account>. Deposit 100 into every account, add interest only to savings accounts, and print each owner: balance.

Practice Answers

Question 1 — Solution

dart
class Bird {
  void fly() => print('Flying');
}

class Parrot extends Bird {}

void main() {
  Parrot p = Parrot();
  p.fly();
}
output
Flying

Question 2 — Solution

dart
class Animal {
  String name = 'Leo';
}

class Lion extends Animal {
  void roar() => print('$name roars');
}

void main() {
  Lion l = Lion();
  l.roar();
}
output
Leo roars

Question 3 — Solution

dart
class Animal {}

class Dog extends Animal {}

void main() {
  Object a = Animal();
  Object d = Dog();
  print(d is Animal);
  print(a is Dog);
  print(d is Dog);
}
output
true
false
true

Question 4 — Solution

dart
class Person {
  String name;
  Person(this.name);
}

class Teacher extends Person {
  String subject;
  Teacher(super.name, this.subject);
}

void main() {
  Teacher t = Teacher('Ali', 'Math');
  print('${t.name} teaches ${t.subject}');
}
output
Ali teaches Math

Question 5 — Solution

dart
class Person {
  String name;
  Person({required this.name});
}

class Teacher extends Person {
  String subject;
  Teacher({required super.name, required this.subject});
}

void main() {
  Teacher t = Teacher(name: 'Sara', subject: 'Science');
  print('${t.name} teaches ${t.subject}');
}
output
Sara teaches Science

Question 6 — Solution

dart
class Person {
  String name;
  Person(this.name) {
    print('Person built');
  }
}

class Teacher extends Person {
  String subject;
  Teacher(super.name, this.subject) {
    print('Teacher built');
  }
}

void main() {
  Teacher('Ali', 'Math');
}
output
Person built
Teacher built

The superclass part is always built first.

Question 7 — Solution

dart
class Product {
  String title;
  double price;
  Product(this.title, this.price);
}

class Food extends Product {
  String expiry;
  Food(super.title, super.price, this.expiry);
}

void main() {
  Food milk = Food('Milk', 120, 'Friday');
  print('${milk.title}: ${milk.price} (expires ${milk.expiry})');
}
output
Milk: 120.0 (expires Friday)

Question 8 — Solution

dart
class Person {
  String name;
  Person(this.name);
}

class Teacher extends Person {
  String subject;
  Teacher(super.name, this.subject);
}

void main() {
  Person p = Teacher('Zayan', 'Art');
  print(p.name);
  if (p is Teacher) {
    print(p.subject);
  }
}
output
Zayan
Art

Question 9 — Solution

dart
class Vehicle {
  int wheels;
  Vehicle(this.wheels);
}

class Car extends Vehicle {
  String brand;
  Car(super.wheels, this.brand);
}

class ElectricCar extends Car {
  int range;
  ElectricCar(super.wheels, super.brand, this.range);
}

void main() {
  ElectricCar e = ElectricCar(4, 'Volt', 400);
  print('${e.brand}: ${e.wheels} wheels, ${e.range} km');
}
output
Volt: 4 wheels, 400 km

Question 10 — Solution

dart
class Account {
  String owner;
  double balance;
  Account(this.owner, this.balance);
  void deposit(double amount) => balance = balance + amount;
}

class SavingsAccount extends Account {
  double rate;
  SavingsAccount(super.owner, super.balance, this.rate);
  void addInterest() => deposit(balance * rate);
}

void main() {
  List<Account> accounts = [
    Account('Ali', 500),
    SavingsAccount('Sara', 1000, 0.10),
  ];
  for (var a in accounts) {
    a.deposit(100);
    if (a is SavingsAccount) {
      a.addInterest();
    }
    print('${a.owner}: ${a.balance}');
  }
}
output
Ali: 600.0
Sara: 1210.0

Sara's account reaches 1100 after the deposit, and 10% interest adds 110.


Quick Revision

  1. What do a superclass and a subclass mean?
  2. Which keyword creates a subclass?
  3. Can a superclass use a field declared only in its subclass?
  4. What does super.name do in a constructor?
  5. Which constructor body runs first: the superclass's or the subclass's?
  6. What is the is-a test?
  7. How many classes can one class extend?
  8. What is the superclass of a class written with no extends?

Key Takeaways

  • class B extends A makes B a subclass that inherits every field and method of A.
  • A subclass can add its own fields and methods; the superclass never gains them.
  • If the superclass constructor needs values, the subclass must forward them — super parameters (super.name) do this in one word.
  • The superclass part of an object is built before the subclass part.
  • Use inheritance only when "a B is an A" is true; "has-a" calls for a field.
  • A Dart class has exactly one superclass, and chains of classes form a hierarchy.
  • A subclass object passes is checks for every class above it, and an is check unlocks subclass members.
  • Every class extends Object in the end, which is where toString() and runtimeType come from.

What's Next?

Lecture 60 — Method Overriding. A subclass can now add new methods, but it still has to accept every inherited method exactly as written — toString() still says Instance of 'Student'. Next we will learn how a subclass replaces an inherited method with its own version, and what @override tells Dart.


Video lecture

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