skip to content

Exceptions & Errors

Dart throws any object, catches with on and catch(e, st), and splits Exception, for conditions you handle, from Error, for bugs you fix. Interviewers ask when catching an Error is wrong.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Dart, how do try, on, catch (e, st) and finally fit together, and when do you use on versus catch?

level: juniorimportance: must knowfreq 70%

answer

  1. clauses tried top to bottom
  2. on filters, catch binds
  3. first matching type wins
  4. second catch parameter: StackTrace
  5. finally runs before propagation continues

basics

~20 s

In Dart, on Type picks which thrown objects a clause handles, catch (e, st) binds the object and its StackTrace, the first matching clause in source order wins, and finally runs whether or not anything was thrown.

solid answer

~40 s

A `try` block is followed by any number of catch clauses and an optional `finally`. A clause can filter with `on SomeType`, bind with `catch (e)` or `catch (e, st)`, or do both: `on FormatException catch (e, st)`. Use `on` when the type decides whether you can handle it, and `catch` when the handler needs the object or its `StackTrace`. Dart tests the clauses in source order and runs only the first whose type matches, so specific types go above `on Exception` and a bare `catch`. If nothing matches, the object keeps propagating. `finally` runs after the try block and any matching clause, and before an unmatched object leaves, so it is the place for cleanup. All Dart exceptions are unchecked: no signature declares them and the compiler never forces a catch.

code

dart · 16 lines
dart
int? parseQuantity(String cell) {
  try {
    return int.parse(cell);
  } on FormatException catch (e, st) {
    print('bad quantity "$cell": ${e.message}');
    print(st);
    return null;
  } finally {
    print('checked "$cell"');
  }
}

void main() {
  print(parseQuantity('3')); // checked "3", then 3
  print(parseQuantity('x')); // bad quantity..., trace, checked "x", then null
}

go deeper

for a junior

Recall the four shapes: on Type, catch (e), catch (e, st), and on Type catch (e, st), plus finally for cleanup. Know that the first matching clause wins.

for a middle

Explain that clauses are matched in source order by runtime type, what static type e gets, and exactly when finally runs relative to propagation.

for a senior

Show you order clauses from specific to general, keep stack traces when logging, and never let control flow in finally swallow a failure.

for a principal

Be ready to set a team rule: which lints enforce catch discipline, and how documented throws contracts replace the checked exceptions Dart does not have.

## The pieces of a try statement Dart's **try statement** has one `try` block and at least one `on`, `catch` or `finally` clause after it; a bare `try { }` is a compile-time error (`missing_catch_or_finally`). The clause forms are: | Form | Filters by type? | Binds the object? | Binds the stack trace? | |---|---|---|---| | `on FormatException { … }` | yes | no | no | | `catch (e) { … }` | no — matches anything | yes, as `Object` | no | | `catch (e, st) { … }` | no — matches anything | yes, as `Object` | yes, as `StackTrace` | | `on FormatException catch (e, st) { … }` | yes | yes, as `FormatException` | yes | | `finally { … }` | — | — | — | So **`on` answers "which failures can I handle?"** and **`catch` answers "what do I need to read from the failure?"**. When the handler only reacts to the type — skip the row, show a fixed message — `on` alone is enough. When it needs the message, a field or the trace, add `catch`. ## How Dart picks a clause When something is thrown inside the `try` block: 1. Dart looks at the **runtime type** of the thrown object. 2. It walks the clauses **in source order** and runs the **first** one whose type the object is a subtype of. A bare `catch` matches anything. 3. Only that one clause runs. Dart does not rank the clauses by specificity. 4. If no clause matches, the `finally` block (if any) runs and the object continues to propagate to the caller. If nothing up the stack catches it, the isolate that raised it is typically terminated. That is why ordering matters. If `on Exception catch (e)` sits above `on FormatException catch (e)`, the second clause can never run, because every `FormatException` is an `Exception`. The code still compiles; the analyzer reports the later clause as dead code (`dead_code_on_catch_subtype`, and `dead_code_catch_following_catch` for clauses after a bare `catch`). Put the most specific types first. ## The two catch parameters - **`e`** is the thrown object. With `on T catch (e)` its static type is `T`; with a bare `catch (e)` it is `Object`, because under sound null safety nothing thrown can be `null`. - **`st`** is a **`StackTrace`** — the call stack from the throw point. Its `toString()` gives the printable trace; the exact format is not fixed, so log it rather than parse it. - To get a trace without throwing, `StackTrace.current` returns the current one. - The analyzer flags parameters you never read (`unused_catch_clause`, `unused_catch_stack`), so declare `st` only when you log or forward it. ## What finally guarantees A **`finally`** block runs when the try statement is left in any of these ways: - the `try` block completes normally; - the `try` block executes `return`, `break` or `continue`; - something is thrown and a clause catches it (`finally` runs after that clause); - something is thrown and no clause matches (`finally` runs, then propagation continues). Use it to release resources — close a file, stop a stopwatch, clear a busy flag. Do not put `return`, `break` or `throw` inside `finally`: an abrupt exit from `finally` replaces whatever was in flight, so a pending exception silently disappears. The `control_flow_in_finally` lint, in the recommended and flutter lint sets, catches this. ```dart int? parseQuantity(String cell) { try { return int.parse(cell); } on FormatException catch (e) { print('bad quantity "$cell": ${e.message}'); return null; } finally { print('checked "$cell"'); // runs on both paths } } ``` ## Unchecked exceptions Dart has **no checked exceptions**. A function's signature never lists what it throws, and callers are never forced to catch. The contract lives in documentation — the SDK writes "Throws a [FormatException] if…" in doc comments — so reading those comments is how you learn which `on` clauses a call needs. ## Mistakes interviewers listen for - Expecting the most specific clause to win regardless of order. - Using a bare `catch (e)` everywhere, which also swallows bugs such as `ArgumentError` and failed asserts. - Writing `catch (e, st)` and never using `st`, or dropping the trace when logging. - Returning from `finally` and wondering where the exception went.

  • In Dart, what is the static type of e in a bare catch (e), and why is it not nullable?
    It is `Object`. Under sound null safety a throw expression must be assignable to `Object`, so `throw null` or throwing a `String?` is a compile-time error, and an `on` clause cannot name a nullable type either. Nothing that reaches a catch can therefore be `null`.
  • In Dart, what happens to an in-flight exception if the finally block executes return?
    It is discarded. An abrupt exit from `finally` — `return`, `break`, `continue` or a new `throw` — replaces the pending completion, so the original exception never reaches the caller. The `control_flow_in_finally` lint in the recommended set warns about exactly this.

saying these in an interview costs you the question

  • Dart picks the most specific matching on clause, whatever the order.
  • Every Dart method must declare or catch the exceptions it throws.
  • finally runs only when an exception was thrown.
  • A bare catch (e) catches only Exception subtypes, never Errors.
  • The stack trace is available as a field on every thrown object.
open as a page

In Dart, what is the difference between Exception and Error, and when is catching an Error the wrong move?

level: middleimportance: must knowfreq 62%

basics

~20 s

In Dart, an Error such as ArgumentError or StateError signals a bug the programmer should have prevented; an Exception such as FormatException signals a failure a correct program still meets. Catching an Error hides the bug instead of fixing it.

open as a page

In Dart, how do you define a custom exception class, and why implement Exception rather than throw a plain String?

level: middleimportance: should knowfreq 44%

basics

~20 s

Dart can throw any non-null object, but a custom exception should be a class that implements Exception, carries fields describing the failure and overrides toString, so callers can catch it precisely with an on clause and read its data.

open as a page

In Dart, how does rethrow differ from throw e inside a catch, and when do you need Error.throwWithStackTrace?

level: middleimportance: should knowfreq 38%

basics

~20 s

Inside a Dart catch, rethrow re-throws the caught object with its original stack trace, while throw e starts a new trace at the catch. Error.throwWithStackTrace (Dart 2.16+) throws any object with a trace you supply, for translating exceptions or throwing outside a catch.

open as a page

A Dart CSV importer must report every bad row without aborting the whole file; how would you structure its exception handling?

level: seniorimportance: should knowfreq 30%

basics

~20 s

In a Dart importer, give each row its own try with on RowException, record row, column and message in a report and continue; let file-level exceptions end the import, and never catch Error, which means the importer is buggy.

open as a page