
An abstract class is a class that cannot be instantiated directly. Its primary purpose is to serve as a blueprint for other classes. You define an abstract class using the abstract
keyword.
Think of it like a template or a contract. It defines a set of methods and properties that any concrete class (a class that can be instantiated) that extends or implements it must follow.
Key Characteristics of Abstract Classes
- Cannot be Instantiated: You can’t create an object of an abstract class. For example,
var myObject = MyAbstractClass();
would result in an error. - Can have Abstract Methods: An abstract method is a method declared in an abstract class without a body. It’s simply a signature, ending with a semicolon. Any concrete class that extends the abstract class must provide an implementation (a body) for all of its abstract methods.
- Can have Concrete Methods: Unlike interfaces in some other languages, a Dart abstract class can have regular, non-abstract methods with a body. These methods can be shared by all subclasses.
- Can have Instance and Static Variables: Abstract classes can contain both instance and static variables.
- Inheritance is Key: A class inherits from an abstract class using the
extends
keyword. This is the only way to use the functionality defined in the abstract class.
Why Use Abstract Classes?
Abstract classes are powerful tools for promoting code reusability and establishing a clear hierarchy within your application. They are especially useful in the following scenarios:
- Shared Behavior and Common Blueprint: When you have a group of related classes that share some common functionality but also have unique behaviors. For example, a
Shape
abstract class could define a common method likecalculateArea()
, but each subclass (Circle
,Square
,Triangle
) would implement this method differently. - Enforcing a Contract: They force subclasses to implement certain methods, ensuring that a specific set of functionalities is always present in any class that inherits from the abstract class. This makes your code more predictable and easier to maintain.
- Preventing Direct Instantiation: They prevent developers from creating a generic, incomplete object. For instance, creating an object of a
Vehicle
abstract class doesn’t make sense, but creating aCar
orMotorcycle
object does.
Abstract Class vs. Interface
While Dart doesn’t have a dedicated interface
keyword, any class can be an interface. When a class implements another class, it must provide a complete implementation for all of its public methods and properties. The key difference lies in the relationship:
Extends
(Abstract Class): A class can onlyextend
one other class, inheriting both the implementation and the contract.Implements
(Interface): A class canimplement
multiple classes, but it only inherits the contract (the method signatures) and must provide its own implementation for every method.
Abstract classes are generally preferred when you want to provide a default implementation that subclasses can inherit and optionally override. Interfaces are better when you simply want to define a contract without any shared implementation.
Example: A Vehicle
Abstract Class

Let’s consider a scenario where we’re building a system for a fleet of vehicles. All vehicles have some common characteristics, but they also have unique behaviors. This is a perfect use case for an abstract class.
Dart
// Define the abstract class
abstract class Vehicle {
// Abstract method - subclasses MUST implement this
void accelerate();
// A concrete method with a default implementation
void brake() {
print('Applying brakes.');
}
// An abstract getter for a property
int get numberOfWheels;
}
// Concrete class extending the abstract class
class Car extends Vehicle {
// Implementing the abstract accelerate method
@override
void accelerate() {
print('The car is accelerating.');
}
// Implementing the abstract getter
@override
int get numberOfWheels => 4;
}
// Another concrete class
class Bicycle extends Vehicle {
// Implementing the abstract accelerate method
@override
void accelerate() {
print('The bicycle is pedaling faster.');
}
// We can override the concrete brake method if needed
@override
void brake() {
print('Squeezing bicycle brake levers.');
}
// Implementing the abstract getter
@override
int get numberOfWheels => 2;
}
void main() {
// You cannot instantiate an abstract class directly
// var myVehicle = Vehicle(); // This will cause an error
var myCar = Car();
myCar.accelerate(); // The car is accelerating.
myCar.brake(); // Applying brakes. (Uses the inherited implementation)
print('A car has ${myCar.numberOfWheels} wheels.'); // A car has 4 wheels.
print('-------------------');
var myBicycle = Bicycle();
myBicycle.accelerate(); // The bicycle is pedaling faster.
myBicycle.brake(); // Squeezing bicycle brake levers. (Uses the overridden implementation)
print('A bicycle has ${myBicycle.numberOfWheels} wheels.'); // A bicycle has 2 wheels.
}
Explanation of the Example

- We create an
abstract class Vehicle
. - Inside
Vehicle
, we declare an abstract methodaccelerate()
. Notice it has no body. This forces any subclass to provide its own version of this method. - We also include a concrete method
brake()
with a default implementation. Subclasses can use this as-is or override it. - We define a concrete class
Car
thatextends
Vehicle
. It must provide an implementation foraccelerate()
. - We define another concrete class
Bicycle
, which alsoextends
Vehicle
. It provides its own implementation foraccelerate()
and also overrides the defaultbrake()
implementation. - In the
main
function, we demonstrate that we can create objects ofCar
andBicycle
but not of theVehicle
abstract class itself. We also show how theaccelerate()
method behaves differently for each subclass, while thebrake()
method shows how a default implementation can be inherited or overridden.