You built a Book class last week, gave it a title, an author, and a page count, and wrote a clean constructor to fill it in. Then the catalog grew. Now you need an ebook that also tracks a file size, and an audiobook that tracks a narrator and a running time. You could copy the title, author, and pages fields into all three classes, but the day you rename one of them you are editing the same thing in three places, and one of those edits is the one you will forget. Dart inheritance is the fix. It lets one base class hold everything your books share, and lets the specialized classes add only the parts that make them different. By the end of this post you will extend a class with the extends keyword, pass values up to a parent constructor with super, override a method the right way, and know when inheritance is the wrong tool to reach for.
How Dart inheritance works: the extends keyword
Inheritance is one class saying “I am a more specific version of that other class, and I want everything it already has.” You write that with the extends keyword. The class you extend is the superclass or parent, and the new class is the subclass or child. Here is a plain Book, and an Ebook that extends it.
class Book {
String title;
String author;
int pages;
Book(this.title, this.author, this.pages);
String describe() => '$title by $author, $pages pages';
}
class Ebook extends Book {
double fileSizeMb;
Ebook(String title, String author, int pages, this.fileSizeMb)
: super(title, author, pages);
}
void main() {
var novel = Ebook('Dune', 'Frank Herbert', 412, 1.8);
print(novel.title); // Dune
print(novel.describe()); // Dune by Frank Herbert, 412 pages
print(novel.fileSizeMb); // 1.8
}
Look at what Ebook did not have to write. It never declared a title, an author, or a page count, and it never wrote a describe method, but novel.title and novel.describe() both work. That is inheritance doing its job: everything a Book has, an Ebook has too, for free. The only new thing Ebook adds is fileSizeMb. The one line worth slowing down on is : super(title, author, pages). When you build an Ebook, Dart has to build the Book part of it first, and the Book constructor is the only thing that knows how to set those three fields. So the Ebook constructor takes the four values it needs, keeps fileSizeMb for itself with the this. shorthand, and hands the other three up to the parent with super(...). Coming from Java or C#, note that Dart does not hand constructors down. A subclass writes its own constructor and calls super on purpose.

Overriding a method with @override
Inheriting the parent’s version is the default, but sometimes the child needs to behave differently. An Ebook’s description should probably mention that it is an ebook and how big the file is. You replace the inherited method by writing your own with the same name and marking it @override.
class Ebook extends Book {
double fileSizeMb;
Ebook(String title, String author, int pages, this.fileSizeMb)
: super(title, author, pages);
@override
String describe() => '${super.describe()} (ebook, ${fileSizeMb}MB)';
}
void main() {
var novel = Ebook('Dune', 'Frank Herbert', 412, 1.8);
print(novel.describe());
// Dune by Frank Herbert, 412 pages (ebook, 1.8MB)
}
Two things are happening here. First, @override is an annotation that tells Dart, and every human who reads this, that you meant to replace a method from the parent. It is technically optional, but leave it on. If you fat-finger the name and write descibe, the @override makes Dart warn you that there is nothing by that name to override, instead of quietly creating a brand new method that never runs. That warning has saved me and a lot of first-semester students an afternoon of confusion. Second, notice super.describe() inside the new method. You are not throwing away the parent’s work, you are calling it and adding to it. The parent builds “Dune by Frank Herbert, 412 pages,” and the child wraps that with the ebook detail. When you override, you get to decide whether to replace the parent’s behavior completely or build on top of it, and building on top with super is usually the cleaner choice.
super, and the error every beginner hits first
The super call trips people up because Dart is strict about it, and the error shows up before you have written any real logic. Say you add an Audiobook and forget the super call in its constructor.
class Audiobook extends Book {
String narrator;
// No super(...) call, and Book has no zero-argument constructor
Audiobook(this.narrator);
}
Dart lights up red with a message like “The superclass ‘Book’ doesn’t have a zero argument constructor.” Here is what it means. When you build an Audiobook, Dart still has to build the Book part first, and if you do not call super(...) yourself, Dart tries to call Book() with no arguments. But Book’s only constructor demands a title, an author, and a page count, so Book() with nothing in it does not exist. Dart is not being fussy for its own sake. It is refusing to build half a Book with three empty fields. The fix is to take the values the parent needs and pass them up.
class Audiobook extends Book {
String narrator;
int durationMinutes;
Audiobook(String title, String author, int pages, this.narrator,
this.durationMinutes)
: super(title, author, pages);
@override
String describe() =>
'${super.describe()}, read by $narrator (${durationMinutes} min)';
}
void main() {
var ab = Audiobook('Dune', 'Frank Herbert', 412, 'Scott Brick', 1260);
print(ab.describe());
// Dune by Frank Herbert, 412 pages, read by Scott Brick (1260 min)
}
Now the Audiobook constructor collects everything, keeps narrator and durationMinutes for itself, and sends the shared three up to Book. The rule to keep in your head is short: if the parent has no no-argument constructor, the child must call super(...) and pass what the parent asked for. Get that one habit down and most inheritance errors disappear.
When inheritance is the wrong tool
Inheritance is satisfying, which is exactly why beginners overuse it. The test is a plain-English one. Use inheritance when the child truly is a kind of the parent. An Ebook is a Book, an Audiobook is a Book, so extending Book is honest. But when one thing merely has another thing, you want a field, not a parent. A Car is not a kind of Engine, so a Car should not extend Engine. A Car has an Engine.
class Engine {
void start() => print('Engine started');
}
class Car {
final Engine engine = Engine(); // a Car HAS an Engine
void drive() {
engine.start();
print('Driving');
}
}
Run that and Car().drive() prints “Engine started” then “Driving.” The Car holds an Engine as a field and uses it, without pretending to be one. This is called composition, and reaching for it instead of inheritance is one of the quieter marks of code that stays easy to change. In CIS225 the “is a versus has a” question is usually where a design either clicks or falls apart, and it comes up long before anyone writes a line of code. When you catch yourself extending a class just to borrow one of its methods, stop and ask whether the child really is that thing. If the answer is no, make it a field.

Your next step
Here is the short version worth keeping. The extends keyword gives a subclass everything the parent has, so shared data and behavior live in one place. A subclass writes its own constructor and passes the parent’s values up with super(...), and Dart will stop you if you forget. Mark replaced methods with @override so a typo cannot turn into a silent bug, and call super.method() when you want to build on the parent instead of throwing its work away. And reach for inheritance only when the child genuinely is a kind of the parent, not just when it is convenient.
The way to make this stick is to build one. Write a Shape class with a method area() that returns zero, then write Circle and Rectangle classes that extend it. Give each one the fields it needs, a constructor that calls super, and an @override of area() that does the real math. Print the area of one of each and watch the right version run. If constructors still feel shaky, the Dart functions post is worth a second read, since a constructor is really just a function with a special job, and the Dart null safety post explains why your subclass fields have to be set the way they are. For the full reference, the Dart language guide on extending classes lists every wrinkle Dart offers. Open your editor and give one class a family.

