AOT vs JIT Compilation

Medium PriorityAsked in ~45% of mid-level interviews

3 min read

Build & Compilation

Flutter modeCompilationHot reloadAsserts onUse for
Debug (flutter run)JITLocal iteration
Profile (flutter run --profile)AOT (mostly) + observatoryPerformance measurement
Release (flutter build … --release)AOTShip to users
JIT (debug)AOT (release)
Compile timeEach function compiled on first callAll code compiled at build time
StartupSlower (compile on demand)Fast
MemoryLarger (compiler runtime present)Smaller
Hot reload
OptimisationsLight (fast compile)Heavy (inlining, tree shaking, devirtualisation)
Binary sizeLargerSmaller
Performance~10× slower in placesProduction-grade

What this means in practice

# Debug — fast iteration, useless for perf numbers
flutter run

# Profile — AOT-compiled, instrumented for DevTools
flutter run --profile

# Release — production binary, fully optimised
flutter build apk --release
flutter build ipa --release

Rule: Never benchmark in debug. Debug-mode timings include the JIT, extra assertions, type checks, layout verification — easily 10× the real cost. Always profile.


How AOT works at a glance

  1. dart compile aot-snapshot main.dart (under the hood)
  2. Produces a snapshot containing fully compiled native code, no Dart bytecode at runtime.
  3. Tree shaking strips unreachable code (see Q47).
  4. Constants are canonicalised and de-duped.
  5. The result is bundled into the app's native binary.

The flip side: no JIT means no dart:mirrors (already disabled), no hot reload, no eval — everything must be statically resolvable.


When each mode matters

Question you're answeringUse mode
Is my UI wired up correctly?Debug
Does my logic produce the right output?Debug (with tests)
Why is this animation janky?Profile
Why is startup slow on user devices?Profile or release
What's the real APK/IPA size?Release
Will tree shaking remove this code?Release + --analyze-size

Common mistakes to avoid

❌ Treating debug-mode frame times as real performance data
   Debug adds layout assertions, type checks, raster overlays.
   ✅ Always profile.

❌ Forgetting hot reload doesn't work in profile/release
   Quick UI iteration MUST be in debug; profile is for measurement.

❌ Shipping debug builds to users
   Larger, slower, leaks debug info. Always build --release.

❌ Assuming AOT removes ALL runtime overhead
   Bounds checks, null checks, allocations still happen.
   Real perf wins still come from algorithmic fixes.

❌ Using `--enable-software-rendering` in profile
   Disables GPU, skews everything. Only for debugging GPU issues.

Interview follow-ups

  1. Why does hot reload only work in debug mode? Hot reload needs a JIT runtime — when you save changes, the framework recompiles only the changed classes and patches them into the running app. AOT-compiled native code can't be hot-patched, so release and profile builds don't support reload (or hot restart).

  2. What's the difference between hot reload and hot restart? Hot reload swaps class definitions into the running VM — state is preserved, initState doesn't re-run. Hot restart restarts the Dart VM from main() — state is lost but you don't recompile native code. Both require JIT (debug mode).

  3. Why does profile mode exist if release exists? Profile is AOT-compiled (so timings are realistic) but keeps DevTools' Observatory connection alive, so you can attach the timeline and inspector. Release strips that out. Profile = "release perf with developer tools attached."

  4. What's the difference between dart compile exe and Flutter's AOT? Same compiler, different targets. dart compile exe produces a standalone Dart binary for command-line tools. Flutter's AOT produces a snapshot bundled into the iOS/Android app and loaded by Flutter's engine. Same underlying technology.


How helpful was this content?

Please sign in to rate this article.