Compilation, runtime and the type system

Dart · Expert · 16 min

Expert decisions about startup time, binary size, web targets and generated code depend on how the toolchain works.

Dart has several compilation targets:

  • JIT (dart run, Flutter debug): the VM compiles on the fly, enabling hot reload — new code is injected into the running isolate while its heap is kept.
  • AOT (dart compile exe, Flutter release): ahead-of-time compiled machine code with a small runtime, fast startup and no JIT. Reflection via dart:mirrors is unavailable, which is why Dart relies on code generation (build_runner with generators such as json_serializable).
  • Web: dart compile js (dart2js, optimized JavaScript) and dart compile wasm (WasmGC, used by Flutter web's --wasm builds).

AOT and web builds perform tree shaking: unreachable code is removed, so binary size depends on what you actually call.

The type system is sound: static types are guaranteed at run time, enforced by the compiler plus a few run-time checks — explicit casts (as), covariant generics, and covariant parameters. Soundness lets AOT compilers trust types to unbox numbers, devirtualize calls and skip null checks.

Memory is managed by a generational garbage collector: a fast scavenger for the young generation (most objects die young) and a concurrent mark-sweep/compact for the old generation. Short-lived allocations are cheap, but in Flutter large allocations per frame still cause GC pauses; reuse buffers in hot paths.

dart:ffi calls C directly from native targets, with NativeFinalizer tying C resource lifetimes to Dart objects.

Example

# Development: JIT with fast edit/run cycles and hot reload in Flutter
dart run bin/main.dart

# Native self-contained executable (AOT, tree-shaken)
dart compile exe bin/main.dart -o build/tool
./build/tool

# Smaller AOT snapshot run by dartaotruntime
dart compile aot-snapshot bin/main.dart

# Web targets
dart compile js -O2 web/main.dart -o build/main.js
dart compile wasm web/main.dart -o build/main.wasm

# Generate code instead of reflection (e.g. json_serializable)
dart run build_runner build --delete-conflicting-outputs

Remember

  • JIT enables hot reload; AOT gives fast startup and no reflection
  • Soundness lets compilers trust static types for optimization
  • Generational GC makes short-lived objects cheap, but avoid big per-frame allocations

Watch out for

  • Expecting dart:mirrors reflection to work in Flutter or AOT builds — use code generation.

Check yourself

  1. Why do Dart AOT targets rely on code generation (e.g. json_serializable) instead of reflection?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: dart.dev/tools/dart-compile

Builds on

Unlocks

Nothing builds on this yet.