← Articles

Introduction to Dart: a language built for client apps

By Ann Tech · 11 March 2026

Dart was designed for building user interfaces. That design intent explains features that seem redundant with other languages — until you use them in a UI context.

Null safety

Null pointer exceptions are the most common crash in mobile apps. Dart's sound null safety makes null-related crashes a compile-time error rather than a runtime surprise:

String name = 'Alice';       // Non-nullable — guaranteed never null
String? nickname;            // Nullable — must be checked before use

final upper = nickname?.toUpperCase();   // Safe access — returns null if nickname is null
final display = nickname ?? name;        // Null-coalescing
nickname ??= 'Ali';                      // Null-aware assignment

Async/await for UI responsiveness

Network calls, database reads, and file access are all async. Dart's async/await makes sequential async code readable:

Future<void> loadDashboard() async {
  setState(() => isLoading = true);
  try {
    final user = await userRepo.getUser(userId);
    final orders = await orderRepo.getOrders(userId);
    setState(() {
      _user = user;
      _orders = orders;
      isLoading = false;
    });
  } catch (e) {
    setState(() { error = e.toString(); isLoading = false; });
  }
}

// Parallel fetches — both requests fire simultaneously
Future<void> loadDashboardFast() async {
  final results = await Future.wait([
    userRepo.getUser(userId),
    orderRepo.getOrders(userId),
  ]);
}

Isolates for background work

Dart is single-threaded within an isolate. CPU-heavy work on the main isolate blocks the UI thread and causes jank:

// Move JSON parsing off the UI thread
final products = await compute(parseProducts, rawJsonString);

List<Product> parseProducts(String json) {
  final list = jsonDecode(json) as List;
  return list.map((e) => Product.fromJson(e as Map<String, dynamic>)).toList();
}

// For longer-running work:
final result = await Isolate.run(() => expensiveComputation(data));

Extension methods

extension StringX on String {
  bool get isValidEmail =>
      RegExp(r'^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);

  String capitalize() =>
      isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
}

'[email protected]'.isValidEmail  // true
'hello'.capitalize()              // 'Hello'

AOT vs JIT compilation

  • JIT (development): Dart VM interprets code — enables hot reload
  • AOT (release): compiles to native ARM — fast startup, no VM overhead

This dual-mode explains why Flutter hot reload is instant yet production builds perform like native apps.

Records and patterns (Dart 3)

// Records: lightweight anonymous tuples with named fields
(String name, int age) getUser() => ('Alice', 30);
final (name, age) = getUser();

// Exhaustive pattern matching
switch (shape) {
  case Circle(radius: final r): return math.pi * r * r;
  case Rectangle(width: final w, height: final h): return w * h;
}

Common pitfalls

Using dynamic to avoid type errors. dynamic opts out of type checking entirely. Mistakes become runtime crashes instead of compile-time errors. Use generics or sealed classes instead.

Blocking the event loop. Any synchronous work over ~2ms — large JSON parsing, image processing, sorting large lists — causes dropped frames on the UI thread. Always offload heavy computation to compute() or Isolate.run().

Sign in to like, dislike, or report.

Introduction to Dart: a language built for client apps — ANN Tech