This episode covers Dart performance optimization: AOT compilation and tree shaking, profiling with DevTools and allocation analysis, performance tips for Flutter and servers, and benchmarking and runtime analysis.

Slow applications drive users away, and wasteful servers drain budgets. Episode 15 covers performance optimization in Dart: how AOT compilation and tree shaking work, how to profile applications with DevTools, practical tips for Flutter and servers, and benchmarking techniques.
Performance isn't a guess — it has to be measured. You'll learn to find bottlenecks with data, not intuition, and then optimize the parts that truly matter.
AOT compilation turns Dart code into machine code before the application runs. The result: no JIT cost at startup, more stable memory allocation, and fast boot times — crucial for mobile apps and servers that get restarted often.
Tree shaking removes code that's never used from the build output. The compiler analyzes the call graph and only includes functions that are actually reachable from the entry point:
flutter build appbundle --releaseflutter build appbundle --release produces a production bundle with tree shaking enabled. For web applications, dart compile js -O4 enables the most aggressive optimizations. Check the artifact size before and after to see the savings.
Tree shaking only works on code that's actually used. Avoid export statements that expose an entire library without control, and avoid dependencies that are only used for one small function. Large dependencies add build time and bundle size.
DevTools is the official profiling suite for Dart and Flutter:
dart devtoolsdart devtools opens the profiling dashboard in your browser. The most useful tabs:
If memory keeps growing without coming down, there's likely a leak — old objects are never discarded. Watch out for common patterns: listeners that aren't cancelled, controllers that aren't closed, and unbounded caches. The Memory tab in DevTools shows objects still being held and their references.
const for static widgets.Use DevTools' frame profiling to find slow frames. Don't optimize before you have evidence.
await and avoid heavy CPU work on the main isolate.Measure performance regressions automatically with a simple benchmark:
void main() {
final stopwatch = Stopwatch()..start();
var total = 0;
for (var i = 0; i < 1000000; i++) {
total += i;
}
stopwatch.stop();
print('Waktu: ${stopwatch.elapsedMilliseconds} ms');
}stopwatch.elapsedMilliseconds measures execution time. Save benchmark results as a baseline and rerun them on major changes to catch performance regressions. Benchmarks are also useful for comparing two approaches objectively — for example, picking a faster data structure or algorithm — as long as the measurement conditions are kept exactly the same.
Don't only measure during development. Collect data from production: p95 latency, memory usage, and error rate. Tools like tracing and observability (episode 19) give you a picture of real performance under genuine user load.
Remember the right optimization order: measure first, then optimize. Fixing code that isn't actually slow only adds complexity. Start by profiling to find the biggest bottleneck, fix things one at a time, and re-measure after every change to confirm the impact is positive.
Finally, don't ignore binary size. For mobile apps, bundle size affects users' download time; for servers, image size affects container startup time. Track both as part of your release metrics.
Key takeaways:
flutter build --release and dart compile js -O4 enable production optimizations.In the next episode 16, we'll cover the Dart VM and native interop — working with the Dart VM and command-line tools, native interop with FFI, building CLI utilities, and embedding Dart in host applications.