On this page20 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. Compile-Time vs Runtime Errors
- 2. What Happens When a Program Crashes
- 3. Reading a Stack Trace
- 4. Two Families: Error and Exception
- 5. The Error Family: Programmer Bugs
- 6. The Exception Family: Expected Failures
- 7. Fix Errors, Handle Exceptions
- 8. 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 83 — Errors vs Exceptions
| Category | 14 — Error & Exception Handling |
| Lecture | 083 of 140 |
| Estimated video length | 8 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
Since Lecture 006 we have cut off the lines Dart prints under
Unhandled exception:. Today we read them — and learn that every crash is either a bug to fix or a failure to plan for.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message and stack trace in this lecture is real, produced with
dart runon Dart 3.12.2. Only the folder part of file paths is replaced with<your-folder>.
Prerequisites
Students should already understand, from Lectures 001–082:
- the runtime crashes met so far:
RangeError,Bad state, failed casts,FormatException,Stack Overflow(Lectures 010, 011, 038, 040, 042) int.tryParseand??(Lectures 010, 014)- functions calling functions (Lectures 030–038)
- classes, inheritance,
is, andObject(Lectures 011, 053–065) - object patterns in a
switch(Lectures 080–081)
Connection With Previous Lectures
Here are two crashes we have already seen:
Unhandled exception:
RangeError (length): Invalid value: Not in inclusive range 0..2: 3Unhandled exception:
FormatException: Invalid radix-10 number (at character 1)
twentyThe first came from a loop that read one index too far — our mistake. The second came
from int.parse('twenty') — the code was fine, the input was bad. Both stop the program
with the same words, yet one needs a code change and the other cannot be prevented by any
code change. Dart separates the two, and so should we.
Learning Objectives
By the end of this lecture you will be able to:
- Distinguish compile-time errors from runtime crashes
- Describe what Dart does when a problem is not handled
- Read a stack trace line by line and find the line to fix
- Explain the difference between Dart's
ErrorandExceptionfamilies - Name the common
Errortypes:RangeError,TypeError,StateError,ArgumentError,NoSuchMethodError,StackOverflowError - Name the common
Exceptiontypes:FormatExceptionand I/O exceptions - Decide whether a crash should be fixed or handled
1. Compile-Time vs Runtime Errors
A compile-time error is found before the program starts. Nothing runs — not even the lines above the mistake:
void main() {
print('Start');
// int count = 'five'; // ✗
}main.dart:3:15: Error: A value of type 'String' can't be assigned to a variable of type 'int'.
int count = 'five';
^A runtime error happens while the program runs. Everything before it works; then it stops:
void main() {
var marks = [85, 92];
print('Start');
print(marks[5]);
print('End');
}Start
Unhandled exception:
RangeError (length): Invalid value: Not in inclusive range 0..1: 5End never prints. The rest of this category is about runtime problems.
2. What Happens When a Program Crashes
When something goes wrong at runtime, Dart throws an object that describes the problem. If no code deals with it, Dart:
- prints
Unhandled exception:, - prints the object's message (
RangeError (length): …), - prints a stack trace — the list of function calls that were running,
- stops the program.
Analogy — a stack trace is like a trail of breadcrumbs. It shows every function the program walked through to reach the spot where it fell.
The words "Unhandled exception" appear for every crash, whatever its family. "Unhandled" is the important word: nothing caught the problem.
3. Reading a Stack Trace
This program has the <= bug from Lecture 040, buried two functions deep:
double average(List<int> marks) {
var total = 0;
for (var i = 0; i <= marks.length; i++) {
total += marks[i];
}
return total / marks.length;
}
void printReport(String name, List<int> marks) {
print('Report for $name');
print('Average: ${average(marks)}');
}
void main() {
printReport('Ali', [85, 92, 78]);
print('Done');
}Report for Ali
Unhandled exception:
RangeError (length): Invalid value: Not in inclusive range 0..2: 3
#0 List.[] (dart:core-patch/growable_array.dart)
#1 average (file:///<your-folder>/main.dart:4:19)
#2 printReport (file:///<your-folder>/main.dart:11:21)
#3 main (file:///<your-folder>/main.dart:15:3)
#4 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:313:19)
#5 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)Each # line is a frame: one function call. Each frame shows the function's name, the
file, and line:column.
| Line | Meaning |
|---|---|
#0 List.[] | The crash happened inside the list's [] operator — Dart's own code (dart:core). |
#1 average … main.dart:4:19 | [] was called by average, line 4, column 19: marks[i]. |
#2 printReport … 11:21 | average was called from printReport, line 11. |
#3 main … 15:3 | printReport was called from main, line 15. |
#4, #5 | Dart's start-up code that called main. Ignore them. |
Read top to bottom to go from the crash back to main. Read bottom to top to replay
the program: main → printReport → average → []. The line to fix is usually the
first frame in your own file — here main.dart:4, inside the loop whose <= should be
<.
4. Two Families: Error and Exception
Every thrown object belongs to a class. In Dart's core library, most of them belong to one of two families:
Error— a bug in the program. The code did something it never should.Exception— an expected failure. The code was right, but the world was not: bad input, a missing file, a dropped connection.
You can create these objects without throwing them and check their family with is:
void main() {
Object bug = RangeError('index too big');
Object failure = FormatException('not a number');
print(bug is Error);
print(bug is Exception);
print(failure is Exception);
print(failure is Error);
}true
false
true
falseAn object belongs to one family, not both. The one leftover is the
IntegerDivisionByZeroException from Lecture 017 (10 ~/ 0), which counts as both and is now
deprecated. Creating and throwing your own is Lecture 085.
5. The Error Family: Programmer Bugs
| Class | Message starts with | Typical cause |
|---|---|---|
RangeError | RangeError | index or position outside the valid range |
TypeError | type 'X' is not a subtype of type 'Y' | a failed as cast |
StateError | Bad state | first, single, reduce on an empty list |
ArgumentError | Invalid argument(s) | a function got a value it cannot accept |
NoSuchMethodError | NoSuchMethodError | calling a missing method on a dynamic value |
StackOverflowError | Stack Overflow | recursion without a base case (Lecture 038) |
RangeError is a special kind of ArgumentError — it extends it. One we have not seen
before is ArgumentError itself. clamp(low, high) needs low to be at most high:
void main() {
var mark = 105;
print(mark.clamp(100, 0));
}Unhandled exception:
Invalid argument(s): 100Every one of these means the code is wrong. Swapping the two numbers fixes it for good.
6. The Exception Family: Expected Failures
| Class | Typical cause |
|---|---|
FormatException | text that is not in the expected format — int.parse('12a'), double.parse('12,5') |
IOException types | the outside world fails: FileSystemException (missing file), SocketException (no network) |
void main() {
var input = '12,5';
print(double.parse(input));
}Unhandled exception:
FormatException: Invalid double
12,5Nothing is wrong with this code. A user typed a comma instead of a dot. Tomorrow another user will type something else. Files and I/O exceptions come in Category 18.
7. Fix Errors, Handle Exceptions
This is the rule of the whole category:
- An
Error→ fix the code. The<=loop, the swappedclampvalues, the missing base case. After the fix, the crash can never happen again. - An
Exception→ handle it. You cannot stop people from typing'twenty'. Instead, plan what the program does when they do.
We already know one way to handle a bad number without crashing — tryParse returns null:
void main() {
var input = '12,5';
var price = double.tryParse(input) ?? 0.0;
print('Price: $price');
}Price: 0.0When no tryParse exists — reading a file, for example — Dart has try/catch, which looks
like try { … } on FormatException { … }. That is the whole of Lecture 084. Catching an
Error just to hide it is almost always wrong: the bug stays.
8. A Real-World Example
Sara's teacher types marks into a form. The text can be wrong (an Exception to plan for);
the empty-list case is a bug to prevent in code:
double average(List<int> marks) {
var total = 0;
for (var i = 0; i < marks.length; i++) {
total += marks[i];
}
return total / marks.length;
}
void main() {
var typed = ['85', 'abc', '92', '78'];
var marks = <int>[];
for (var text in typed) {
var mark = int.tryParse(text);
if (mark == null) {
print('Skipped "$text" — not a number');
} else {
marks.add(mark);
}
}
if (marks.isEmpty) {
print('No valid marks');
} else {
print('Highest: ${marks.reduce((a, b) => a > b ? a : b)}');
print('Average: ${average(marks)}');
}
}Skipped "abc" — not a number
Highest: 92
Average: 85.0The loop uses <, so no RangeError. isEmpty guards reduce, so no StateError. And
tryParse turns a would-be FormatException into a planned message.
Code Examples
Example 1 — A Runtime RangeError
void main() {
var names = ['Ali', 'Sara'];
print(names[0]);
print(names[2]);
}Ali
Unhandled exception:
RangeError (length): Invalid value: Not in inclusive range 0..1: 2Example 2 — A StateError
void main() {
var cart = <double>[];
print(cart.first);
}Unhandled exception:
Bad state: No elementExample 3 — A TypeError
void main() {
Object item = 20;
print((item as String).length);
}Unhandled exception:
type 'int' is not a subtype of type 'String' in type castExample 4 — A NoSuchMethodError
void main() {
dynamic score = 5;
score.shout();
}Unhandled exception:
NoSuchMethodError: Class 'int' has no instance method 'shout'.
Receiver: 5
Tried calling: shout()With dynamic, Dart could not check the call before running (Lecture 011).
Example 5 — A FormatException
void main() {
print(int.parse('12a'));
}Unhandled exception:
FormatException: Invalid radix-10 number (at character 1)
12a
^Dart repeats the text it could not read, so you can see exactly what the user typed.
Example 6 — Checking the Family
void main() {
Object a = StateError('empty');
Object b = ArgumentError('bad value');
Object c = StackOverflowError();
print(a is Error);
print(b is Error);
print(c is Error);
print(b is Exception);
}true
true
true
falseExample 7 — Student Marks: Fixing an Error
The crashing report from section 3, with <= changed to <:
double average(List<int> marks) {
var total = 0;
for (var i = 0; i < marks.length; i++) {
total += marks[i];
}
return total / marks.length;
}
void main() {
print('Report for Ali');
print('Average: ${average([85, 92, 78])}');
}Report for Ali
Average: 85.0How it works
- The trace pointed to
main.dart:4, themarks[i]inside the loop. - With
<,istops at2, the last valid index. - The fix is in the code; nothing needs to be "handled".
Example 8 — Shopping Total: Handling Bad Input
void main() {
var price = 450;
var typedQuantity = 'two';
var quantity = int.tryParse(typedQuantity) ?? 1;
print('Quantity: $quantity');
print('Total: ${price * quantity}');
}Quantity: 1
Total: 450How it works
int.parse('two')would throw aFormatException.tryParsereturnsnullinstead, and??chooses a default of1.- The input may be wrong next time too — so the program plans for it.
Example 9 — Bank Balance: Sorting Problems by Family
String advice(Object problem) {
return switch (problem) {
Error() => 'Bug: fix the code',
Exception() => 'Failure: handle it',
_ => 'Unknown',
};
}
void main() {
print(advice(RangeError('account index 7')));
print(advice(FormatException('amount "1o0"')));
print(advice(StateError('no transactions')));
}Bug: fix the code
Failure: handle it
Bug: fix the codeHow it works
Error()andException()are object patterns (Lecture 080) that test the type.RangeErrorandStateErrorextendError;FormatExceptionimplementsException._covers anything else, sinceObjecthas more subtypes than these two.
Example 10 — Temperature Readings
void main() {
var readings = ['21.5', 'ERR', '23.0', ''];
var valid = <double>[];
for (var text in readings) {
var value = double.tryParse(text);
if (value != null) {
valid.add(value);
}
}
print('Valid readings: $valid');
if (valid.isNotEmpty) {
print('Last: ${valid.last}');
}
}Valid readings: [21.5, 23.0]
Last: 23.0How it works
- A sensor sending
'ERR'is an expected failure;tryParseskips it. valid.laston an empty list would be aStateError— a bug — soisNotEmptyprevents it.
Common Beginner Mistakes
1. Reading only the message, not the trace
RangeError … 0..2: 3 says what went wrong, not where. The first frame in your own file
(main.dart:4:19) says where.
2. Stopping at frame #0
#0 is often inside dart:core. Dart's list code is not broken; skip down to your file.
3. Thinking "Unhandled exception" means an Exception
Dart prints those words for every crash. RangeError is still an Error.
4. Confusing a compile error with an Error object
Error: A value of type 'String' can't be assigned… comes from the compiler, before the
program runs. The Error family is about objects thrown while it runs.
5. Using parse on input that may be wrong
void main() {
var typed = 'abc';
// print(int.parse(typed)); // ✗ crashes for any non-number
print(int.tryParse(typed) ?? 0);
}06. Planning to "handle" a bug
A RangeError from <= is not bad luck — it happens every time. Fix the loop.
Best Practices
- Read a stack trace from the first frame in your own file.
- Treat every
Erroras a bug: change the code so it cannot happen. - Treat every
Exceptionas a possible outcome: decide what the program does then. - Guard list operations with
isEmpty/isNotEmptyand loops with<. - Prefer
tryParsefor text that comes from people or files. - Avoid
dynamicand uncheckedascasts — they move errors from compile time to runtime.
Mini Challenge
Zayan's program crashes. Read the error, find which family it belongs to, and fix it.
void main() {
var coins = [10, 5, 20];
// print('Last coin: ${coins[coins.length]}'); // ✗
}Unhandled exception:
RangeError (length): Invalid value: Not in inclusive range 0..2: 3Try it yourself before opening the solution.
Show solution
RangeError is in the Error family — a bug — so we fix the code:
void main() {
var coins = [10, 5, 20];
print('Last coin: ${coins.last}');
}Last coin: 20How it works
- The last valid index is
length - 1, which is2;coins[3]does not exist. lastreads the final element without any index arithmetic.
Practice Questions
Beginner
- Sort into
ErrororException:RangeError,FormatException,StateError,TypeError,ArgumentError,FileSystemException. - Which of these is a compile-time error, and which a runtime error?
(a)
int age = 'ten';(b)print([1, 2][5]); - Store
Object p = StateError('x');and print whetherpis anErrorand whether it is anException.
Application
-
Read this trace. Which function, line, and column should you look at first?
#0 RangeError.checkValidRange (dart:core/errors.dart:392:9) #1 _StringBase.substring (dart:core-patch/string_patch.dart:437:27) #2 initials (file:///<your-folder>/main.dart:2:15) #3 greet (file:///<your-folder>/main.dart:6:19) #4 main (file:///<your-folder>/main.dart:11:3) -
Using the trace in question 4, list the order in which the functions were called, starting with
main. -
This program crashes. Fix it so it prints
No marks yet:void main() { var marks = <int>[]; print(marks.first); } -
Read
'7x'as a quantity without crashing; use0if it is not a number. PrintQuantity: 0. -
Predict the full output, then name the class of the problem:
void main() { print('A'); print(10.clamp(20, 5)); print('B'); }
Challenge
- Write
String family(Object problem)that returns'Error','Exception', or'Other'. Test it withArgumentError('x'),FormatException('x'), and'oops'. - Sara's wallet app reads amounts typed by users:
['100', '2o0', '50']. Add the valid ones, report the bad one, and print the total. Then say which part of your program deals with anExceptionand which part prevents anError, if any.
Practice Answers
Question 1 — Solution
Error: RangeError, StateError, TypeError, ArgumentError.
Exception: FormatException, FileSystemException.
Question 2 — Solution
(a) is a compile-time error — the program never starts. (b) compiles, then crashes at
runtime with a RangeError.
Question 3 — Solution
void main() {
Object p = StateError('x');
print(p is Error);
print(p is Exception);
}true
falseQuestion 4 — Solution
initials, line 2, column 15 — the first frame in your own file. Frames #0 and #1 are
inside Dart's string code.
Question 5 — Solution
main → greet → initials → substring → checkValidRange. Read the trace bottom to
top.
Question 6 — Solution
void main() {
var marks = <int>[];
if (marks.isEmpty) {
print('No marks yet');
} else {
print(marks.first);
}
}No marks yetQuestion 7 — Solution
void main() {
var quantity = int.tryParse('7x') ?? 0;
print('Quantity: $quantity');
}Quantity: 0Question 8 — Solution
A
Unhandled exception:
Invalid argument(s): 20B never prints. The class is ArgumentError — a bug, because 20 is larger than 5.
Question 9 — Solution
String family(Object problem) {
return switch (problem) {
Error() => 'Error',
Exception() => 'Exception',
_ => 'Other',
};
}
void main() {
print(family(ArgumentError('x')));
print(family(FormatException('x')));
print(family('oops'));
}Error
Exception
OtherQuestion 10 — Solution
void main() {
var typed = ['100', '2o0', '50'];
var total = 0;
for (var text in typed) {
var amount = int.tryParse(text);
if (amount == null) {
print('Bad amount: $text');
} else {
total += amount;
}
}
print('Total: $total');
}Bad amount: 2o0
Total: 150tryParse plus the null check handles the would-be FormatException. for-in never
uses an index, so no RangeError is possible — the Error is prevented by design.
Quick Revision
- What is the difference between a compile-time error and a runtime error?
- What four things happen when a problem is not handled?
- What does one frame of a stack trace show?
- Which frame do you usually look at first?
- What kind of problem does the
Errorfamily represent? AndException? - Which family does
Bad state: No elementbelong to? - Why do we fix
Errors but handleExceptions?
Key Takeaways
- Compile-time errors stop the program before it starts; runtime problems stop it midway.
- An unhandled problem prints
Unhandled exception:, a message, and a stack trace. - Each frame shows a function, a file, and
line:column; start at the first frame in your own file. Error= programmer bug:RangeError,TypeError,StateError,ArgumentError,NoSuchMethodError,StackOverflowError.Exception= expected failure:FormatException, I/O exceptions.- Fix
Errors in the code; handleExceptions with a plan. tryParseand guards likeisEmptyalready avoid many crashes.
What's Next?
Lecture 084 — try, catch & finally. tryParse handles bad numbers, but most operations
that can fail have no "try" version. Next we learn try and catch, which let a program
catch a thrown Exception, respond to it, and carry on — plus finally, which runs no
matter what.