skip to content

In Flutter, what are kDebugMode, kProfileMode and kReleaseMode, and when would you use assert instead?

level: middleimportance: should knowfreq 40%

answer

  1. const, so dead code is removed
  2. dart.vm.product and dart.vm.profile
  3. kDebugMode is neither of the two
  4. assert runs only in debug
  5. side effects in asserts vanish

basics

~10 s

They are const booleans from package:flutter/foundation.dart telling which mode the app was compiled in, so gated code is removed elsewhere. Prefer kDebugMode for debug-only code and assert for invariants; asserts run only in debug.

solid answer

~40 s

`kReleaseMode` is `bool.fromEnvironment('dart.vm.product')`, `kProfileMode` reads `dart.vm.profile`, and `kDebugMode` is true when neither is set. Because they are compile-time constants, `if (kDebugMode) { ... }` is known to be false in release, so the compiler can drop the block. Use `kDebugMode` for debug-only features such as verbose logging or a debug menu; the `kReleaseMode` docs warn that gating on it makes profile differ from release. Use `assert` for invariants that should fail loudly during development; assertions are enabled only in debug, and `assert(() { ...; return true; }())` runs a debug-only block. Never put side effects inside an assert, because release builds skip them.

code

dart · 15 lines
dart
import 'package:flutter/foundation.dart';

class SessionCache {
  final Map<String, String> _entries = <String, String>{};

  void evict(String key) {
    // Wrong: assert(_entries.remove(key) != null); the removal would vanish in release.
    final String? removed = _entries.remove(key);
    assert(removed != null, 'evict called for unknown key $key');

    if (kDebugMode) {
      debugPrint('evicted $key, ${_entries.length} left');
    }
  }
}

go deeper

for a junior

Know the three constants, what each means, and that asserts only run in debug builds.

for a middle

Explain why const values let the compiler drop gated code, and why kDebugMode or assert is preferred over kReleaseMode.

for a senior

Catch side effects hidden in asserts and mode-dependent business logic in review, since both produce release-only bugs.

for a principal

Set a rule that build mode gates only tooling, never behaviour, and give configuration its own mechanism.

## Three constants from `package:flutter/foundation.dart` Flutter exposes the current build mode as three **compile-time constants**: | Constant | Defined as | True in | |---|---|---| | `kReleaseMode` | `bool.fromEnvironment('dart.vm.product')` | Release builds | | `kProfileMode` | `bool.fromEnvironment('dart.vm.profile')` | Profile builds | | `kDebugMode` | `!kReleaseMode && !kProfileMode` | Debug builds (and tests) | Because they are `const`, the compiler knows their value when it builds the app. In a release build, `if (kDebugMode) { ... }` has a condition that is known to be `false`, so the block **can be removed** from the output entirely. That is the whole point: debug tooling costs nothing in the shipped app. ## Which constant to use - **`kDebugMode`** for code that should exist only during development: verbose logging, a debug menu, fake data switches, extra validation. Profile and release then run the same code. - **`kProfileMode`** rarely, for example to turn on extra timeline markers only while profiling. - **`kReleaseMode`** sparingly. Its own documentation warns that gating on it creates differences between release and profile builds, which makes performance testing less representative, and recommends `kDebugMode` or `assert` instead. ## `assert` as a debug-only gate Dart **`assert`** statements run only when assertions are enabled, which in Flutter means debug builds (including tests). In profile and release they are compiled out, arguments and all. Two idioms: 1. **A condition check**: `assert(items.isNotEmpty, 'items must not be empty');` fails loudly in debug when a precondition is broken. 2. **A debug-only block**, which the `kDebugMode` documentation shows as an alternative strategy: ```dart assert(() { // debug-only code here return true; }()); ``` The closure runs only when asserts are on, and must return `true` so the assertion itself passes. ## The classic bug: side effects inside an assert Because assert expressions are not evaluated in release, **anything with a side effect inside an assert disappears** from release builds: ```dart assert(_cache.remove(key) != null); // the removal never happens in release ``` The code works in debug, where the assert runs and removes the entry, and silently misbehaves in release. Do the work outside, then assert on the result. ## `kDebugMode` versus `assert` | | `if (kDebugMode)` | `assert(...)` | |---|---|---| | Purpose | Run debug-only code | Check an invariant, or run debug-only code via a closure | | Failure behaviour | None; it is just a branch | Throws `AssertionError` in debug | | Removed from release | Yes, the constant condition lets the compiler drop it | Yes, assertions are disabled | | Reads naturally for | Logging, debug menus, fake backends | Preconditions and invariants | The framework uses both: many service extensions are registered only `if (!kReleaseMode)`, so they exist in debug **and** profile, while debug-only ones such as the debug banner toggle are registered inside an `assert` block. ## Practical guidance - Never make **business logic** depend on the build mode; a feature that behaves differently in release is untestable in debug. - Keep debug-only code small and obviously isolated, so reviewers can see nothing leaks into release behaviour. - Remember that `kDebugMode` is true in `flutter test`, because tests run with assertions and debug settings. - For per-environment configuration such as API URLs, use a separate mechanism (flavors or compile-time defines), not the build mode. ## What to look for in code review 1. Any function call inside an `assert` whose result matters beyond the check. 2. `kReleaseMode` used to switch features on or off, rather than tooling. 3. Debug-only helpers (fake backends, seeded accounts) reachable without a `kDebugMode` guard. 4. Tests that pass only because an assertion throws early, while release would continue in a bad state.

  • Is kDebugMode true inside flutter test?
    Yes. Tests are compiled without the product or profile flags, so `kDebugMode` is true and assertions run. That is why a test can catch an invariant that a release build would silently skip.
  • Why must the closure in assert(() { ... }()) return true?
    The assert evaluates the closure's result as its condition. Returning `true` makes the assertion pass after the debug-only code has run; returning `false` would throw an `AssertionError`. In profile and release the whole expression, closure included, is not evaluated.

saying these in an interview costs you the question

  • kDebugMode is read at runtime, so debug code still ships in release.
  • Assertions run in profile mode to help catch performance bugs.
  • Putting a removal inside an assert is fine because asserts always evaluate.
  • kReleaseMode is the recommended gate for debug-only logging.
  • kDebugMode is false when running flutter test.