Dart is an object-oriented language with single inheritance . Every class implicitly extends Object. Unlike Java, Dart has no interfaces as a separate construct — classes themselves serve as Interfaces.
// Fields (instance variables)
int _age; // Private (by convention, prefix with _)
// Factory constructor (returns an instance, does not always create new)
factory User.fromJson(Map<String, dynamic> json) {
name: json['name'] as String,
email: json['email'] as String,
if (value < 0) throw ArgumentError('Age cannot be negative');
String greet() => 'Hello, I \' m $ name ';
Dart provides several constructor patterns:
// Generative constructor (default)
Point . origin () : x = 0 , y = 0 ;
// Redirecting constructor
Point . alongX ( double x) : this (x, 0 );
// Constant constructor (compile-time constant)
const Point . zero () : x = 0 , y = 0 ;
factory Point . polar ( double r, double theta) {
return Point (r * cos (theta), r * sin (theta));
flowchart TD
A["Generative<br/>Point(this.x, this.y)"] --> B["Named<br/>Point.origin()"]
A --> C["Redirecting<br/>Point.alongX(x)"]
A --> D["Constant<br/>const Point.zero()"]
A --> E["Factory<br/>factory Point.polar(r, θ)"]
B -.->|"delegates to"| A
C -.->|"redirects to"| A
D -.->|"compile-time"| A
E -.->|"returns"| A
style E fill:#fff3e0
style D fill:#e8f5e9 The initializer list runs before the constructor body and can be used to:
Initialize final fields. Assert preconditions. Call the superclass constructor. // area is computed from width and height before the body runs
Rectangle ( double width, double height)
assert (width > 0 , 'Width must be positive' ),
assert (height > 0 , 'Height must be positive' );
More efficient — they initialize fields directly, while the constructor body runs after all fields Have been initialized (to their default values first).Dart supports single inheritance with the extends keyword. Multiple inheritance of Implementation is not supported, but a class can implement multiple interfaces.
void speak () => print ( ' $ name makes a sound' );
class Dog extends Animal {
Dog ( String name, this .breed) : super (name);
void speak () => print ( ' $ name ( $ breed ) barks' );
// Call superclass method
super . speak (); // Animal's speak
print ( '...then wags tail' );
Abstract classes define interfaces that cannot be instantiated directly. They may or may not Contain implementation:
// Abstract method (no implementation — subclasses must override)
// Concrete method (subclasses inherit)
void describe () => print ( 'Area: ${ area ()} ' );
class Circle extends Shape {
double area () => pi * radius * radius;
class Square extends Shape {
double area () => side * side;
In Dart, every class implicitly defines an interface . Any class can implement another class’s Interface without inheriting its implementation:
// A class defines both an implementation and an interface
String greet ( String name) => 'Hello, $ name ' ;
// 'implement' requires implementing ALL members (no inheritance)
class FormalGreeter implements Greeter {
String greet ( String name) => 'Good day, $ name ' ;
// A class can implement multiple interfaces
class LoudGreeter implements Greeter , Comparable < LoudGreeter > {
String greet ( String name) => 'HELLO, ${ name . toUpperCase ()} !' ;
int compareTo ( LoudGreeter other) => 0 ;
flowchart LR
subgraph "extends (inheritance)"
A["Animal"] --> B["Dog"]
B --> C["methods inherited<br/>+ can override"]
end
subgraph "implements (interface)"
D["Greeter (interface)"] --> E["FormalGreeter"]
E --> F["must implement ALL<br/>no code inherited"]
end
subgraph "with (mixin)"
G["class Dog"] --> H["mixin Serializable"]
H --> I["methods injected<br/>no inheritance chain"]
end Mixins provide a way to inject reusable code into classes without using inheritance. A mixin is Declared with the mixin keyword and applied with with:
// Can have fields, methods, but no constructor
Map < String , dynamic > toJson ();
// Can call 'super' if the class using the mixin has the method
void logSerialization () {
print ( 'Serialized: ${ toJson ()} ' );
// A class can use multiple mixins
class User with Serializable , Validatable {
User ( this .name, this .age);
Map < String , dynamic > toJson () => { 'name' : name, 'age' : age};
bool validate () => name.isNotEmpty && age >= 0 ;
// Mixins can have type constraints (must extend/implement a type)
mixin Persistable on Serializable {
logSerialization (); // Can call methods from Serializable
Feature extendsimplementswith (mixin)Inherit implementation Yes No Yes Require method override Optional All methods Optional Multiple No (single) Yes Yes Can have constructors Yes No (if mixin) No Use case Is-a relationship Has-capability contract Code reuse across classes
same hash code. Use `Object.hash()` or `Object.hashAll()` for combining multiple values.Extensions add methods to existing types without modifying the original class :
extension StringX on String {
bool get isBlank => trim ().isEmpty;
String get capitalized =>
isEmpty ? '' : " ${ this [ 0 ]. toUpperCase ()}${ substring ( 1 )} '';
print(" hello '.isBlank); // false
print(' hello '.capitalized); // Hello
print(' hello world '.capitalized); // Hello world
Extensions can also have generic type parameters:
extension ListX < T > on List < T > {
T ? firstWhereOrNull ( bool Function ( T ) test) {
for ( final item in this ) {
if ( test (item)) return item;
Syntactic sugar for static function calls. This means they cannot be used polymorphically (a `dynamic` variable won't have access to extension methods).Classes are blueprints, mixins are plug-and-play upgrades: In Dart, extends gives you a single family tree — one parent, inherited traits. implements is a contract: “I promise to have these methods, but I’ll write them myself.” Mixins are the third option: grab-bag capabilities you snap onto any class without inheritance, like adding a USB device to a computer. Every class implicitly defines an interface, so any class can serve as a contract for another — no separate interface keyword needed.
Why it matters: Single inheritance keeps the hierarchy simple, mixins prevent the “diamond problem,” and implicit interfaces mean you can write test doubles for any class without designing for testability upfront.
The key insight: Dart’s three mechanisms (extends, implements, with) map to three distinct relationships: is-a, has-contract, and has-capability — choose the right one for each design problem.
Forgetting that O ( n log n ) O(n \log n) O ( n log n ) average-case for quicksort becomes O ( n 2 ) O(n^2) O ( n 2 ) worst-case on already sorted input.
Neglecting to normalise database designs, leading to data redundancy and update anomalies.
Misunderstanding the difference between a stack (LIFO) and a queue (FIFO) in data structure applications.
Forgetting edge cases in algorithm design (e.g., empty input, single element, already sorted data).
The key principles covered in this topic are linked in the sub-pages above. Focus on understanding the definitions, applying the formulas or frameworks, and evaluating strengths and limitations of each approach.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Variables : Field specifiers (final, const, late) used in class declarations.Class Modifiers : Dart 3 sealed, base, interface, and final modifiers for class hierarchies.Error Handling : Custom exception classes using sealed class hierarchies.Async and Futures : Asynchronous factory constructors and mixin patterns in async contexts.