On this page25 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is a Constructor?
- 2. The Default Constructor
- 3. Writing Your Own Constructor
- 4. The Body Is Too Late
- 5. Initializing Formals: this.field
- 6. Your Constructor Replaces the Default One
- 7. Named Parameters and required
- 8. Optional Parameters and Default Values
- 9. final Fields Set by the Constructor
- 10. Initializer Lists
- 11. Running Code in the Constructor Body
- 12. A Quick Look at assert
- 13. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 54 — Constructors
| Category | 9 — Object-Oriented Programming |
| Lecture | 054 of 140 |
| Estimated video length | 11 min |
| Target | Dart 3.12.2 |
| Level | Beginner |
Creating an object and then filling its fields one by one lets you forget a field. A constructor fills them while the object is being created — and Dart refuses to create it until every field has a value.
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 001–053:
finalvariables (Lecture 006) and nullable types (Lecture 013)- positional, optional
[ ]and default parameters (Lecture 032) - named parameters and
required(Lecture 033) - classes, fields, methods, creating objects, and cascades
..(Lecture 053)
Connection With Previous Lectures
In Lecture 053 we created an object first and filled in its fields afterwards:
class Student {
String name = '';
int age = 0;
}
void main() {
var ali = Student()
..name = 'Ali'
..age = 20;
var sara = Student();
print('${ali.name} is ${ali.age}');
print('${sara.name} is ${sara.age}');
}Ali is 20
is 0Nothing stopped us from forgetting Sara's details, so she became a student with no name,
aged 0. The fake starting values '' and 0 hid the mistake. We need a way to say: a
Student cannot exist without a name and an age.
Learning Objectives
By the end of this lecture you will be able to:
- Explain what a constructor is and when it runs
- Recognize the default constructor Dart provides
- Write a constructor with parameters using
this.field - Use named parameters,
required, and default values in a constructor - Set
finalfields from a constructor - Compute a field from other values with an initializer list
- Run extra code in a constructor body
- Recognize the errors Dart gives for missing or unset fields
1. What Is a Constructor?
A constructor is a special function inside a class that runs once, at the moment an object is created. Its job is to give the new object its starting values.
Analogy — think of a school admission form. A student is not enrolled until the form is filled in. The constructor is that form: the object does not exist until its details are provided.
A constructor has the same name as the class and no return type.
2. The Default Constructor
Every time we wrote Student() in Lecture 053, we were already calling a constructor. If a
class declares no constructor, Dart quietly adds one with no parameters, called the default
constructor:
class Student {
String name = 'Unknown';
}
void main() {
var s = Student();
print(s.name);
}UnknownThe brackets in Student() are the call. The default constructor takes nothing, so every
field needed a starting value written in the class.
3. Writing Your Own Constructor
Write the class name, a parameter list, and a body — just like a function without a return type:
class Student {
String name = '';
int age = 0;
Student(String newName, int newAge) {
name = newName;
age = newAge;
}
}
void main() {
var ali = Student('Ali', 20);
print('${ali.name} is ${ali.age}');
}Ali is 20Student('Ali', 20) passes the arguments to the constructor, and the body copies them into
the fields. A constructor that creates and fills a new object like this is called a
generative constructor.
4. The Body Is Too Late
Those placeholder values '' and 0 are still there. Remove them and Dart objects:
class Student {
String name;
int age;
Student(String newName, int newAge) {
name = newName;
age = newAge;
}
}Error: Field 'name' should be initialized because its type 'String' doesn't allow null.
String name;
^^^^
Error: Field 'age' should be initialized because its type 'int' doesn't allow null.
int age;
^^^Dart's rule: every non-nullable field must have a value before the body starts running.
Inside the body, the object already exists — so its fields would be null for a moment, and
String does not allow null. We need to set fields earlier.
5. Initializing Formals: this.field
Write this.name as a parameter, and Dart puts the argument straight into the field before
the body runs:
class Student {
String name;
int age;
Student(this.name, this.age);
}
void main() {
var sara = Student('Sara', 19);
print('${sara.name} is ${sara.age}');
}Sara is 19This is called an initializing formal. There is no body at all — the semicolon ends the
constructor. No placeholders, no copying, and Sara can no longer be created without details.
this means "the object being built"; we study it properly in Lecture 056.
6. Your Constructor Replaces the Default One
Once you write a constructor, Dart no longer adds the empty default one:
class Student {
String name;
int age;
Student(this.name, this.age);
}
void main() {
var s = Student();
}Error: Too few positional arguments: 2 required, 0 given.
var s = Student();
^That is exactly what we wanted: forgetting the details is now a compile error, not a silent bug.
7. Named Parameters and required
Constructors accept named parameters, exactly like the functions in Lecture 033. Combine
{ }, required and this.:
class Student {
String name;
int age;
Student({required this.name, required this.age});
}
void main() {
var zayan = Student(age: 6, name: 'Zayan');
print('${zayan.name} is ${zayan.age}');
}Zayan is 6The call now explains itself, and the order does not matter. Leave one out and Dart names it:
Error: Required named parameter 'age' must be provided.
var s = Student(name: 'Ali');
^8. Optional Parameters and Default Values
An optional parameter needs a default value or a nullable type (Lectures 032 and 033). The same rule applies to constructors:
class Student {
String name;
int age;
String city;
String? email;
Student(this.name, {this.age = 18, this.city = 'Karachi', this.email});
}
void main() {
var ali = Student('Ali');
var sara = Student('Sara', age: 21, email: 'sara@mail.com');
print('${ali.name}, ${ali.age}, ${ali.city}, ${ali.email}');
print('${sara.name}, ${sara.age}, ${sara.city}, ${sara.email}');
}Ali, 18, Karachi, null
Sara, 21, Karachi, sara@mail.comname is required and positional; the rest are optional named parameters. email is
String?, so it simply stays null when left out.
9. final Fields Set by the Constructor
A student's ID should never change after creation. Mark the field final and let the
constructor set it:
class Student {
final int id;
String name;
Student(this.id, this.name);
}
void main() {
var ali = Student(101, 'Ali');
ali.name = 'Ali Khan';
print('${ali.id}: ${ali.name}');
}101: Ali Khanname can change; id cannot. Try ali.id = 202; and Dart refuses, because a final field
has no way to be assigned from outside:
Error: The setter 'id' isn't defined for the type 'Student'.10. Initializer Lists
Sometimes a field is not passed in but calculated from other values. Put it after a colon
:, between the parameters and the (optional) body. This is an initializer list:
class Product {
final String name;
final double price;
final int quantity;
final double total;
Product(this.name, this.price, this.quantity) : total = price * quantity;
}
void main() {
var pen = Product('Pen', 25.5, 4);
print('${pen.name}: ${pen.total}');
}Pen: 102.0The initializer list runs before the body, so it can set even final and non-nullable
fields. Separate several entries with commas.
11. Running Code in the Constructor Body
A body is still useful for work that is not just setting fields. It runs last, after every field has its value:
class BankAccount {
final String owner;
double balance;
BankAccount(this.owner, this.balance) {
print('Account opened for $owner with $balance');
}
}
void main() {
var account = BankAccount('Sara', 1000.0);
account.balance = account.balance + 500;
print('Balance: ${account.balance}');
}Account opened for Sara with 1000.0
Balance: 1500.0The order is always: parameters → initializer list → body.
12. A Quick Look at assert
An initializer list can also contain an assert, a check that stops the program during
development when a condition is false:
class Product {
final String name;
final double price;
Product(this.name, this.price) : assert(price >= 0);
}
void main() {
var bag = Product('Bag', 1500);
print('${bag.name}: ${bag.price}');
}Bag: 1500.0Assertions only run when enabled (for example dart run --enable-asserts), so they are a
developer's safety net, not real validation. We will study assertions in Lecture 086 —
Rethrowing, Exception Design & Assertions.
13. A Real-World Example
class OrderLine {
final String product;
final double price;
final int quantity;
final double discountRate;
final double total;
OrderLine({
required this.product,
required this.price,
this.quantity = 1,
this.discountRate = 0.0,
}) : total = price * quantity * (1 - discountRate) {
print('Added $quantity x $product');
}
}
void main() {
var shoes = OrderLine(product: 'Shoes', price: 3000, discountRate: 0.1);
var socks = OrderLine(product: 'Socks', price: 200, quantity: 3);
print('Shoes: ${shoes.total}');
print('Socks: ${socks.total}');
print('Bill: ${shoes.total + socks.total}');
}Added 1 x Shoes
Added 3 x Socks
Shoes: 2700.0
Socks: 600.0
Bill: 3300.0Required named parameters, defaults, final fields, an initializer list and a body — every
order line is complete and correct the moment it exists.
Code Examples
Example 1 — The Default Constructor
class Car {
String brand = 'Toyota';
int wheels = 4;
}
void main() {
var car = Car();
print('${car.brand} has ${car.wheels} wheels');
}Toyota has 4 wheelsExample 2 — A Constructor with a Body
class Pet {
String name = '';
Pet(String petName) {
name = petName;
}
}
void main() {
var pet = Pet('Whiskers');
print(pet.name);
}WhiskersExample 3 — Initializing Formals
class Book {
String title;
int pages;
Book(this.title, this.pages);
}
void main() {
var book = Book('Dart Basics', 240);
print('${book.title}: ${book.pages} pages');
}Dart Basics: 240 pagesExample 4 — Required Named Parameters
class City {
String name;
int population;
City({required this.name, required this.population});
}
void main() {
var city = City(population: 2000000, name: 'Lahore');
print('${city.name}: ${city.population}');
}Lahore: 2000000Example 5 — Default and Nullable Parameters
class Player {
String name;
int level;
String? team;
Player(this.name, {this.level = 1, this.team});
}
void main() {
var p1 = Player('Zayan');
var p2 = Player('Sara', level: 5, team: 'Red');
print('${p1.name} L${p1.level} ${p1.team ?? 'no team'}');
print('${p2.name} L${p2.level} ${p2.team ?? 'no team'}');
}Zayan L1 no team
Sara L5 RedExample 6 — An Optional Positional Parameter
class Greeting {
String name;
String message;
Greeting(this.name, [this.message = 'Hello']);
}
void main() {
var g1 = Greeting('Ali');
var g2 = Greeting('Sara', 'Welcome');
print('${g1.message}, ${g1.name}!');
print('${g2.message}, ${g2.name}!');
}Hello, Ali!
Welcome, Sara!Example 7 — Student Marks
class StudentResult {
final String name;
final int math;
final int science;
final int english;
final int total;
StudentResult(this.name, this.math, this.science, this.english)
: total = math + science + english;
}
void main() {
var ali = StudentResult('Ali', 80, 75, 85);
print('${ali.name}: ${ali.total}');
}Ali: 240How it works
- The four
this.parameters fill the mark fields. - The initializer list adds them into
totalbefore the object is finished. - Every field is
final, so a result can never be altered after it is created.
Example 8 — Shopping Total
class CartItem {
final String product;
final int price;
final int quantity;
final int total;
CartItem({required this.product, required this.price, this.quantity = 1})
: total = price * quantity;
}
void main() {
var notebook = CartItem(product: 'Notebook', price: 500, quantity: 3);
var pen = CartItem(product: 'Pen', price: 50);
print('${notebook.product}: ${notebook.total}');
print('${pen.product}: ${pen.total}');
}Notebook: 1500
Pen: 50How it works
productandpriceare required;quantitydefaults to1.totalis calculated once, in the initializer list.- The pen left out
quantity, so its total is50 * 1.
Example 9 — Bank Balance
class BankAccount {
final String owner;
double balance;
BankAccount(this.owner, {this.balance = 0.0}) {
print('Welcome, $owner!');
}
void deposit(double amount) {
balance = balance + amount;
}
}
void main() {
var account = BankAccount('Ali', balance: 1000.0);
account.deposit(250.5);
print('Balance: ${account.balance}');
}Welcome, Ali!
Balance: 1250.5How it works
ownerisfinal;balanceis not, because deposits change it.- The body runs after both fields are set, so it can use
owner. depositis an ordinary method, as in Lecture 053.
Example 10 — Temperature Conversion
class Temperature {
final double celsius;
final double fahrenheit;
Temperature(this.celsius) : fahrenheit = celsius * 9 / 5 + 32;
}
void main() {
var today = Temperature(25.0);
print('${today.celsius} C = ${today.fahrenheit} F');
}25.0 C = 77.0 FHow it works
- Only
celsiusis passed in. - The initializer list converts it into
fahrenheit. - Both values are
finaland always agree with each other.
Common Beginner Mistakes
1. Leaving a non-nullable field unset
class Student {
String name;
int age; // ✗ no constructor parameter sets it
Student(this.name);
}Error: Field 'age' should be initialized because its type 'int' doesn't allow null.Add this.age to the constructor, give it a default, or make it int?.
2. Assigning fields in the body instead of before it
class Student {
String name; // ✗
Student(String newName) {
name = newName;
}
}Error: Field 'name' should be initialized because its type 'String' doesn't allow null.The body runs too late. Use Student(this.name);.
3. Setting a final field in the body
class Product {
final double price;
final double total;
Product(this.price) {
// total = price * 2; // ✗
}
}Error: The setter 'total' isn't defined for the type 'Product'.
Error: Final field 'total' is not initialized.Calculated final fields belong in the initializer list: Product(this.price) : total = price * 2;.
4. Calling the empty default constructor after writing your own
// var s = Student(); // ✗ Student(this.name, this.age) needs two argumentsError: Too few positional arguments: 2 required, 0 given.5. Passing names to positional parameters
// var s = Student(name: 'Ali', age: 20); // ✗ the constructor is Student(this.name, this.age)Error: Too few positional arguments: 2 required, 0 given.Named arguments only work when the constructor declares { }.
6. Expecting assert to always run
An assert is skipped by a plain dart run. Don't rely on it to protect real data.
Best Practices
- Prefer
this.fieldparameters over copying values in a body. - Make fields
finalwhen they should not change after creation. - Use
requirednamed parameters when a constructor takes several values of the same type. - Give optional fields sensible defaults; use a nullable type only when "no value" is real.
- Calculate derived fields once, in the initializer list.
- Keep constructor bodies short — no body at all is common and good.
- Avoid placeholder values like
''and0just to satisfy Dart.
Mini Challenge
Write a Rectangle class with final double width, final double height, and a final double area calculated in an initializer list. Create a 4.0 by 2.5 rectangle and print:
Area: 10.0Try it yourself before opening the solution.
Show solution
class Rectangle {
final double width;
final double height;
final double area;
Rectangle(this.width, this.height) : area = width * height;
}
void main() {
var r = Rectangle(4.0, 2.5);
print('Area: ${r.area}');
}Area: 10.0How it works
this.widthandthis.heightfill the two measurements.- The initializer list multiplies them into
area. - All three fields are
final, so the area can never fall out of date.
Practice Questions
Work through these in order — they run from beginner to application to challenge. Every question gives you exact values, so you can compare your output with the answers.
Beginner
- Write a
Fruitclass with aString namefield and a constructor usingthis.name. CreateFruit('Mango')and print its name. - Write a
Laptopclass withbrandandprice(anint) set by a positional constructor. Create one for'Dell'at150000and printDell: 150000. - What does Dart give a class that declares no constructor? Write a
Counterclass withint value = 0;and no constructor, create one, and printvalue.
Application
-
Write a
Teacherclass whose constructor takes required namednameandsubject. Create Sara, who teaches Math, and printSara teaches Math. -
Write a
Coffeeclass withString sizedefaulting to'Medium'andbool sugardefaulting tofalse, both named. Print the two fields ofCoffee()and ofCoffee(size: 'Large', sugar: true), one object per line. -
Write a
Scoreclass withfinal String playerandint points. Create Zayan with10points, add15topoints, and printZayan: 25. -
This class does not compile. Fix it without giving the fields starting values:
class Student { String name; int age; Student(String n, int a) { name = n; age = a; } }
Challenge
- Write an
Invoiceclass withprice(double),taxRate(double, default0.1) and afinal double totalequal toprice + price * taxRate. Printinvoice.totalforInvoice(2000.0). - Write a
Marksclass takingname,obtainedandoutOf(bothint). Computefinal double percentagein an initializer list. PrintAli: 80.0%for marks400out of500. - Write a
Thermometerclass withfinal double celsiusand afinal bool isHotthat istruewhencelsiusis above30. Print both fields for35.0and22.5like35.0 hot: true. The constructor must have no body.
Practice Answers
Question 1 — Solution
class Fruit {
String name;
Fruit(this.name);
}
void main() {
var fruit = Fruit('Mango');
print(fruit.name);
}MangoQuestion 2 — Solution
class Laptop {
String brand;
int price;
Laptop(this.brand, this.price);
}
void main() {
var laptop = Laptop('Dell', 150000);
print('${laptop.brand}: ${laptop.price}');
}Dell: 150000Question 3 — Solution
Dart adds the default constructor, which takes no arguments.
class Counter {
int value = 0;
}
void main() {
var counter = Counter();
print(counter.value);
}0Question 4 — Solution
class Teacher {
String name;
String subject;
Teacher({required this.name, required this.subject});
}
void main() {
var sara = Teacher(name: 'Sara', subject: 'Math');
print('${sara.name} teaches ${sara.subject}');
}Sara teaches MathQuestion 5 — Solution
class Coffee {
String size;
bool sugar;
Coffee({this.size = 'Medium', this.sugar = false});
}
void main() {
var c1 = Coffee();
var c2 = Coffee(size: 'Large', sugar: true);
print('${c1.size} ${c1.sugar}');
print('${c2.size} ${c2.sugar}');
}Medium false
Large trueQuestion 6 — Solution
class Score {
final String player;
int points;
Score(this.player, this.points);
}
void main() {
var score = Score('Zayan', 10);
score.points = score.points + 15;
print('${score.player}: ${score.points}');
}Zayan: 25Question 7 — Solution
Use initializing formals, so the fields are set before the body would run:
class Student {
String name;
int age;
Student(this.name, this.age);
}
void main() {
var s = Student('Ali', 20);
print('${s.name} ${s.age}');
}Ali 20Question 8 — Solution
class Invoice {
final double price;
final double taxRate;
final double total;
Invoice(this.price, {this.taxRate = 0.1}) : total = price + price * taxRate;
}
void main() {
var invoice = Invoice(2000.0);
print(invoice.total);
}2200.0Question 9 — Solution
class Marks {
final String name;
final int obtained;
final int outOf;
final double percentage;
Marks(this.name, this.obtained, this.outOf)
: percentage = obtained / outOf * 100;
}
void main() {
var ali = Marks('Ali', 400, 500);
print('${ali.name}: ${ali.percentage}%');
}Ali: 80.0%/ always gives a double, so percentage prints as 80.0.
Question 10 — Solution
class Thermometer {
final double celsius;
final bool isHot;
Thermometer(this.celsius) : isHot = celsius > 30;
}
void main() {
var t1 = Thermometer(35.0);
var t2 = Thermometer(22.5);
print('${t1.celsius} hot: ${t1.isHot}');
print('${t2.celsius} hot: ${t2.isHot}');
}35.0 hot: true
22.5 hot: falseQuick Revision
- When does a constructor run?
- What does Dart provide when a class has no constructor?
- What does
this.namein a parameter list do? - Why can't a constructor body set a non-nullable field that has no starting value?
- How do you make a constructor parameter named and compulsory?
- Where is a calculated
finalfield set? - In what order do the parameters, initializer list and body run?
Key Takeaways
- A constructor has the class's name and runs once, when an object is created.
- With no constructor written, Dart supplies an empty default constructor; writing your own removes it.
this.fieldparameters (initializing formals) put arguments straight into fields.- Non-nullable and
finalfields must be set before the body — bythis.field, a default, or the initializer list. - Constructors support positional,
[ ]optional, named,requiredand default parameters. - An initializer list (
: total = price * quantity) computes fields from other values. - The body runs last, for extra work after the fields are set.
assertin an initializer list is a development-only check (Lecture 086).
What's Next?
Lecture 55 — Named & Constant Constructors. A class can have only one unnamed
constructor, yet objects are often created in different ways — a student from a name and age,
or a guest student with no details at all. Next we will give constructors their own names, and
learn how const constructors create objects that never change.