Watch the video (11 min)
On this page21 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Throwing an Exception
  5. 2. Three Built-in Types to Throw
  6. 3. Catching What You Throw
  7. 4. throw Is an Expression
  8. 5. Functions That Return Never
  9. 6. A Custom Exception Class
  10. 7. A Hierarchy of Exceptions
  11. 8. Protecting Invariants in Constructors and Setters
  12. 9. A Real-World Example
  13. Code Examples
  14. Common Beginner Mistakes
  15. Best Practices
  16. Mini Challenge
  17. Practice Questions
  18. Practice Answers
  19. Quick Revision
  20. Key Takeaways
  21. What's Next?

Lecture 85 — throw & Custom Exceptions

Category14 — Error & Exception Handling
Lecture085 of 140
Estimated video length11 min
TargetDart 3.12.2
LevelIntermediate

Until now, a method given a bad value could only print a message or return false. With throw, your code raises an exception the caller cannot ignore, and custom exception classes say exactly what went wrong.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every error and warning in this lecture is real. Errors come from dart run, warnings from dart analyze, on Dart 3.12.2. Stack trace lines after Unhandled exception: are left out to save space.


Prerequisites

Students should already understand, from Lectures 030–084:

  • functions, =>, and ?? (Lectures 014, 030–035)
  • classes, constructors, setters, private members, inheritance, implements (053–063)
  • the Never type (075) and switch expressions (081)
  • errors vs exceptions (083), and try / on Type catch (e) (084)

Connection With Previous Lectures

In Lecture 057, BankAccount.withdraw returned false for a bad amount. The caller can simply ignore that false, and a false cannot say why: a negative amount, or not enough money? In Lecture 084 you learned to catch the exceptions Dart throws. Now our own code throws them, with our own types that carry the details.


Learning Objectives

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

  • Throw built-in errors and exceptions with throw
  • Choose between ArgumentError, StateError, and FormatException
  • Use throw as an expression after ?? and =>
  • Write a function whose return type is Never
  • Create a custom exception class with fields and a toString
  • Build a small hierarchy of exceptions and catch them by type
  • Protect an object's invariants by throwing from constructors and setters

1. Throwing an Exception

throw stops the current function immediately and hands an object up to whoever called it. If nobody catches it, the program ends.

Analogy — a referee's whistle stops play at once, and the card shows what the foul was. throw is the whistle; the object you throw is the card.

dart
void deposit(int amount) {
  if (amount <= 0) {
    throw ArgumentError('Deposit must be positive: $amount');
  }
  print('Deposited $amount');
}

void main() {
  deposit(500);
  deposit(-50);
  print('Done');
}
output
Deposited 500
Unhandled exception:
Invalid argument(s): Deposit must be positive: -50

'Done' never prints: nothing after the throw runs.


2. Three Built-in Types to Throw

Dart already has a class for the most common problems:

Throw thisWhenIt prints as
ArgumentErrora caller passed a bad valueInvalid argument(s): …
StateErrorthe object is not ready for this callBad state: …
FormatExceptiontext is not in the expected formatFormatException: …

You met two already: int.parse('twenty') (Lecture 010) and first on an empty list (Lecture 040). Example 2 below throws a StateError from a class whose call came at the wrong moment.


3. Catching What You Throw

Your own throws are caught like Dart's, with on Type catch (e) (Lecture 084). ArgumentError.value records the bad value and parameter name too:

dart
int parseMarks(String text) {
  var marks = int.tryParse(text);
  if (marks == null) {
    throw FormatException('Marks must be a number', text);
  }
  if (marks < 0 || marks > 100) {
    throw ArgumentError.value(marks, 'marks', 'must be 0 to 100');
  }
  return marks;
}

void main() {
  for (var text in ['85', 'abc', '120']) {
    try {
      print('Saved ${parseMarks(text)}');
    } on FormatException catch (e) {
      print('Not a number: ${e.source}');
    } on ArgumentError catch (e) {
      print('${e.name} ${e.message}, got ${e.invalidValue}');
    }
  }
}
output
Saved 85
Not a number: abc
marks must be 0 to 100, got 120

Each failure lands in its own on block.


4. throw Is an Expression

throw can sit where a value is expected. After ?? it means "use this value, or fail"; put it in parentheses there:

dart
String findPhone(Map<String, String> phones, String name) =>
    phones[name] ?? (throw ArgumentError('No phone for $name'));

void main() {
  var phones = {'Ali': '0300-111', 'Sara': '0321-222'};
  print(findPhone(phones, 'Sara'));
  print(findPhone(phones, 'Zayan'));
}
output
0321-222
Unhandled exception:
Invalid argument(s): No phone for Zayan

The result type is String, not String?: either we return a phone number or we never return at all. throw also works as an => body, for example as the _ => arm of a switch expression that rejects every value the other arms did not handle (see Example 5).


5. Functions That Return Never

In Lecture 075 you met Never, the type with no values. A function that always throws has nothing to return, so its return type is Never:

dart
Never fail(String message) {
  throw StateError(message);
}

String describe(int? stock) {
  if (stock == null) fail('Stock unknown');
  return 'In stock: ${stock + 1}';
}

void main() {
  print(describe(3));
  print(describe(null));
}
output
In stock: 4
Unhandled exception:
Bad state: Stock unknown

stock + 1 needs no !: because fail never returns, Dart knows stock is not null after the if, and promotes it to int.


6. A Custom Exception Class

Built-in types say what kind of problem happened, not the details. A custom exception is an ordinary class that implements Exception:

dart
class InsufficientFundsException implements Exception {
  final int balance;
  final int requested;

  InsufficientFundsException(this.balance, this.requested);

  int get shortfall => requested - balance;

  @override
  String toString() =>
      'InsufficientFundsException: wanted $requested, only $balance available';
}

void withdraw(int balance, int amount) {
  if (amount > balance) {
    throw InsufficientFundsException(balance, amount);
  }
  print('Paid $amount');
}

void main() {
  try {
    withdraw(300, 500);
  } on InsufficientFundsException catch (e) {
    print(e);
    print('Sorry, you need ${e.shortfall} more.');
  }
}
output
InsufficientFundsException: wanted 500, only 300 available
Sorry, you need 200 more.

implements Exception marks the class as an exception. The fields carry the facts, and toString builds the message (without it you would see Instance of 'InsufficientFundsException'). Because e has the custom type, the caller can read e.shortfall, which a bool result could never provide.


7. A Hierarchy of Exceptions

Related exceptions can share a parent, so a caller can catch one problem or the whole family:

dart
abstract class BankException implements Exception {
  final String message;
  BankException(this.message);

  @override
  String toString() => message;
}

class InvalidAmountException extends BankException {
  InvalidAmountException(int amount) : super('Invalid amount: $amount');
}

class InsufficientFundsException extends BankException {
  InsufficientFundsException(int balance, int requested)
    : super('Wanted $requested, only $balance available');
}

void withdraw(int balance, int amount) {
  if (amount <= 0) throw InvalidAmountException(amount);
  if (amount > balance) throw InsufficientFundsException(balance, amount);
  print('Paid $amount');
}

void main() {
  for (var amount in [200, -5, 900]) {
    try {
      withdraw(500, amount);
    } on InsufficientFundsException catch (e) {
      print('Low balance. $e');
    } on BankException catch (e) {
      print('Bank problem. $e');
    }
  }
}
output
Paid 200
Bank problem. Invalid amount: -5
Low balance. Wanted 900, only 500 available

on clauses are checked top to bottom, so put the specific type first and the parent last; -5 falls through to on BankException.


8. Protecting Invariants in Constructors and Setters

Lecture 057's invariant was the balance is never negative. Its constructor quietly turned -300 into 0, so Sara got an account she never asked for. Throwing refuses to build a bad object at all:

dart
class InsufficientFundsException implements Exception {
  final int balance;
  final int requested;
  InsufficientFundsException(this.balance, this.requested);
}

class BankAccount {
  final String owner;
  int _balance;

  BankAccount(this.owner, this._balance) {
    if (_balance < 0) {
      throw ArgumentError.value(_balance, 'balance', 'cannot be negative');
    }
  }

  int get balance => _balance;

  void withdraw(int amount) {
    if (amount > _balance) throw InsufficientFundsException(_balance, amount);
    _balance -= amount;
  }
}

void main() {
  var ali = BankAccount('Ali', 1000);
  ali.withdraw(400);
  print('Ali: ${ali.balance}');
  var sara = BankAccount('Sara', -300);
  print('Sara: ${sara.balance}');
}
output
Ali: 600
Unhandled exception:
Invalid argument (balance): cannot be negative: -300

sara is never created; a negative balance cannot exist. withdraw now returns void: success is the normal path, and failure cannot be ignored.

A setter guards an invariant the same way. Lecture 056's marks setter printed Rejected; now it throws a RangeError (an ArgumentError for numbers out of range) before storing anything:

dart
  set marks(int value) {
    if (value < 0 || value > 100) throw RangeError.range(value, 0, 100, 'marks');
    _marks = value;
  }

Setting -40 throws RangeError (marks): Invalid value: Not in inclusive range 0..100: -40.


9. A Real-World Example

A small bank processing a list of transfers:

dart
abstract class BankException implements Exception {
  final String message;
  BankException(this.message);

  @override
  String toString() => message;
}

class AccountNotFoundException extends BankException {
  AccountNotFoundException(String name) : super('No account for $name');
}

class InsufficientFundsException extends BankException {
  InsufficientFundsException(String owner, int shortfall)
    : super('$owner is short by $shortfall');
}

class Bank {
  final Map<String, int> _balances = {'Ali': 1000, 'Sara': 200};

  int _find(String name) =>
      _balances[name] ?? (throw AccountNotFoundException(name));

  void transfer(String from, String to, int amount) {
    if (amount <= 0) throw ArgumentError.value(amount, 'amount');
    var fromBalance = _find(from);
    var toBalance = _find(to);
    if (amount > fromBalance) {
      throw InsufficientFundsException(from, amount - fromBalance);
    }
    _balances[from] = fromBalance - amount;
    _balances[to] = toBalance + amount;
    print('OK: $from -> $to $amount');
  }

  void report() => print(_balances);
}

void main() {
  var bank = Bank();
  var transfers = [
    ('Ali', 'Sara', 300),
    ('Sara', 'Zayan', 50),
    ('Sara', 'Ali', 900),
  ];
  for (var (from, to, amount) in transfers) {
    try {
      bank.transfer(from, to, amount);
    } on InsufficientFundsException catch (e) {
      print('Declined: $e');
    } on BankException catch (e) {
      print('Failed: $e');
    }
  }
  bank.report();
}
output
OK: Ali -> Sara 300
Failed: No account for Zayan
Declined: Sara is short by 400
{Ali: 700, Sara: 500}

Every check throws before a balance changes, so failed transfers change nothing. Nobody catches the ArgumentError on purpose: as Lecture 083 explained, an Error means the calling code has a bug to fix, while an Exception is a problem a correct program handles.


Code Examples

Example 1 — Rejecting a Bad Argument

dart
double toFahrenheit(double celsius) {
  if (celsius < -273.15) {
    throw ArgumentError('Below absolute zero: $celsius');
  }
  return celsius * 9 / 5 + 32;
}

void main() {
  print(toFahrenheit(25));
  try {
    toFahrenheit(-300);
  } on ArgumentError catch (e) {
    print(e);
  }
}
output
77.0
Invalid argument(s): Below absolute zero: -300.0

Example 2 — A StateError for the Wrong Moment

dart
class Order {
  bool _paid = false;

  void pay() => _paid = true;

  void ship() {
    if (!_paid) throw StateError('Order not paid yet');
    print('Shipped');
  }
}

void main() {
  var order = Order();
  try {
    order.ship();
  } on StateError catch (e) {
    print(e);
  }
  order.pay();
  order.ship();
}
output
Bad state: Order not paid yet
Shipped

Example 3 — A FormatException for Bad Text

dart
(String, int) parseEntry(String line) {
  var parts = line.split(':');
  if (parts.length != 2) {
    throw FormatException('Expected name:marks', line);
  }
  return (parts[0], int.parse(parts[1]));
}

void main() {
  print(parseEntry('Ali:80'));
  try {
    parseEntry('Sara-90');
  } on FormatException catch (e) {
    print('${e.message} in "${e.source}"');
  }
}
output
(Ali, 80)
Expected name:marks in "Sara-90"

Example 4 — ?? (throw ...)

dart
void main() {
  var prices = {'pen': 50, 'book': 450};
  for (var item in ['book', 'bag']) {
    try {
      var price = prices[item] ?? (throw StateError('No price for $item'));
      print('$item: $price');
    } on StateError catch (e) {
      print(e);
    }
  }
}
output
book: 450
Bad state: No price for bag

Example 5 — throw in a Switch Arm

dart
String dayName(int day) => switch (day) {
  1 => 'Monday',
  2 => 'Tuesday',
  _ => throw RangeError.range(day, 1, 2, 'day'),
};

void main() {
  print(dayName(2));
  try {
    print(dayName(9));
  } on RangeError catch (e) {
    print(e);
  }
}
output
Tuesday
RangeError (day): Invalid value: Not in inclusive range 1..2: 9

Example 6 — A Never Helper

dart
Never missing(String field) {
  throw FormatException('Missing field: $field');
}

void main() {
  var form = {'name': 'Zayan'};
  var name = form['name'] ?? missing('name');
  print('Hello, $name');
  var city = form['city'] ?? missing('city');
  print(city);
}
output
Hello, Zayan
Unhandled exception:
FormatException: Missing field: city

Example 7 — Student Marks

dart
class InvalidMarksException implements Exception {
  final int marks;
  InvalidMarksException(this.marks);
}

void main() {
  var sheet = {'Ali': 80, 'Sara': 105, 'Zayan': 70};
  sheet.forEach((student, marks) {
    try {
      if (marks > 100) throw InvalidMarksException(marks);
      print('$student: $marks');
    } on InvalidMarksException catch (e) {
      print('Check $student: ${e.marks} is above 100');
    }
  });
}
output
Ali: 80
Check Sara: 105 is above 100
Zayan: 70

Example 8 — Shopping Total

dart
class OutOfStockException implements Exception {
  final String item;
  final int available;
  OutOfStockException(this.item, this.available);
}

void main() {
  var stock = {'notebook': 10, 'pen': 2};
  var total = 0;
  try {
    for (var (item, qty, price) in [('notebook', 3, 500), ('pen', 5, 50)]) {
      if (qty > stock[item]!) throw OutOfStockException(item, stock[item]!);
      total += qty * price;
    }
  } on OutOfStockException catch (e) {
    print('Only ${e.available} ${e.item}(s) left');
  }
  print('Total: $total');
}
output
Only 2 pen(s) left
Total: 1500

Example 9 — Bank Balance

dart
class BankException implements Exception {
  final String message;
  BankException(this.message);

  @override
  String toString() => message;
}

class FrozenException extends BankException {
  FrozenException(String owner) : super('$owner is frozen');
}

void main() {
  var all = [('Ali', false, 500), ('Sara', false, 50), ('Zayan', true, 900)];
  for (var (owner, frozen, balance) in all) {
    try {
      if (frozen) throw FrozenException(owner);
      if (balance < 100) throw BankException('$owner is short');
      print('$owner paid 100');
    } on BankException catch (e) {
      print('Failed: $e');
    }
  }
}
output
Ali paid 100
Failed: Sara is short
Failed: Zayan is frozen

One on BankException catches the parent and every child.

Example 10 — Temperature Readings

dart
class SensorException implements Exception {
  final String reading;
  SensorException(this.reading);
}

double parseReading(String text) =>
    double.tryParse(text) ?? (throw SensorException(text));

void main() {
  var valid = <double>[];
  for (var text in ['21.5', 'ERR', '23.5']) {
    try {
      valid.add(parseReading(text));
    } on SensorException catch (e) {
      print('Skipped "${e.reading}"');
    }
  }
  print('Average: ${(valid[0] + valid[1]) / valid.length}');
}
output
Skipped "ERR"
Average: 22.5

Common Beginner Mistakes

1. throw after ?? without parentheses

dart
int parse(String? text) {
  // return text?.length ?? throw ArgumentError('No text'); // ✗
  return text?.length ?? (throw ArgumentError('No text')); // ✓
}
output
Error: 'throw' can't be used as an identifier because it's a keyword.

2. Extending Exception

dart
// class PaymentException extends Exception {} // ✗
class PaymentException implements Exception {}

void main() {}
output
Error: The class 'Exception' can't be extended outside of its library because it's an interface class.

Use implements Exception; extending your own base class, like BankException, is fine.

3. Forgetting the throw keyword

dart
void deposit(int amount) {
  if (amount <= 0) {
    ArgumentError('Bad amount'); // ✗ creates the object, throws nothing
  }
  print('Deposited $amount');
}

void main() => deposit(-50);
output
Deposited -50

No error, which is exactly the problem.

4. Catching the parent before the child

dart
class BankException implements Exception {}

class InsufficientFundsException extends BankException {}

void main() {
  try {
    throw InsufficientFundsException();
  } on BankException {
    print('Bank problem');
  } on InsufficientFundsException {
    print('Low balance'); // never reached
  }
}
output
Bank problem

dart analyze flags the dead block:

output
warning - Dead code: This on-catch block won't be executed because 'InsufficientFundsException' is a subtype of 'BankException' and hence will have been caught already.

Best Practices

  • Throw as soon as you detect a problem, before changing any state.
  • Throw Errors for mistakes in calling code; throw Exceptions for problems a correct program should handle.
  • Name custom exceptions with an Exception suffix and use implements Exception.
  • Give custom exceptions final fields for the details and a clear toString.
  • Throw objects, never strings: throw 'No money' runs, but cannot be caught by a useful type.

Mini Challenge

Build a Thermostat with int _target = 20, a getter, and a setter that throws a custom TemperatureOutOfRangeException (field requested) outside 16..28. Set 22, then 35, catching the failure, and print the final target.

Your program should print exactly:

output
Target set to 22
Rejected: 35
Final target: 22

Try it yourself before opening the solution.

Show solution
dart
class TemperatureOutOfRangeException implements Exception {
  final int requested;
  TemperatureOutOfRangeException(this.requested);
}

class Thermostat {
  int _target = 20;

  int get target => _target;

  set target(int value) {
    if (value < 16 || value > 28) throw TemperatureOutOfRangeException(value);
    _target = value;
  }
}

void main() {
  var thermostat = Thermostat();
  for (var value in [22, 35]) {
    try {
      thermostat.target = value;
      print('Target set to $value');
    } on TemperatureOutOfRangeException catch (e) {
      print('Rejected: ${e.requested}');
    }
  }
  print('Final target: ${thermostat.target}');
}
output
Target set to 22
Rejected: 35
Final target: 22

The setter throws before _target changes, so the print after it is skipped.


Practice Questions

Beginner

  1. Write int square(int n) that throws ArgumentError('n must not be negative') for a negative n. Call square(4) and square(-2) in one try, and print the caught error.
  2. What do print(FormatException('Bad date')) and print(StateError('Closed')) show?
  3. Write class Lamp whose turnOff() throws StateError('Lamp is already off') while bool _on is false. Call it on a new lamp, catch, and print.

Application

  1. Write an arrow function String capital(String country) that looks up {'Japan': 'Tokyo'} and throws StateError('Unknown: $country') with ?? (throw ...). Print capital('Japan'), then catch and print capital('Peru').
  2. Write Never invalid(String reason) that throws ArgumentError(reason), and use it in int age(int? years) to reject null with 'Age is required'. Print age(21), then catch and print age(null).
  3. Create EmptyCartException implements Exception whose toString returns 'Cart is empty'. Write int checkout(List<int> prices) that throws it for an empty list and returns the sum otherwise. Print checkout([500, 250]), then catch and print checkout([]).
  4. Create PasswordTooShortException with a field length. Throw it from setPassword('abc') (minimum 8 characters) and print Too short: 3.

Challenge

  1. Create LibraryException (field message, toString) and a child BookNotFoundException(title) saying Not found: <title>. borrow(title) reads {'Dart Basics': true} (true = borrowed), throws the child for a missing title and LibraryException('Already borrowed: <title>') for a borrowed one. Borrow 'Dart Basics' and 'Python 101' with one on LibraryException.
  2. Write BankAccount(this._balance) that throws ArgumentError.value when negative, and withdraw that throws InsufficientFundsException (fields balance, requested). Ali opens with 1000, withdraws 400, then 700. Print Ali needs 100 more.
  3. Zayan's ScoreCard has int _score = 0 and a setter that throws RangeError.range(value, 0, 50, 'score'). Set 30, 60, then 45, printing each caught error, and finish with Score: 45.

Practice Answers

Question 1 — Solution

dart
int square(int n) {
  if (n < 0) throw ArgumentError('n must not be negative');
  return n * n;
}

void main() {
  try {
    print(square(4));
    print(square(-2));
  } on ArgumentError catch (e) {
    print(e);
  }
}
output
16
Invalid argument(s): n must not be negative

Question 2 — Solution

dart
void main() {
  print(FormatException('Bad date'));
  print(StateError('Closed'));
}
output
FormatException: Bad date
Bad state: Closed

Question 3 — Solution

dart
class Lamp {
  bool _on = false;

  void turnOff() {
    if (!_on) throw StateError('Lamp is already off');
    _on = false;
  }
}

void main() {
  try {
    Lamp().turnOff();
  } on StateError catch (e) {
    print(e);
  }
}
output
Bad state: Lamp is already off

Question 4 — Solution

dart
String capital(String country) =>
    {'Japan': 'Tokyo'}[country] ?? (throw StateError('Unknown: $country'));

void main() {
  print(capital('Japan'));
  try {
    capital('Peru');
  } on StateError catch (e) {
    print(e);
  }
}
output
Tokyo
Bad state: Unknown: Peru

Question 5 — Solution

dart
Never invalid(String reason) => throw ArgumentError(reason);

int age(int? years) {
  if (years == null) invalid('Age is required');
  return years;
}

void main() {
  print(age(21));
  try {
    age(null);
  } on ArgumentError catch (e) {
    print(e);
  }
}
output
21
Invalid argument(s): Age is required

return years; needs no ! because invalid returns Never.

Question 6 — Solution

dart
class EmptyCartException implements Exception {
  @override
  String toString() => 'Cart is empty';
}

int checkout(List<int> prices) {
  if (prices.isEmpty) throw EmptyCartException();
  return prices.reduce((a, b) => a + b);
}

void main() {
  print(checkout([500, 250]));
  try {
    checkout([]);
  } on EmptyCartException catch (e) {
    print(e);
  }
}
output
750
Cart is empty

Question 7 — Solution

dart
class PasswordTooShortException implements Exception {
  final int length;
  PasswordTooShortException(this.length);
}

void setPassword(String p) {
  if (p.length < 8) throw PasswordTooShortException(p.length);
}

void main() {
  try {
    setPassword('abc');
  } on PasswordTooShortException catch (e) {
    print('Too short: ${e.length}');
  }
}
output
Too short: 3

Question 8 — Solution

dart
class LibraryException implements Exception {
  final String message;
  LibraryException(this.message);

  @override
  String toString() => message;
}

class BookNotFoundException extends LibraryException {
  BookNotFoundException(String title) : super('Not found: $title');
}

final books = {'Dart Basics': true};

void borrow(String title) {
  var borrowed = books[title] ?? (throw BookNotFoundException(title));
  if (borrowed) throw LibraryException('Already borrowed: $title');
}

void main() {
  for (var title in ['Dart Basics', 'Python 101']) {
    try {
      borrow(title);
    } on LibraryException catch (e) {
      print(e);
    }
  }
}
output
Already borrowed: Dart Basics
Not found: Python 101

Question 9 — Solution

dart
class InsufficientFundsException implements Exception {
  final int balance;
  final int requested;
  InsufficientFundsException(this.balance, this.requested);
}

class BankAccount {
  int _balance;

  BankAccount(this._balance) {
    if (_balance < 0) throw ArgumentError.value(_balance, 'balance');
  }

  void withdraw(int amount) {
    if (amount > _balance) throw InsufficientFundsException(_balance, amount);
    _balance -= amount;
  }
}

void main() {
  var ali = BankAccount(1000);
  ali.withdraw(400);
  try {
    ali.withdraw(700);
  } on InsufficientFundsException catch (e) {
    print('Ali needs ${e.requested - e.balance} more');
  }
}
output
Ali needs 100 more

Question 10 — Solution

dart
class ScoreCard {
  int _score = 0;

  int get score => _score;

  set score(int value) {
    if (value < 0 || value > 50) throw RangeError.range(value, 0, 50, 'score');
    _score = value;
  }
}

void main() {
  var zayan = ScoreCard();
  for (var value in [30, 60, 45]) {
    try {
      zayan.score = value;
    } on RangeError catch (e) {
      print(e);
    }
  }
  print('Score: ${zayan.score}');
}
output
RangeError (score): Invalid value: Not in inclusive range 0..50: 60
Score: 45

Quick Revision

  1. What happens to the rest of a function after throw runs?
  2. Which built-in type fits "the object is not ready for this call"?
  3. Why does ?? throw ... need parentheses?
  4. What return type does a function that always throws have?
  5. Why implements Exception and not extends?
  6. In which order do on clauses for a parent and a child go?

Key Takeaways

  • throw stops the function and hands an object to the caller; uncaught, it ends the program.
  • ArgumentError, RangeError, StateError, and FormatException cover common problems.
  • throw is an expression: use it after ?? in parentheses, or as an => body.
  • A function that always throws returns Never, and Dart promotes variables after calling it.
  • Custom exceptions implements Exception, carry details in final fields, and override toString; a shared parent lets callers catch the whole family.
  • Throwing from constructors and setters keeps invalid objects from ever existing.

What's Next?

Lecture 086 — Rethrowing, Exception Design & Assertions. Sometimes a function must catch an exception, log it, and still pass it on. Next: rethrow, designing exceptions for a whole program, and assert for checking your own assumptions during development.


Video lecture

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