Dart Cheatsheet - Dart Syntax & Flutter Reference

This reference is for Dart developers building Flutter apps or backend services, centered on the syntax you write all day: null-safe variables (final/const vs late), functions and closures, classes with mixins, and async flow with Future/async/await. Unlike a flat Dart feature list, entries are grouped by the construct and flag null-safety behavior that trips people new from Java/JS. After reading you should be able to write null-safe chains without non-null assertions everywhere, compose classes with mixins, and handle asynchronous work with Future and async/await without blocking the UI thread.

Languages·43 commands·Last updated 2026-07-21
dartflutterMobile Development

Typical Use Case

For engineers who write Flutter UI and plain Dart services, especially those arriving from Java or TypeScript. Three things punish overconfident assumptions: null safety is a compile-time guarantee rather than a runtime check, and null differs from an empty collection or string; one dynamic gives up the static type; and the collection API is chained, so where and map need toList before you hold a List. Covers types, functions, mixins, null operators, Future and Stream.

Variables & Types 10

var name = 'Dart';
Type inference
String name = 'Dart';
Explicit type
final name = 'Dart';
Final variable (runtime constant)
const pi = 3.14;
Compile-time constant
int? nullableInt;
Nullable type
late String lateVar;
Late initialization
dynamic anything = 42;
Dynamic type
List<int> numbers = [1, 2, 3];
List
Map<String, int> ages = {'Alice': 25};
Map
Set<int> unique = {1, 2, 3};
Set

Functions & Closures 6

int add(int a, int b) => a + b;
Arrow function
void greet(String name, [String? title]) { }
Optional positional parameter
void greet({required String name, int age = 0}) { }
Named parameters
Function fn = (x) => x * 2;
Closure
void Function(int) callback;
Function type
typedef Comparator<T> = int Function(T a, T b);
Type alias

Classes & Mixins 7

class Person { String name; Person(this.name); }
Class and constructor
class Student extends Person { }
Inheritance
class Flyable mixin Fly { }
mixin
class Bird extends Animal with Flyable { }
Use a mixin
abstract class Shape { double area(); }
Abstract class
class Singleton { static final instance = Singleton._(); Singleton._(); }
Singleton pattern
class Immutable { final int value; const Immutable(this.value); }
Immutable class

Null Safety 6

String? nullable;
Nullable declaration
nullable?.length
Safe call
nullable ?? 'default'
Null-coalescing operator
nullable!
Non-null assertion
if (nullable != null) { nullable.length; }
Type promotion
late String lateVar;
Late initialization (must be assigned before use)

Asynchronous 6

Future<int> fetchData() async { return 42; }
Async function
int result = await fetchData();
Await an async result
Stream<int> countStream() async* { yield 1; }
Async generator
await for (var value in stream) { }
Listen to a stream
Future.wait([future1, future2]);
Await multiple futures
Completer<int> completer = Completer();
Manually control a Future

Collection Operations 8

list.where((x) => x > 5).toList()
Filter
list.map((x) => x * 2).toList()
Map
list.reduce((a, b) => a + b)
Reduce
list.fold(0, (sum, x) => sum + x)
Fold
list.any((x) => x > 0)
Any exists
list.every((x) => x > 0)
All satisfy
list.sort((a, b) => a.compareTo(b))
Sort
list.groupBy((x) => x.category)
Group (needs the collection package)

Parameter matrix

ParameterEffectExample
var name = 'Dart';Type inference; later assignments must be compatible with the inferred type—do not use it to dodge type design.var name = 'Dart';
final name = 'Dart';Cannot be reassigned at runtime but can be late-initialized; the preferred modifier for class fields.final name = 'Dart';
const pi = 3.14;Compile-time constant that must be a known value; it can be inlined at use sites and is required for switch cases.const pi = 3.14;
int? nullableInt;Explicitly nullable type; Dart 3 is non-nullable by default, so any bare int cannot be null.int? nullableInt;
dynamic anything = 42;Disables static type checking, so type errors surface only at runtime; use it only for dynamic interop with JS/JSON.dynamic anything = 42;
List<int> / Map<String, int> / Set<int>The three collection types: Set deduplicates and is unordered, Map keys are unique; picking the wrong collection silently drops data.List<int> numbers = [1, 2, 3];
void greet(String name, [String? title])Optional positional parameters use square brackets and must follow required parameters; they can be omitted at the call site.void greet(String name, [String? title]) { }
void greet({required String name, int age = 0})Named parameters use curly braces and can carry defaults and required; preferable to positional parameters when the meaning is complex.void greet({required String name, int age = 0}) { }
typedef Comparator<T> = int Function(T a, T b);A function-type alias that makes callback signatures reusable and named.typedef Comparator<T> = int Function(T a, T b);
class Bird extends Animal with Flyable { }A mixin after extends mixes in behavior; this is Dart's main path for polymorphic reuse.class Bird extends Animal with Flyable { }
abstract class Shape { double area(); }An abstract class only declares, never implements, and cannot be instantiated directly.abstract class Shape { double area(); }
const Immutable(this.value)A const constructor makes the instance compile-time constant, so it can serve as a collection key or default value.class Immutable { final int value; const Immutable(this.value); }
nullable?.lengthNull-safe call; when the receiver is null the whole expression short-circuits to null.nullable?.length
nullable ?? 'default'Null-coalescing operator; takes the right side when the left is null. The opposite of !, so prefer it over force-unwrapping.nullable ?? 'default'
nullable!Asserts non-null. The compiler lets it pass but a runtime null throws immediately—this defers a compile error to production.nullable!
Future<int> fetchData() async { return 42; }An async function returns a Future that needs await to get the value; not awaiting silently drops exceptions.Future<int> fetchData() async { return 42; }
Future.wait([future1, future2])Awaits multiple Futures concurrently; if any fails the whole thing throws, so it does not suit "partial success" scenarios.Future.wait([future1, future2]);
Stream<int> countStream() async* { yield 1; }async* produces a single-subscription Stream consumed with await for or listen.Stream<int> countStream() async* { yield 1; }
Completer<int> completer = Completer();Completes a Future manually, used to bridge callback-style APIs into Futures.Completer<int> completer = Completer();
list.where((x) => x > 5).toList()Lazy Iterable; where/map do not evaluate eagerly, and forgetting toList() yields an Iterable rather than a List.list.where((x) => x > 5).toList()
list.reduce((a, b) => a + b)Reduces the Iterable element by element with no initial value; an empty collection throws StateError directly.list.reduce((a, b) => a + b)
list.fold(0, (sum, x) => sum + x)Reduction with an initial value; an empty collection returns the initial value instead of throwing, safer than reduce for accumulation.list.fold(0, (sum, x) => sum + x)
list.any((x) => x > 0) / list.every((x) => x > 0)Existence and universal checks that can short-circuit mid-iteration, cheaper than filtering then testing isEmpty.list.any((x) => x > 0)
list.sort((a, b) => a.compareTo(b))In-place sort requiring a comparator that returns negative/zero/positive; returning a bool throws a type error.list.sort((a, b) => a.compareTo(b))
list.groupBy((x) => x.category)Groups by key into a Map (provided by the collection package), the most direct one-liner for grouped statistics.list.groupBy((x) => x.category)

Common pitfalls

SymptomAt runtime you get "Null check operator used on a null value", crashing at a spot that looks impossible to be null.

CauseYou used ! to force-unwrap. The compiler has let the null check through, so encountering null at runtime throws immediately; null safety is supposed to be enforced by the compiler.

FixReplace ! with ?. or a ?? fallback; if it truly cannot be null, narrow the type with a local final variable at the assignment boundary before use.

SymptomAfter where / map you want to index into the result or pass it to an API needing a List, and get "The method [] was called on Iterable".

Causewhere and map return a lazy Iterable, while indexing requires the List interface.

FixAppend .toList() at the end of the chain; keep it lazy when you only iterate or map again, saving an intermediate allocation.

SymptomReducing over an empty list throws "Bad state: No element".

Causereduce has no initial value and seeds itself from the first element, which does not exist on an empty collection.

FixSwitch to fold with an explicit initial value (0 / 0.0 / []); this is the default approach for collections that may be empty.

SymptomAfter sort the order is completely wrong, or a runtime "Invalid argument(s)" is thrown.

CauseThe comparator returned a bool or String, but a comparator function must return an int.

FixUse a.compareTo(b) instead of a > b; for descending order write b.compareTo(a).

SymptomJSON received as dynamic compiles cleanly, and a type-cast exception only fires on some special production payload.

Causedynamic disables static checking, so type errors only surface when that branch actually runs.

FixPerform explicit conversion and validation right after parsing; do not let dynamic flow into business logic.

SymptomA Future is awaited but its exception is not caught, so the program silently skips a chunk of logic and continues.

CauseExceptions from a Future that is never awaited do not propagate; and Future.wait fails as a whole if any one fails, dropping straight into the zone when there is no try.

FixEvery async call must be awaited or explicitly handled with catchError; use runZonedGuarded / the framework error hook at the app entry point as a backstop.

SymptomThe same Stream processes events multiple times, or a second subscription throws outright.

Causeasync* produces a single-subscription Stream that errors when listened to twice; meanwhile recreating a new Stream on every build causes repeated subscriptions.

FixSwitch to a broadcast Stream when several consumers are needed, and close it when the widget is disposed to avoid stacked listeners in the UI layer.

SymptomAfter deduplicating with a Set, the order you get back has changed.

CauseSet does not guarantee insertion order (the default implementation is a hash set); only List preserves order.

FixFor ordered deduplication write list.toSet().toList(), or rely on Map's insertion-order behavior.

Troubleshooting

  1. 1Let the analyzer surface every null-safety problem.

    dart analyze

    Clear the warnings one by one as prompted; under strict mode every non-nullable reference must pass null checks.

  2. 2Format the code to stay consistent with project style.

    dart format .

    Unify formatting so pure style churn does not mix into diffs.

  3. 3Refresh dependency resolution.

    dart pub get

    On version conflicts or missing packages, refresh resolution first and check whether pubspec.lock is stale if needed.

  4. 4See which dependency versions actually take effect.

    dart pub deps

    Confirm the effective version, not the version range written in pubspec.yaml.

  5. 5Run with assertions to check invariants that should never be hit.

    dart --enable-asserts run bin/main.dart

    Once a branch in an assert is triggered, the problem is exposed immediately.

  6. 6Treat info as errors for a release gate.

    dart analyze --fatal-infos

    Stricter than the default; good for blocking new warnings in CI.

Command Examples

Four ways to handle a nullable value

String? nullable; final len = nullable?.length; // Safe call: the whole expression is null when it is null final name = nullable ?? "default"; // Null-coalescing: use the fallback when null // nullable!.length // Force non-null: throws immediately when null if (nullable != null) { print(nullable.length); // Type promotion: nullable is non-null here }

?. only guards against null along the call chain — one null makes the whole result null, and assigning it to an int then fails to compile on the implicit conversion. ?? yields a non-null fallback. ! throws on production data with a missing field, so reserve it for values you truly control.

Choosing between final, const, and late

var name = "Dart"; // Type inference, reassignable final name = "Dart"; // Resolved once at runtime, then immutable const pi = 3.14; // Compile-time constant, must be a literal late String cfg; // Deferred until assigned before first use cfg = load();

const only accepts compile-time constants (literals or const expressions), so const on a value computed at runtime fails to compile. Reading a late variable before assignment throws LateInitializationError — and because it only fires on one branch, it is far harder to reproduce than a null-safety error.

Await futures in parallel instead of one by one

Future<int> fetchA() async => 1; Future<int> fetchB() async => 2; final results = await Future.wait([fetchA(), fetchB()]); // Any single failure fails the whole thing try { await Future.wait([fetchA(), fetchB()]); } catch (e) { rethrow; }

Two consecutive awaits run serially and their latencies add up; passing both futures to Future.wait starts them in parallel so total time is the slowest one. By default any failure fails the whole wait — partial success requires catching each future yourself.

Chaining collection operations: filter, map, reduce

List<int> nums = [1, 2, 3, 4, 5, 6]; final evens = nums.where((x) => x.isEven).toList(); final doubled = nums.map((x) => x * 2).toList(); final total = nums.reduce((a, b) => a + b); final hasNeg = nums.any((x) => x < 0); final allPos = nums.every((x) => x > 0); nums.sort((a, b) => a.compareTo(b));

where and map are lazy — without toList() nothing is actually iterated, which saves intermediate lists in long chains. fold(0, ...) reads better than reduce when you need a starting value. any and every short-circuit, so the order of cheap checks matters on large lists.

Immutable class plus singleton: const constructor and static final

class Immutable { final int value; const Immutable(this.value); } class Config { static final instance = Config._(); Config._(); }

final fields plus a const constructor let the compiler treat instances as constants and simultaneously forbid mutation; drop one final and you are back to a mutable object. static final only means "initialized once" — it is not a thread-safety or lazy-loading guarantee, so heavy work in a singleton still needs your own lock.

Common Pitfalls

  • Using ! skips the compiler check: local fixtures look fine, but the moment a production record lacks the field, that line is an uncaught exception — a blanket 500 with only "null check operator used on a null value" in the log.
  • const requires a compile-time constant, so applying it to a runtime-computed value fails to compile. Use final when you only mean "assigned once, immutable"; mixing the two makes intent hard to read.
  • Reading a late variable before its first assignment throws LateInitializationError. If the assignment happens in an async callback or somewhere after construction, the failure only appears in one execution order and is very hard to reproduce.
  • Consecutive awaits run serially; mistaking them for parallel work multiplies endpoint latency. Collect the futures and wait on them together when you mean parallel.
  • An await-for over a Stream terminates the subscription silently when the body throws, and an outer try/catch may never see it. Cancel the subscription in a finally block or the listener stays attached.
  • Crossing into dynamic turns off null-safety checking: member access is only validated at runtime. Treating dynamic as an escape hatch pushes every null dereference the compiler would have caught straight into production.

Tips

  • Dart's null safety is a compile-time check; use ? and ?? to avoid runtime exceptions.
  • async/await is core to asynchronous programming; avoid blocking the UI thread.
  • mixin is Dart's way to achieve multiple inheritance; good for code reuse.

FAQ

How does Dart null safety differ from an optional-chaining style approach?

Dart treats null safety as a compile-time concern. Declaring String? nullable marks the variable as legitimately nullable, and writing nullable.length is a compile error until you deal with it — there are four tools. nullable?.length short-circuits the whole chain to null if any link is null. nullable ?? 'default' substitutes a default only for null. Type promotion makes an if (nullable != null) { } block treat the variable as non-null inside, with no cast needed. And nullable! asserts non-nullness to the compiler, throwing at runtime if the promise is wrong, so use it sparingly. The guiding idea: make null visible in the type rather than discovering it at runtime.

What separates final from const in Dart?

Both prevent reassignment, but they decide at different moments. const pi = 3.14 is a compile-time constant: the value is fixed at compile time and can participate in const expressions, such as living inside a const list or serving as a switch case. final name = 'Dart' is a runtime constant — the binding never changes after initialisation, though the object it points to may still be mutable. A class of const Immutable(this.value) with a final int value field gives you an immutable class whose instances can never be rewritten afterwards. Use final when the value is computed at runtime but must not change again; use const when it must take part in compile-time computation or a constant context.

Dart allows only one superclass — how does mixin solve horizontal reuse?

The single-inheritance limit shows up as class Student extends Person { }, while mixin covers reuse across unrelated hierarchies: declare the capability with class Flyable mixin Fly { }, then mix it into any class with class Bird extends Animal with Flyable { }. The same Fly behaviour is now shared by many classes without copy-pasting methods and without colliding with the one-superclass rule. The key constraint is that a mixin may not declare a constructor — it is a bundle of behaviour, not a full class. If all you need is to share a few functions without an inheritance relationship at all, a type alias such as typedef Comparator<T> = int Function(T a, T b) is the lighter tool.

When should I reach for a Future versus a Stream?

A Future represents a single eventual result — Future<int> fetchData() async { return 42; }, consumed with int result = await fetchData() — which suits one-off requests or computations. A Stream represents a sequence: declare it with Stream<int> countStream() async* { yield 1; } and consume it with await for (var value in stream) { }, which fits continuously produced data such as paginated loading, progress updates or records arriving one by one. When several independent futures must complete together, use Future.wait([future1, future2]); when a Future must be completed from outside, use Completer<int>(). Note that yielding multiple values requires the async* generator form before await for can drain it.

reduce and fold both aggregate a list — what is the actual difference?

The difference is the initial value. list.reduce((a, b) => a + b) requires a non-empty list and uses the first element as the accumulator, with the return type forced to match the element type — so reducing a list of strings into a number will not compile. list.fold(0, (sum, x) => sum + x) takes an explicit seed, which means an empty list still yields that seed, and the accumulator type may differ from the element type: fold<String>('', (s, x) => s + x) turns [1, 2, 3] into "123". Rule of thumb: reduce for non-empty collections of a consistent type, fold when you need an empty-collection fallback or a cross-type accumulation, then chain it after list.where or list.map when the input needs filtering first.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us