Dart runs on a single-threaded event loop with an isolated memory model. Unlike languages with Threads (Java, C++, Rust), Dart uses event-driven concurrency — the single thread processes Events from a queue, interleaving async operations without blocking.
This design is fundamental to Flutter”s architecture: the UI must remain responsive (60 fps) while Performing I/O (network requests, file reads, database queries). If any operation blocks the thread, The entire UI freezes.
flowchart TD
A["Event Loop"] --> B{"Is the queue empty?"}
B -->|No| C["Dequeue next event"]
C --> D["Execute event handler"]
D --> E{"Handler complete?"}
E -->|Yes| B
E -->|No<br/>(async result pending)| F["Register callback<br/>in microtask queue"]
F --> G["Yield to event loop"]
G --> B
B -->|Yes| H["Idle<br/>(wait for next I/O event)"]
H --> B A Future<T> represents a value that will be available at some point in the future — either a value Of type T or an error. It is Dart’s equivalent of JavaScript’s Promise or Rust’s Future.
// From a computation (runs on the event loop when awaited)
Future < int > computeSquare ( int n) async {
// From a callback-based API
Future <http. Response > fetchUser () {
return http. get ( Uri . parse ( 'https://api.example.com/user' ));
// With Future.value (immediately resolved)
Future < String > cachedGreeting () {
return Future . value ( 'Hello' );
// With Future.delayed (resolved after a delay)
Future < void > delayedGreeting () async {
await Future . delayed ( Duration (seconds : 1 ));
print ( 'Hello after 1 second' );
// With Future.error (immediately rejected)
return Future . error ( Exception ( 'Something went wrong' ));
The async/await syntax is syntactic sugar for working with Futures. async marks a function as Asynchronous, and await suspends execution until the Future completes:
// Without async/await (callback style)
Future < void > loadData () {
return http. get ( Uri . parse ( 'https://api.example.com/data' )). then ((response) {
var data = jsonDecode (response.body);
print ( 'Got ${ data [ 'items' ]. length } items' );
// With async/await (linear style — easier to read and reason about)
Future < void > loadData () async {
final response = await http. get ( Uri . parse ( 'https://api.example.com/data' ));
final data = jsonDecode (response.body);
print ( 'Got ${ data [ 'items' ]. length } items' );
Future < int > fetchAge () async {
final response = await http. get ( Uri . parse ( 'https://api.example.com/user' ));
if (response.statusCode != 200 ) {
throw Exception ( 'Failed to fetch user' );
return jsonDecode (response.body)[ 'age' ] as int ;
Future < void > example () async {
final age = await fetchAge ();
} on FormatException catch (e) {
print ( 'Invalid JSON: $ e ' );
} on http. ClientException catch (e) {
print ( 'Network error: $ e ' );
print ( 'Unexpected error: $ e ' );
// catchError on the Future chain
Future < void > example () async {
final age = await fetchAge (). catchError ((e) {
// Sequential: each await blocks until the Future completes
Future < void > sequential () async {
final user = await fetchUser (); // 1 second
final orders = await fetchOrders (user.id); // 1 second
final profile = await fetchProfile (user.id); // 1 second
// Parallel: all Futures start immediately, await all results
Future < void > parallel () async {
final results = await Future . wait ([
// Total: 1 second (all run concurrently)
final orders = results[ 1 ];
final profile = results[ 2 ];
// Parallel with named results
Future < void > parallelNamed () async {
final userFuture = fetchUser ();
final ordersFuture = fetchOrders ( 'user-1' );
final profileFuture = fetchProfile ( 'user-1' );
// Each await blocks only until its own Future completes
final user = await userFuture;
final orders = await ordersFuture;
final profile = await profileFuture;
For dependent operations where the result of one is needed by the next.A Stream<T> is a sequence of asynchronous events. While a Future<T> delivers a single value, a Stream<T> delivers zero or more values over time. It is Dart’s equivalent of Rust’s Stream or JavaScript’s Observable.
Type Description Use Case Single subscription One listener only; events are buffered if no listener exists File I/O, HTTP response body Broadcast Multiple listeners; events are discarded if no listener exists UI events, sensor data, WebSocket messages
// Creating a single-subscription stream
Stream < int > countStream ( int max) async* {
for ( var i = 1 ; i <= max; i ++ ) {
await Future . delayed ( Duration (seconds : 1 ));
// Creating a broadcast stream
final controller = StreamController < String >. broadcast ();
controller.stream. listen ((event) => print ( 'Listener 1: $ event ' ));
controller.stream. listen ((event) => print ( 'Listener 2: $ event ' ));
controller. add ( 'Hello' ); // Both listeners receive it
final numbers = countStream ( 10 );
final doubled = numbers. map ((n) => n * 2 );
final evens = numbers. where ((n) => n % 2 == 0 );
final sum = await numbers. reduce ((a, b) => a + b);
// async* — generator function for streams
Stream < int > fibonacci () async* {
// Take first N elements (limits an infinite stream)
await fibonacci (). take ( 10 ). toList (); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// Transform with async* (like Rust's .flat_map)
Stream < String > fetchNames ( List < int > ids) async* {
final user = await fetchUserById (id);
For creating streams from events:
final _controller = StreamController < Event >. broadcast ();
Stream < Event > get events => _controller.stream;
void emit ( Event event) => _controller. add (event);
void dispose () => _controller. close ();
Dart’s answer to threads. Each isolate has its own memory heap and event loop — there is no Shared state between isolates. Communication is via message passing (ports), similar to Erlang Processes or Rust’s mpsc channels.
flowchart LR
subgraph "Main Isolate"
A["Event Loop"] --> B["Memory Heap"]
A --> C["SendPort<br/>(sends messages)"]
end
subgraph "Worker Isolate"
D["Event Loop"] --> E["Memory Heap<br/>(independent)"]
D --> F["ReceivePort<br/>(receives messages)"]
end
C -- "message passing" --> F
F -- "message passing" --> C // The entry point for the new isolate — must be a top-level function
void _isolateEntry ( SendPort sendPort) {
final receivePort = ReceivePort ();
sendPort. send (receivePort.sendPort);
receivePort. listen ((message) {
if (message == 'shutdown' ) {
final result = _expensiveComputation (message as int );
int _expensiveComputation ( int n) {
// CPU-intensive work that would block the UI
return List . generate (n, (i) => i * i). reduce ((a, b) => a + b);
// Spawning and communicating
Future < int > runInIsolate ( int input) async {
final receivePort = ReceivePort ();
await Isolate . spawn (_isolateEntry, receivePort.sendPort);
final sendPort = await receivePort.first as SendPort ;
final responsePort = ReceivePort ();
sendPort. send (responsePort.sendPort);
final result = await responsePort.first as int ;
For simple use cases, Isolate.run simplifies the API:
Future < int > heavyComputation ( int n) async {
return await Isolate . run (() {
return List . generate (n, (i) => i * i). reduce ((a, b) => a + b);
Flutter provides compute() for running functions in isolates:
final result = await compute (expensiveFunction, inputData);
Event from the event queue is processed. This is why `Future.then` callbacks (which schedule Microtasks) run before `Future.delayed` callbacks (which schedule events).The event loop is a single receptionist handling one call at a time: Imagine a receptionist who takes a call (event), hands it off to the right department (async operation), and immediately picks up the next call while waiting. That’s Dart’s event loop — it never blocks, never puts anyone on hold. A Future is like a promise slip: “we’ll call you back when the result is ready.” async/await is the receptionist reading from a to-do list — “when X finishes, do Y.” Isolates are separate offices — each with their own receptionist and files, communicating only through inter-office mail (message passing).
Why it matters: This model makes Flutter UIs responsive by design — the event loop always returns to processing UI events between async operations. But CPU-heavy work on the main isolate blocks the receptionist, freezing the entire app.
The key insight: Dart’s concurrency model trades shared-memory threads for isolated message-passing — eliminating data races by architecture, not by discipline.
// Bad: fire-and-forget — errors are silently swallowed
Future < void > processData () async {
fetchUser (); // WARNING: Not awaited -- errors are unhandled
// Good: await the future
Future < void > processData () async {
// Or explicitly ignore errors if that's intentional
Future < void > processData () async {
fetchUser (). ignore (); // From package:async
// Bad: synchronous I/O blocks the thread
final file = File ( 'large.txt' ). readAsStringSync (); // Blocks!
final file = await File ( 'large.txt' ). readAsString (); // Doesn't block
// Bad: CPU-intensive computation on the main isolate
final result = heavyComputation ( 1000000 ); // Blocks UI!
// Good: use Isolate for CPU work
final result = await Isolate . run (() => heavyComputation ( 1000000 ));
Dart constructors cannot be async. Use a static factory method instead:
static Future < Database > connect ( String uri) async {
final conn = await Connection . open (uri);
This topic covers the core concepts of async and futures, including underlying theory, practical implementation, and key applications.
Key concepts include:
core concepts and terminology algorithms and computational thinking practical implementation security and ethical considerations applications in the real world Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Error Handling : Try-catch patterns for Future errors and zone error handlers.Entry Point : How the event loop initializes from main().Variables : Nullable and late variables in asynchronous code.Best Practices : Recommended patterns for async/await and isolate usage.