Watch the video (17 min)
On this page25 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Catch, Act, Pass On: rethrow
  5. 2. rethrow vs throw e: The Stack Trace
  6. 3. Wrapping a Low-Level Exception
  7. 4. Keeping the Original Trace: Error.throwWithStackTrace
  8. 5. Rule 1 — Throw Early
  9. 6. Rule 2 — Catch Where You Can Act
  10. 7. Rule 3 — Never Swallow an Exception
  11. 8. Rule 4 — Specific Types, Messages with Context
  12. 9. An Alternative: Sealed Result Types
  13. 10. assert: Checks for Developers
  14. 11. assert in an Initializer List
  15. 12. assert or Exception?
  16. 13. A Real-World Example
  17. Code Examples
  18. Common Beginner Mistakes
  19. Best Practices
  20. Mini Challenge
  21. Practice Questions
  22. Practice Answers
  23. Quick Revision
  24. Key Takeaways
  25. What's Next?

Lecture 86 — Rethrowing, Exception Design & Assertions

Category14 — Error & Exception Handling
Lecture086 of 140
Estimated video length17 min
TargetDart 3.12.2
LevelIntermediate

You can throw, catch, and define your own exceptions. Now you decide where each one should be thrown, caught, or passed on, and you add assert checks that catch your own mistakes while you develop.

Contents

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

Every error, warning and stack trace in this lecture is real. Errors come from dart run, lint messages from dart analyze, on Dart 3.12.2. Long file paths are shortened to …, and the extra stack-trace lines after a failed assertion are left out.


Prerequisites

Students should already understand, from Lectures 054–085:

  • constructors and initializer lists (Lecture 054)
  • generic classes (Lecture 073) and switch expressions on patterns (Lecture 081)
  • sealed classes and exhaustive switches over them (Lecture 082)
  • the difference between an Error and an Exception (Lecture 083)
  • try, on, catch (e, s), and finally (Lecture 084)
  • throw and custom exception classes (Lecture 085)

Connection With Previous Lectures

We can now throw and catch exceptions, but real programs have layers: a small function reads a number, a bigger one builds a report, main() talks to the user. When the small function fails, which layer should catch it? Can a layer note the problem and still pass it upward? And how do we catch bugs in our own code, not just bad input?


Learning Objectives

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

  • Use rethrow to pass on an exception with its original stack trace
  • Explain why throw e loses where the problem started
  • Wrap a low-level exception in a domain exception, with Error.throwWithStackTrace if needed
  • Apply the design rules: throw early, catch where you can act, never swallow
  • Catch specific types and write messages that carry context
  • Return a sealed Result type instead of throwing, when failure is an expected outcome
  • Write assert checks and run them with dart run --enable-asserts
  • Put assert in a constructor's initializer list

1. Catch, Act, Pass On: rethrow

Sometimes a function wants to notice an exception — to log it — while the caller still decides what to do. Inside a catch block, rethrow throws the same exception on upward.

Analogy — a sorting-office clerk notes a damaged parcel in the log book, then sends the same parcel on, original label and all, to the office that can deal with it.

dart
int parseMarks(String text) {
  var marks = int.tryParse(text);
  if (marks == null) {
    throw FormatException('Not a number: "$text"');
  }
  return marks;
}

int loadMarks(String text) {
  try {
    return parseMarks(text);
  } catch (e) {
    print('log: could not read "$text"');
    rethrow;
  }
}

void main() {
  try {
    loadMarks('eighty');
  } on FormatException catch (e) {
    print('main: $e');
  }
}
output
log: could not read "eighty"
main: FormatException: Not a number: "eighty"

loadMarks logged it; main() received it unchanged.


2. rethrow vs throw e: The Stack Trace

throw e; throws the same exception, but with a new stack trace starting at the catch, so it no longer shows where the problem began. firstFrame prints a trace's first line:

dart
String firstFrame(StackTrace s) =>
    s.toString().split('\n').first.split(' (').first;

int parseMarks(String text) =>
    int.tryParse(text) ?? (throw FormatException('Not a number: "$text"'));

int withRethrow(String text) {
  try {
    return parseMarks(text);
  } catch (e) {
    rethrow;
  }
}

int withThrowE(String text) {
  try {
    return parseMarks(text);
  } catch (e) {
    throw e;
  }
}

void main() {
  for (var load in [withRethrow, withThrowE]) {
    try {
      load('abc');
    } catch (e, s) {
      print(firstFrame(s));
    }
  }
}
output
#0      parseMarks
#0      withThrowE

rethrow still points at parseMarks, the real culprit. dart analyze flags throw e:

output
info - Use 'rethrow' to rethrow a caught exception. Try replacing the 'throw' with a
'rethrow'. - use_rethrow_when_possible

3. Wrapping a Low-Level Exception

A FormatException from deep inside means little to the code that asked for a report. Throw a domain exception instead — one in your program's own language — keeping the original as its cause:

dart
class ReportException implements Exception {
  final String message;
  final Object? cause;
  ReportException(this.message, {this.cause});

  @override
  String toString() => 'ReportException: $message';
}

int loadMarks(String student, String text) {
  try {
    return int.parse(text);
  } on FormatException catch (e) {
    throw ReportException('Marks for $student are unreadable', cause: e);
  }
}

void main() {
  try {
    loadMarks('Sara', '9O');
  } on ReportException catch (e) {
    print(e);
    print('cause: ${e.cause}');
  }
}
output
ReportException: Marks for Sara are unreadable
cause: FormatException: Invalid radix-10 number (at character 1)
9O
^

Callers catch ReportException; the details stay available for debugging.


4. Keeping the Original Trace: Error.throwWithStackTrace

A wrapped exception gets a new stack trace that starts at the catch. To throw the new exception with the old trace, use Error.throwWithStackTrace(exception, stackTrace):

dart
String firstFrame(StackTrace s) =>
    s.toString().split('\n').first.split(' (').first;

int parseMarks(String text) =>
    int.tryParse(text) ?? (throw FormatException('Not a number: "$text"'));

int loadMarks(String student, String text) {
  try {
    return parseMarks(text);
  } on FormatException catch (e, s) {
    Error.throwWithStackTrace(Exception('$student: ${e.message}'), s);
  }
}

void main() {
  try {
    loadMarks('Zayan', 'n/a');
  } catch (e, s) {
    print(e);
    print(firstFrame(s));
  }
}
output
Exception: Zayan: Not a number: "n/a"
#0      parseMarks

Its return type is Never (Lecture 075), so Dart knows loadMarks never falls off the end.


5. Rule 1 — Throw Early

Check inputs at the start of a function, before anything changes. A half-done operation is harder to undo than one that never started:

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

class Account {
  double balance;
  Account(this.balance);

  void withdraw(double amount) {
    if (amount <= 0) {
      throw ArgumentError.value(amount, 'amount', 'must be positive');
    }
    if (amount > balance) {
      throw InsufficientFundsException('balance $balance is less than $amount');
    }
    balance -= amount;
  }
}

void main() {
  var account = Account(1000);
  try {
    account.withdraw(1500);
  } on InsufficientFundsException catch (e) {
    print(e.message);
  }
  print('Balance: ${account.balance}');
}
output
balance 1000.0 is less than 1500.0
Balance: 1000.0

A negative amount is a caller's bug: ArgumentError. Too little money is a normal situation to handle: an Exception (Lecture 083).


6. Rule 2 — Catch Where You Can Act

Catch an exception only where you can do something useful: show a message, skip an item, try again. parseMarks just throws; the loop in main() decides to skip and report:

dart
int parseMarks(String text) {
  var marks = int.tryParse(text);
  if (marks == null) {
    throw FormatException('Not a number: "$text"');
  }
  return marks;
}

void main() {
  var lines = ['80', 'x', '70'];
  var total = 0;
  for (var line in lines) {
    try {
      total += parseMarks(line);
    } on FormatException catch (e) {
      print('Skipped: ${e.message}');
    }
  }
  print('Total: $total');
}
output
Skipped: Not a number: "x"
Total: 150

7. Rule 3 — Never Swallow an Exception

An empty catch swallows the exception. Replace the catch in section 6 with this:

dart
// } catch (e) {} // ✗ swallowed

It still prints Total: 150, but now nobody knows a line was lost, and the analyzer warns:

output
info - Empty catch block. Try adding statements to the block, adding a comment to the
block, or removing the 'catch' clause. - empty_catches

If ignoring really is correct, say why in a comment inside the block.


8. Rule 4 — Specific Types, Messages with Context

A bare catch (e) catches everything, including bugs. Here the code reads parts[2] instead of parts[1], and the catch-all reports a "bad number":

dart
void main() {
  var parts = ['Ali', '80'];
  try {
    print(int.parse(parts[2])); // bug: should be parts[1]
  } catch (e) {
    print('Invalid number');
  }
}
output
Invalid number

With on FormatException the RangeError would surface at once. Messages need context, too: 'Not a number: "x"' beats 'Invalid input'. Include the value and what was expected.


9. An Alternative: Sealed Result Types

When failure is an expected outcome, like letters typed into a marks field, return it instead of throwing. A sealed class (Lecture 082) describes both outcomes:

dart
sealed class Result<T> {}

class Ok<T> extends Result<T> {
  final T value;
  Ok(this.value);
}

class Failure<T> extends Result<T> {
  final String message;
  Failure(this.message);
}

Result<int> parseMarks(String text) {
  var marks = int.tryParse(text);
  if (marks == null) return Failure('Not a number: "$text"');
  if (marks < 0 || marks > 100) return Failure('Out of range: $marks');
  return Ok(marks);
}

void main() {
  for (var text in ['85', 'abc', '120']) {
    var message = switch (parseMarks(text)) {
      Ok(:var value) => 'Marks: $value',
      Failure(:var message) => 'Problem: $message',
    };
    print(message);
  }
}
output
Marks: 85
Problem: Not a number: "abc"
Problem: Out of range: 120

Forgetting the Failure arm is a compile error. Use exceptions for the unexpected; use Result when failure is one of the normal answers.


10. assert: Checks for Developers

assert(condition, message) stops the program if condition is false. Use it for facts that are always true if your code is correct:

dart
double average(List<int> marks) {
  assert(marks.isNotEmpty, 'average() needs at least one mark');
  var total = 0;
  for (var m in marks) {
    total += m;
  }
  return total / marks.length;
}

void main() {
  print(average([80, 90]));
  print(average([]));
}

The catch: assertions are switched off by default. A plain dart run skips them:

output
$ dart run main.dart
85.0
NaN

Turn them on with --enable-asserts:

output
$ dart run --enable-asserts main.dart
85.0
Unhandled exception:
'file:///…/main.dart': Failed assertion: line 2 pos 10: 'marks.isNotEmpty': average() needs at least one mark

A failed assert throws an AssertionError — an Error, not meant to be caught. Tests (Lecture 118 onward) run with assertions on.


11. assert in an Initializer List

In Lecture 054 we met : assert(price >= 0) in a constructor. The initializer list runs before the object exists, so a bad value is stopped at the door:

dart
class Product {
  final String name;
  final double price;

  Product(this.name, this.price)
    : assert(name.isNotEmpty, 'name must not be empty'),
      assert(price >= 0, 'price must not be negative, got $price');
}

void main() {
  var pen = Product('Pen', -50);
  print('${pen.name}: ${pen.price}');
}
output
$ dart run main.dart
Pen: -50.0

$ dart run --enable-asserts main.dart
Unhandled exception:
'file:///…/main.dart': Failed assertion: line 7 pos 14: 'price >= 0': price must not be negative, got -50.0

Separate several asserts with commas.


12. assert or Exception?

Because assertions vanish without the flag, they are never real validation:

SituationUse
Bad input from a user, file or networkthrow an Exception (or return a Result)
A caller passes a wrong argument to a public functionthrow ArgumentError
"This can't happen if my code is right"assert

13. A Real-World Example

Ali's bank app transfers money. Every rule from this lecture appears once:

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

class Account {
  final String owner;
  double balance;

  Account(this.owner, this.balance)
    : assert(owner.isNotEmpty, 'owner must not be empty');

  void withdraw(double amount) {
    if (amount > balance) {
      throw StateError('$owner has $balance, needs $amount');
    }
    balance -= amount;
  }
}

void transfer(Account from, Account to, double amount) {
  if (amount <= 0) {
    throw ArgumentError.value(amount, 'amount', 'must be positive');
  }
  try {
    from.withdraw(amount);
    to.balance += amount;
  } on StateError catch (e, s) {
    print('log: ${e.message}');
    Error.throwWithStackTrace(
      TransferException('${from.owner} → ${to.owner} failed'),
      s,
    );
  }
}

void main() {
  var ali = Account('Ali', 500);
  var sara = Account('Sara', 200);
  for (var amount in [300.0, 400.0]) {
    try {
      transfer(ali, sara, amount);
      print('Sent $amount. Ali: ${ali.balance}, Sara: ${sara.balance}');
    } on TransferException catch (e) {
      print('Sorry: ${e.message}');
    }
  }
}
output
Sent 300.0. Ali: 200.0, Sara: 500.0
log: Ali has 200.0, needs 400.0
Sorry: Ali → Sara failed

An assert guards the constructor, transfer throws early, the StateError is logged and wrapped with its trace, and main() catches only what it can act on.


Code Examples

Example 1 — Log and Rethrow

dart
void main() {
  try {
    try {
      int.parse('12a');
    } catch (e) {
      print('inner: noted');
      rethrow;
    }
  } on FormatException {
    print('outer: handled');
  }
}
output
inner: noted
outer: handled

Example 2 — Handle Some, Rethrow the Rest

dart
int readQuantity(String text) {
  try {
    return int.parse(text);
  } on FormatException {
    if (text.isEmpty) return 0;
    rethrow;
  }
}

void main() {
  print(readQuantity(''));
  try {
    readQuantity('two');
  } on FormatException {
    print('"two" is not a quantity');
  }
}
output
0
"two" is not a quantity

Example 3 — Wrapping in a Domain Exception

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

int readPort(String raw) {
  try {
    return int.parse(raw);
  } on FormatException {
    throw ConfigException('port "$raw" is not a number');
  }
}

void main() {
  try {
    readPort('eighty');
  } on ConfigException catch (e) {
    print(e.message);
  }
}
output
port "eighty" is not a number

Example 4 — Error.throwWithStackTrace

dart
void main() {
  try {
    try {
      throw FormatException('bad data');
    } on FormatException catch (e, s) {
      Error.throwWithStackTrace(Exception('Import failed: ${e.message}'), s);
    }
  } catch (e) {
    print(e);
  }
}
output
Exception: Import failed: bad data

Example 5 — An Assertion with a Message

dart
void main() {
  var items = <String>[];
  assert(items.isNotEmpty, 'cart must not be empty');
  print('Checking out ${items.length} items');
}
output
$ dart run main.dart
Checking out 0 items

$ dart run --enable-asserts main.dart
Unhandled exception:
'file:///…/main.dart': Failed assertion: line 3 pos 10: 'items.isNotEmpty': cart must not be empty

Example 6 — Assert in an Initializer List

dart
class Temperature {
  final double kelvin;
  Temperature(this.kelvin) : assert(kelvin >= 0, 'below absolute zero');
}

void main() {
  var t = Temperature(300);
  print('${t.kelvin} K');
}
output
300.0 K

A valid value passes in both modes.

Example 7 — Student Marks: Specific Types

dart
String grade(int marks) {
  if (marks < 0 || marks > 100) {
    throw RangeError.range(marks, 0, 100, 'marks');
  }
  return marks >= 50 ? 'Pass' : 'Fail';
}

void main() {
  for (var m in [72, 140]) {
    try {
      print('$m → ${grade(m)}');
    } on RangeError catch (e) {
      print(e);
    }
  }
}
output
72 → Pass
RangeError (marks): Invalid value: Not in inclusive range 0..100: 140

Example 8 — Shopping Total, Skipping Bad Lines

dart
void main() {
  var prices = {'Pen': '50', 'Bag': 'free', 'Book': '400'};
  var total = 0;
  prices.forEach((item, price) {
    try {
      total += int.parse(price);
    } on FormatException {
      print('Skipped $item: "$price" is not a price');
    }
  });
  print('Total: $total');
}
output
Skipped Bag: "free" is not a price
Total: 450

Example 9 — Bank Balance: Throw Early

dart
void deposit(List<double> history, double amount) {
  if (amount <= 0) {
    throw ArgumentError.value(amount, 'amount', 'must be positive');
  }
  history.add(amount);
}

void main() {
  var history = <double>[];
  deposit(history, 500);
  try {
    deposit(history, -20);
  } on ArgumentError catch (e) {
    print(e);
  }
  print(history);
}
output
Invalid argument (amount): must be positive: -20.0
[500.0]

The bad deposit never reached the list. (Catching an Error is only for this demo.)

Example 10 — Temperature Sensor: Wrap and Report

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

double readCelsius(String raw) {
  try {
    return double.parse(raw);
  } on FormatException {
    throw SensorException('sensor sent "$raw" instead of a number');
  }
}

void main() {
  for (var raw in ['21.5', 'ERR']) {
    try {
      print('${readCelsius(raw)} °C');
    } on SensorException catch (e) {
      print(e.message);
    }
  }
}
output
21.5 °C
sensor sent "ERR" instead of a number

Common Beginner Mistakes

1. rethrow outside a catch

dart
// void check(int marks) {
//   if (marks < 0) rethrow; // ✗
// }
output
Error: 'rethrow' can only be used in catch clauses.

Outside a catch, use throw with a new exception.

2. throw e instead of rethrow — the trace loses its origin (section 2).

3. An empty catch — the result is silently wrong (section 7).

4. A bare catch (e) around everything — it hides bugs like a RangeError (section 8).

5. Using assert for user input — a plain dart run skips it (section 11).


Best Practices

  • Pass a caught exception on with rethrow, never throw e.
  • Never leave a catch empty without a comment explaining why.
  • Put the bad value and the expectation in every message.
  • Return a sealed Result when failure is a normal answer.
  • Develop with --enable-asserts on.

Mini Challenge

Write int parseAge(String text) that wraps a FormatException in an AgeException('Age "<text>" is not a number') and asserts the age is not negative. In main(), try '25' and 'old'.

output
Age: 25
Age "old" is not a number

Try it yourself before opening the solution.

Show solution
dart
class AgeException implements Exception {
  final String message;
  AgeException(this.message);
}

int parseAge(String text) {
  try {
    var age = int.parse(text);
    assert(age >= 0, 'age must not be negative');
    return age;
  } on FormatException {
    throw AgeException('Age "$text" is not a number');
  }
}

void main() {
  for (var text in ['25', 'old']) {
    try {
      print('Age: ${parseAge(text)}');
    } on AgeException catch (e) {
      print(e.message);
    }
  }
}
output
Age: 25
Age "old" is not a number

Practice Questions

They run from beginner to application to challenge.

Beginner

  1. Write a try-catch around int.parse('x') that prints Logging and then uses rethrow. Wrap it in an outer try that prints Caught outside.
  2. Write assert(1 > 2, 'math is broken'); followed by print('Done');. What does dart run print? What does dart run --enable-asserts print first?
  3. Give a class Box a field final int size and a constructor with assert(size > 0) in its initializer list. Create Box(3) and print its size.

Application

  1. Write double parsePrice(String text) that turns a FormatException into PriceException('Price "<text>" is invalid'). Call it with '12.5' and 'abc'.
  2. Store marks = ['90', '', '75', 'A']. Sum them, skipping bad entries with a message Skipped: "<value>" for each, then print Total: 165.
  3. Choose assert, ArgumentError, or an Exception for each: (a) a user types -5 as a quantity; (b) a private helper is called with an empty list, which your code never does; (c) a public withdraw(amount) is called with amount = 0.
  4. Using the Result, Ok and Failure classes from section 9, write Result<double> divide(int a, int b) returning Failure('Cannot divide by zero') when b == 0. Print both outcomes for divide(10, 4) and divide(1, 0).

Challenge

  1. What does this print, and what does dart analyze report?

    dart
    void main() {
      var total = 0;
      for (var t in ['5', 'five', '10']) {
        try {
          total += int.parse(t);
        } catch (e) {}
      }
      print(total);
    }
  2. Rewrite question 4 so the PriceException keeps the FormatException's stack trace.

  3. Sara's score keeper: int addScore(int score, String points) adds points to score, treats empty points as 0, and rethrows any other FormatException. In main(), start at 10 and add '5', '', 'x', printing Ignored "x", then Score: 15.


Practice Answers

Question 1 — Solution

dart
void main() {
  try {
    try {
      int.parse('x');
    } catch (e) {
      print('Logging');
      rethrow;
    }
  } catch (e) {
    print('Caught outside');
  }
}
output
Logging
Caught outside

Question 2 — Solution

dart
void main() {
  assert(1 > 2, 'math is broken');
  print('Done');
}
output
$ dart run main.dart
Done

$ dart run --enable-asserts main.dart
Unhandled exception:
'file:///…/main.dart': Failed assertion: line 2 pos 10: '1 > 2': math is broken

Question 3 — Solution

dart
class Box {
  final int size;
  Box(this.size) : assert(size > 0);
}

void main() {
  print(Box(3).size);
}
output
3

Question 4 — Solution

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

double parsePrice(String text) {
  try {
    return double.parse(text);
  } on FormatException {
    throw PriceException('Price "$text" is invalid');
  }
}

void main() {
  for (var text in ['12.5', 'abc']) {
    try {
      print(parsePrice(text));
    } on PriceException catch (e) {
      print(e.message);
    }
  }
}
output
12.5
Price "abc" is invalid

Question 5 — Solution

dart
void main() {
  var marks = ['90', '', '75', 'A'];
  var total = 0;
  for (var m in marks) {
    try {
      total += int.parse(m);
    } on FormatException {
      print('Skipped: "$m"');
    }
  }
  print('Total: $total');
}
output
Skipped: ""
Skipped: "A"
Total: 165

Question 6 — Solution

(a) an Exception (or a Result): bad user input is expected. (b) assert: it can only happen through your own bug. (c) ArgumentError: the caller broke the function's rules.

Question 7 — Solution

Add the three classes from section 9 above this code.

dart
Result<double> divide(int a, int b) {
  if (b == 0) return Failure('Cannot divide by zero');
  return Ok(a / b);
}

void main() {
  for (var r in [divide(10, 4), divide(1, 0)]) {
    print(switch (r) {
      Ok(:var value) => 'Result: $value',
      Failure(:var message) => 'Error: $message',
    });
  }
}
output
Result: 2.5
Error: Cannot divide by zero

Question 8 — Solution

output
15

The empty catch swallows the FormatException for 'five', so nobody learns a value was lost. dart analyze reports Empty catch block … - empty_catches.

Question 9 — Solution

Only parsePrice changes; the class and main() stay as in Question 4.

dart
double parsePrice(String text) {
  try {
    return double.parse(text);
  } on FormatException catch (e, s) {
    Error.throwWithStackTrace(PriceException('Price "$text" is invalid'), s);
  }
}
output
12.5
Price "abc" is invalid

Same output, but the stack trace now begins inside double.parse, where the bad text was found.

Question 10 — Solution

dart
int addScore(int score, String points) {
  try {
    return score + int.parse(points);
  } on FormatException {
    if (points.isEmpty) return score;
    rethrow;
  }
}

void main() {
  var score = 10;
  for (var p in ['5', '', 'x']) {
    try {
      score = addScore(score, p);
    } on FormatException {
      print('Ignored "$p"');
    }
  }
  print('Score: $score');
}
output
Ignored "x"
Score: 15

Quick Revision

  1. What does rethrow do, and where can it be written?
  2. How does the stack trace differ between rethrow and throw e?
  3. Why wrap a FormatException in a domain exception?
  4. What does Error.throwWithStackTrace keep that a plain throw loses?
  5. What are the four exception design rules from this lecture?
  6. When is a sealed Result better than throwing?

Key Takeaways

  • rethrow keeps the original stack trace; throw e starts a new one at the catch.
  • Wrap low-level exceptions in domain exceptions, keeping the original as a cause or its trace with Error.throwWithStackTrace.
  • Throw early, catch where you can act, never swallow, catch specific types, and write messages with context.
  • A sealed Result makes an expected failure part of the return type.
  • assert runs only with --enable-asserts; it catches the developer's mistakes, never the user's — also in initializer lists.

What's Next?

Lecture 087 — Synchronous vs Asynchronous Programming. That closes error handling. So far every line waited for the one before it. Category 15 asks what happens when a task, like waiting for a server, takes seconds, and how Dart keeps working meanwhile.


Video lecture

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