Dart — Reference

Source: https://dart.dev/guides

Dart

  • Created: 2011 by Lars Bak and Kasper Lund at Google (1.0 in 2013; Dart 2 in 2018 made type system sound; Dart 3 in 2023 made null safety mandatory and added records + patterns)
  • Latest stable: 3.11 (current as of 2026-05; the Dart team ships a stable release roughly every 3 months — verify exact patch on https://dart.dev/get-dart)
  • Paradigms: Multi-paradigm — object-oriented (everything is an object, including null and functions; classes only — no free-standing structs), imperative, functional-leaning (first-class functions, closures, records, sealed/pattern matching since 3.0)
  • Typing: Static, strong, sound null safety (since 2.12, mandatory since 3.0); type inference; mixin-based composition; generics with bounded type parameters and reified runtime types
  • Memory: Garbage-collected — generational, parallel scavenger for new-gen + concurrent mark-sweep for old-gen; isolates have separate heaps (no shared mutable state across isolates)
  • Compilation: JIT for development (fast iteration, hot reload, dart run); AOT for production (dart compile exe/aot-snapshot); dart2js + dart2wasm for the web; Flutter targets iOS/Android/desktop/web from one codebase
  • Primary domains: Flutter (the dominant use — cross-platform mobile/desktop/web/embedded UI), CLI tools (Google internal + community), server-side via dart_frog / shelf, devops / build tooling
  • Official docs: https://dart.dev/guides

At a glance

  • Steward: Google (Dart team, originally with the V8/Strongtalk pedigree). The language and tooling are open source under BSD-3.
  • Dart’s killer app is Flutter — without Flutter, Dart would be a niche language. Flutter went stable in 2018 and has become the de-facto cross-platform UI toolkit.
  • Release cadence: ~quarterly stable releases (channels: stable, beta, dev, main). Major versions follow Flutter’s needs.
  • Flutter releases bundle a Dart SDK; using Flutter you typically don’t install Dart separately.
  • Reference impl: the Dart VM (in C++; ARM64/x64 native + WASM); also dart2js (transpiler to JS), dart2wasm (transpiler to Wasm GC).
  • License: BSD-3-Clause.

Getting started

  • Install: Pick one — Homebrew (brew tap dart-lang/dart && brew install dart), Chocolatey on Windows (choco install dart-sdk), apt on Debian/Ubuntu (Google APT repo), Docker (google/dart), or just install Flutter which bundles the Dart SDK. (https://dart.dev/get-dart)

  • Version manager: fvm (Flutter Version Management) is the dominant choice for Flutter+Dart project pinning; puro is a newer alternative; asdf-dart for asdf users. Dart itself doesn’t ship a first-party multi-version switcher.

  • Hello world (hello.dart):

    void main() {
      print('Hello, world!');
    }

    Run: dart run hello.dart (JIT) or dart compile exe hello.dart -o hello && ./hello (AOT).

  • Project layout (dart create): pubspec.yaml (manifest), pubspec.lock, lib/<name>.dart (public API), lib/src/ (private), bin/<entrypoint>.dart (executable apps), test/, analysis_options.yaml (lints). Flutter projects add android/, ios/, lib/main.dart, web/.

  • Package manager: pub (built into the SDK; commands surface as dart pub and flutter pub). Public registry is pub.dev. Versions follow caret semver (^3.0.0 means >=3.0.0 <4.0.0).

  • REPL: Dart historically lacked a REPL. DartPad (https://dartpad.dev) is the official browser playground. dart run --enable-vm-service + the observatory CLI is the closest thing to interactive debugging. Community projects (interactive_dart) exist but aren’t standard.

Basics

  • Types/literals: int (64-bit on VM, JS-numeric on web), double (IEEE-754), num (supertype of both), String, bool, List<T>, Set<T>, Map<K, V>, Symbol, Runes (Unicode codepoints), Future<T>, Stream<T>, Record (since 3.0 — (int, String) or named ({int x, int y})). Numeric literals: 1_000_000 (since 2.13), 0xFF, 1.5e3. No char type — use single-char String or int codepoint.
  • Variables: var (inferred type), final (single-assignment, runtime), const (compile-time constant), late (late-init non-null), explicit type (int x = 1). Top-level vars allowed.
  • Scoping: Lexical, block-scoped. Closures capture by reference. library directive (now mostly implicit) groups multiple files.
  • Control flow: if/else (statement only — no ternary as if expression, but cond ? a : b works); switch (statement and expression since 3.0, with patternsswitch (point) { case (0, 0) => 'origin'; case (var x, 0) => 'on x-axis'; }); for/for-in/while/do-while; try/catch/on/finally. Patterns and records (3.0): destructuring (var (x, y) = point;), exhaustive switch on sealed hierarchies.
  • Functions: int add(int a, int b) => a + b; (arrow body) or int add(int a, int b) { return a + b; }. Optional positional [], optional named {}, required named required int x. First-class — can be assigned, passed, returned. Anonymous: (x) => x * 2. Tear-offs: list.forEach(print).
  • Strings: Single or double quotes; 'Hello $name' and 'Hello ${user.name}' interpolation; raw r'\n'; multi-line '''...''' or """...""". Strings are UTF-16 code unit sequences under the hood — .codeUnits (UTF-16), .runes (Unicode codepoints).
  • Collections: List (growable by default; List.filled(n, 0, growable: false) for fixed), Set ({1, 2, 3} literal), Map ({'a': 1} literal). Collection-if and collection-for in literals: [for (var x in xs) if (x.isEven) x * 2]. Spread: [...a, ...?maybeNull, b].

Intermediate

  • Type system depth: generics (List<int>), bounded type params (T extends Comparable<T>), function types as values (int Function(int, int)), typedef aliases, dynamic (escape hatch — turns off static checks but keeps runtime checks), Object? for “anything nullable”, Never (bottom type — function never returns). Sound null safety means non-nullable types cannot hold null at runtime, eliminating a class of NPE errors.
  • Modules / libraries: files are libraries by default; import 'package:foo/foo.dart', import 'dart:io', import 'src/util.dart'. Visibility: leading underscore = library-private (_foo). export for re-export. Conditional imports for cross-platform (import 'src/io.dart' if (dart.library.html) 'src/web.dart').
  • Error handling: Exception (recoverable) vs Error (programming bug — shouldn’t be caught). throw, try/catch with on TypeName catch (e, stackTrace), finally, rethrow. Async errors flow through Future.catchError/await ... try/catch. StackTrace is first-class.
  • Concurrency primitives:
    • Future<T> + async/await — single-threaded event-loop concurrency (like JS). The Dart VM has one event loop per isolate.
    • Stream<T> — async iterables, single-subscription or broadcast; await for, StreamController, StreamTransformer.
    • Isolates — true parallel workers with separate heaps (no shared memory). Communicate via SendPort/ReceivePort message passing. Isolate.spawn and the higher-level Isolate.run (since 2.19) for fire-and-forget. Now isolates can share TransferableTypedData and (since 3.7) some immutable data without copying.
    • No native threads / no GIL discussions — Dart simply doesn’t share mutable state across isolates by design.
  • I/O: dart:io (server/CLI/desktop — File, Directory, Socket, HttpClient, Process, Platform, Stdin/Stdout); dart:html (web only — DOM); package:http for high-level HTTP; package:dio for batteries-included HTTP. Web targets do not have dart:io.
  • Stdlib highlights: dart:core (collections, strings, num), dart:async (Future, Stream, Timer, Zone), dart:collection (LinkedList, Queue, UnmodifiableMapView), dart:convert (json, utf8, base64, custom converters), dart:math (Random, Point, Rectangle, Vector), dart:io, dart:ffi, dart:isolate, dart:typed_data (Uint8List, Float64List — flat numeric buffers), dart:mirrors (limited; see God mode).

Advanced

  • Memory/GC: Generational concurrent GC. New-gen uses a parallel scavenger (Cheney-style copying); old-gen uses concurrent mark-sweep with optional compaction. Each isolate has its own heap and its own GC — no stop-the-world across isolates. The VM exposes GC stats via the VM Service Protocol / Observatory. (https://github.com/dart-lang/sdk/blob/main/runtime/docs/gc.md)
  • Concurrency deep dive: the event loop runs in two queues — the microtask queue (drained completely between events; scheduleMicrotask) and the event queue (I/O, timers, isolate messages). await schedules a microtask to resume after the awaited Future completes. Zone (runZoned) provides scoped error/print interception — used by Flutter’s framework to catch async errors. (https://dart.dev/articles/archive/zones)
  • AOT vs JIT:
    • JIT (dart run) — fast startup, full reflection (dart:mirrors), supports hot reload; what Flutter flutter run uses in debug.
    • AOT (dart compile exe / flutter build apk --release) — single binary, fast startup, no JIT warmup, no dart:mirrors, tree-shakes aggressively. Slower compile, faster runtime.
  • FFI (dart:ffi): call C ABI directly without writing native plugins. Native<T>() annotations + Pointer<T> + DynamicLibrary.open(...). Supports structs, arrays, callbacks (Dart→C), and (since 3.0) the @Native annotation for direct binding generation by package:ffigen (which parses .h files via libclang). (https://dart.dev/interop/c-interop)
  • Reflection: dart:mirrors exists but is discouraged — incompatible with AOT (it disables tree-shaking, ballooning binary size). Use code generation instead (build_runner + source_gen). Limited runtime type info: runtimeType, is/as, Type literals.
  • Performance tools: DevTools (https://dart.dev/tools/dart-devtools) — full IDE-grade Flutter/Dart profiler with CPU sampler, memory, network, timeline, widget inspector. VM Service Protocol is the underlying JSON-RPC interface (used to be called Observatory). dart run --observe exposes it. Benchmark harness in package:benchmark_harness.

God mode

  • Macros (experimental, 3.x preview): static metaprogramming planned to replace much of build_runner codegen. Macro authors write Dart code that runs at compile time and emits new declarations. Not stable as of 3.11 — see https://dart.dev/language/macros for status.
  • Code generation via build_runner + source_gen: the current metaprogramming story. Annotation processors (json_serializable, freezed, riverpod, drift, injectable, retrofit) generate .g.dart files. Run dart run build_runner build (or watch). The source_gen package gives you a Builder API on top of the analyzer.
  • dart:mirrors caveats: AOT-incompatible; not available on web (dart2js). Use only in dev tooling, JIT-only servers, or test code. The Flutter framework forbids it.
  • VM Service Protocol: a stable JSON-RPC interface to a running Dart VM — list isolates, get stack traces, evaluate expressions in scope, sample CPU, get GC events, hot-reload. DevTools is just a fancy client. Spec: https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md
  • Hot reload internals: the VM swaps in modified Dart source at runtime, preserving isolate state and existing object instances. Works in JIT mode only. The framework (Flutter) reruns build() to repaint widgets with the new code. Class-shape changes (adding fields, removing methods) trigger hot restart (full state loss) instead.
  • Tree shaking: the AOT compiler eliminates unused declarations. @pragma('vm:entry-point') marks symbols that must be retained (used by FFI callbacks invoked from native code). Mirrors disable tree-shaking entirely.
  • Isolates internals: each isolate is a separate VM “actor” with its own heap, microtask queue, and event loop. Spawning costs ~ms; messaging is via copying (or transferable typed data for zero-copy). Isolate.run (2.19+) is a higher-level “do this in a worker, give me back a Future” wrapper. Isolate groups (3.0+) share the JIT cache and code memory.
  • Flutter engine binding: Flutter is C++ (Skia/Impeller renderer + Dart VM embedder). Dart calls into the engine via FFI-like dart:ui bindings. The widget tree → element tree → render tree pipeline is pure Dart on top.
  • AOT snapshots: dart compile aot-snapshot produces a .aot file that the dartaotruntime executable runs. Smaller than compile exe (no embedded runtime). Used by app servers like dart_frog.
  • Pragmas (@pragma): undocumented-ish hints to the compiler — vm:entry-point, vm:prefer-inline, vm:never-inline, vm:always-consider-inlining, vm:notify-debugger-on-exception. Listed in runtime/vm/compiler/recognized_methods_list.h of the SDK source.
  • Wasm GC backend: dart compile wasm since 3.4 emits Wasm GC modules — runs in browsers without dart2js intermediate. Smaller, faster startup than dart2js for compute-heavy workloads.

Idioms & style

  • Naming: lowerCamelCase for variables/functions/parameters/methods, UpperCamelCase for types/extensions/enums/typedefs, lowercase_with_underscores for libraries/packages/file names, leading _ for library-private, SCREAMING_CAPS is not idiomatic — use lowerCamelCase even for constants.
  • Formatter / linter: dart format is built into the SDK and is the answer (no Prettier-style alternatives). dart analyze runs the static analyzer; configure via analysis_options.yaml with package:lints (the Google-recommended set: core, recommended, flutter) or package:very_good_analysis (Very Good Ventures’ stricter preset). Style guide: Effective Dart (https://dart.dev/effective-dart).
  • Idiomatic patterns: prefer final over var; use const constructors aggressively (Flutter optimizes them); use named parameters for clarity (esp. boolean arguments); prefer composition over inheritance; use sealed classes + exhaustive switch for ADTs (3.0+); records for tuples; cascade operator .. for fluent setup (obj..a = 1..b = 2).
  • Expert review focus: missing const on widget constructors (Flutter perf), setState from outside a State class, captured BuildContext after await, growable lists where fixed would do, List<dynamic> from JSON without typing, isolate sends of non-transferable objects, leaking StreamSubscriptions, late fields read pre-init, mirroring in AOT-targeted code.

Ecosystem

  • UI frameworks: Flutter (mobile/desktop/web/embedded — the dominant Dart use case), Jaspr (server-rendered web framework with Flutter-like widgets).
  • Server-side: dart_frog (Vercel-style file-based routing, Very Good Ventures), shelf (low-level middleware), serverpod (full BaaS-style backend with codegen client/server), alfred (Express-like).
  • State management (Flutter): Provider, Riverpod (codegen-driven), BLoC + flutter_bloc, GetX, MobX, Redux.
  • DB / persistence: Drift (formerly Moor — type-safe SQLite + Postgres), Isar (NoSQL embedded), Hive, Sembast, ObjectBox, sqflite (raw SQLite).
  • Codegen: build_runner, source_gen, freezed (immutable data classes + unions), json_serializable, injectable, auto_route, retrofit, mockito (build_runner-driven mocking).
  • Testing: package:test (the standard runner), flutter_test (widget tests with WidgetTester), integration_test (full-app on-device), mocktail (mockito alternative without codegen), golden_toolkit (Flutter golden image testing), patrol (E2E with native interactions).
  • Docs: dart doc (formerly dartdoc) generates HTML from /// doc comments. Hosted at https://pub.dev/documentation for every published package.
  • Notable users: Google (Ads, Pay, Earth, Classroom — many internal apps on Flutter), Alibaba (Xianyu was an early Flutter case study), BMW (My BMW app), eBay Motors, Toyota (in-car infotainment), Tencent (parts of WeChat), ByteDance, Philips Hue, Wonderous (Flutter showcase), Reflectly, Hamilton (the musical’s official app).

Gotchas

  • int is 64-bit on the VM but JS-numeric (53-bit safe range) on the web. Bigint-style ops fail silently above 2^53 in browsers. Use BigInt for arbitrary precision.
  • String is UTF-16 code units, not Unicode code points. Iterating with for (var c in str.codeUnits) breaks surrogate pairs; use .runes for codepoints, or package:characters for grapheme clusters.
  • == is value equality by default but you must override hashCode whenever you override ==. The analyzer warns. Use freezed or records to avoid the boilerplate.
  • async functions return Future immediately — anything after the first await runs in a microtask, which may alter ordering vs sync code.
  • late field uninitialized throws LateInitializationError only when read — easy to miss.
  • BuildContext after async gap (Flutter): if (!mounted) return; after every await in widget code. The analyzer’s use_build_context_synchronously lint catches this.
  • const constructors are deeply structural-equal — two const Foo(1) literals are the same instance. Lifesaver for Flutter rebuild perf.
  • Isolate sends copy by default — large objects are expensive. Use TransferableTypedData or Isolate.exit (transfers ownership).
  • dart:mirrors + AOT = doesn’t work. AOT release builds will fail or balloon. Always design around codegen.
  • pubspec.yaml indentation matters — YAML, two spaces.
  • Flutter SDK pins Dart SDK — using a global dart outside Flutter’s bundled one can cause version mismatches. Use flutter pub not dart pub in Flutter projects, or use fvm.
  • No method overloading — define optional named parameters or factory constructors instead.
  • Equality on List / Map is identity, not deep — use package:collection’s ListEquality / DeepCollectionEquality.
  • Web targets ship dart2js or dart2wasm output — JS-interop differs from VM dart:io; conditional imports are mandatory.
  • Hot reload limits: changing top-level main, mutating const constructors, or restructuring class hierarchies forces hot restart (state lost).

Citations