skip to content

In Dart, when does an assert statement actually run, and why must its condition never contain side effects?

level: middleimportance: should knowfreq 42%

answer

  1. development only, tool decides
  2. dart run needs a flag
  3. debug on, release off
  4. arguments are not evaluated when off
  5. AssertionError, an Error subclass

basics

~20 s

A Dart assert only runs when the tool enables assertions: Flutter debug mode, dart test, or dart run --enable-asserts. Otherwise the whole statement, arguments included, is skipped, so side effects inside it vanish in release builds.

solid answer

~40 s

`assert(condition, message)` throws an `AssertionError` when assertions are enabled and the condition is false. They are enabled in Flutter debug mode, by the `dart test` runner, and by `dart run --enable-asserts` or the same flag on `dart compile exe`; plain `dart run` and every Flutter profile or release build ignore them. When ignored, the arguments are not even evaluated, so `assert(cache.warmUp())` means `warmUp()` never runs in production, a bug you only see after release. So I keep assert conditions pure, always add a message, and never use assert for user-input or API validation; checks that must hold in production throw an `ArgumentError` or a domain exception instead.

code

dart · 14 lines
dart
class Bracket {
  const Bracket(this.upTo, this.rate)
    : assert(rate >= 0 && rate <= 1, 'rate must be 0..1, got $rate');
  final double upTo;
  final double rate;
}

bool _warmed = false;
bool warmUp() => _warmed = true;

void main() {
  assert(warmUp()); // BUG: skipped entirely when asserts are off
  print('warmed: $_warmed'); // true with --enable-asserts, false without
}

go deeper

for a junior

Recall that assert throws AssertionError when enabled, and that Flutter debug mode has assertions on while release has them off.

for a middle

Explain which tools enable assertions, including dart run --enable-asserts and dart test, and that disabled assertions skip argument evaluation entirely.

for a senior

Show the production failure mode: behaviour that only exists because of an assert side effect, or validation that silently disappears in release; move such checks to thrown errors.

for a principal

Set policy: which invariants belong in asserts versus runtime checks, and make sure CI exercises the code with assertions enabled so they actually protect something.

## What `assert` does `assert(condition, optionalMessage);` checks a boolean condition **during development**. If assertions are enabled and the condition is `false`, Dart throws an **`AssertionError`**, a subclass of `Error`, carrying the optional message. If the condition is `true`, execution continues. ```dart assert(rate >= 0 && rate <= 1, 'rate must be a fraction, got $rate'); ``` The same form works in a **constructor initializer list**, where it checks arguments before the object exists: `Bracket(this.upTo, this.rate) : assert(rate >= 0);`. ## When assertions are enabled Assertions are **off unless the tool turns them on**. Knowing which tool does is the heart of the interview question: | Where the code runs | Assertions | |---|---| | `dart run bin/main.dart` | **off** by default; add `--enable-asserts` | | `dart compile` targets such as `exe` | **off** by default; `--enable-asserts` turns them on | | `dart test` (package:test runner) | **on**: the runner enables them for test code | | `webdev serve` and similar development servers | typically **on** | | Flutter `flutter run` in debug mode | **on** | | Flutter profile and release builds | **off** | When assertions are off, the statement is not just skipped: **its arguments are never evaluated**. The dart.dev language guide says so directly: "In production code, assertions are ignored, and the arguments to assert aren't evaluated." ## Why side effects in an assert are a bug Because the arguments disappear in production, anything with a side effect inside `assert` vanishes with them: ```dart assert(cache.warmUp()); // warmUp() never runs in release ``` The app works in development and tests, where assertions are on, and then misbehaves only in production. Typical cases: - calling an initializer, registering a listener or incrementing a counter inside the condition; - computing a value in the message argument that something else later relies on; - using `assert` to **validate user input or API arguments**: in production the invalid value passes straight through. Throw an `ArgumentError` or a domain exception for checks that must hold in every build. ## Where assertions shine 1. **Documenting invariants** a developer could break: "this list must be sorted", "this controller must not be disposed yet". 2. **Constructor preconditions** in the initializer list, so misuse is caught at the call site during development. 3. **Expensive consistency checks** that would be too slow in production but are worth running in tests and debug builds. 4. **Framework-style guard rails**: the Flutter framework itself uses asserts heavily to report misuse in debug mode, which is why many layout errors appear only in debug builds. A useful habit is to always include the **message** argument: an `AssertionError` without one only says that an assertion failed. The `prefer_asserts_with_message` lint (not in the default sets) enforces it. ## Checklist for review - The condition is a pure boolean expression with no side effects. - The message explains what was expected and shows the actual value. - Nothing that must be true in production depends on the assert alone. - CI runs the tests with assertions on, which `dart test` and `flutter test` do, so the checks actually fire somewhere before release. ## What an interviewer listens for - "Assertions throw `AssertionError` when enabled and are completely ignored, arguments included, when disabled." - "`dart run` needs `--enable-asserts`; Flutter debug mode and `dart test` have them on; release builds have them off." - "Never put side effects or production validation in an assert."

  • Should you use assert to validate arguments passed to a public Dart API?
    Not for checks that must hold in production. With assertions disabled, the default in release builds and plain `dart run`, an invalid argument passes straight through. Throw an `ArgumentError` or `RangeError` for public API contracts, and keep `assert` for internal invariants and developer misuse you want caught early in debug and test runs.
  • What does a failing Dart assert throw, and should code catch it?
    It throws an `AssertionError`, which extends `Error`, not `Exception`. Errors signal programming mistakes, so normal code should not catch it; the point is to fail loudly during development so the bug gets fixed. Tests may expect it with a matcher when verifying that an API rejects misuse in debug builds.

saying these in an interview costs you the question

  • Thinks asserts run in release builds too
  • Puts a side-effecting call inside assert
  • Uses assert to validate user input in production
  • Believes the message argument is evaluated even when asserts are off
  • Expects plain dart run to enable asserts