Learn Dart - Core Concepts & Dart Language Architecture
Series/Learn Dart/Episode 2
Episode 2 of 23

Learn Dart - Core Concepts & Dart Language Architecture

This episode dissects how Dart works behind the scenes: the runtime with the JIT and AOT model, the library and package-based program structure, the type system with sound typing, and the Future and Stream foundations for asynchronous programming.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

To become proficient in Dart, you need to understand what happens behind the scenes: how your code is executed, how programs are structured, and how the type system protects you from bugs. Episode 2 dissects Dart's language architecture from the runtime to the type system.

We'll cover the JIT and AOT compilation models, the library and package-based program structure, the concept of sound typing along with null safety, and the Future and Stream foundations. This material is the lens that will make all the following episodes make sense.

Don't worry if some terms feel abstract. Focus on understanding the concepts, while the syntax details you'll master in episodes 4 through 7.

Dart Runtime and Compilation Model

Dart VM with JIT

During development, Dart code runs on the Dart VM using JIT (Just-In-Time). JIT compiles code while the application is running, enabling features like hot reload and hot restart in Flutter, as well as dart run which executes scripts directly. The instant startup makes the development loop feel light.

AOT for Production

When the application is ready for release, Dart can be compiled AOT (Ahead-Of-Time) into native machine code. AOT eliminates the compilation cost at runtime, so startup is faster, memory is more stable, and performance is more predictable. The command used:

Compile AOT native
dart compile exe bin/app.dart -o bin/app

The result is a native executable that doesn't require the Dart SDK to run. The command dart compile exe bin/app.dart produces a binary file that can be run directly on the target machine without a separate runtime.

dart2js and the Flutter Engine

For web, Dart is compiled to JavaScript by dart compile js. Meanwhile, for Flutter applications, Dart code is compiled to machine code and run inside the Flutter engine, which is written in C++. One language, many targets: native, browser, and mobile.

Dart Program Structure

Library, Import, and Part

All Dart code lives in a library. Every Dart file is an implicit library, and you can combine libraries with import to reuse code from other files or packages. The lib/ directory is where importable code lives, while bin/ is for entry points.

There's also a part mechanism for splitting one library into several files — but modern practice recommends import over part. Here's an example folder structure from a project created with dart create:

Dart project structure
my_app/
├── bin/my_app.dart
├── lib/my_app.dart
├── test/my_app_test.dart
├── pubspec.yaml
└── analysis_options.yaml

This structure is already set up by dart create my_app and we'll explore it further in episodes 3 and 8.

Package as a Distribution Unit

Above the library, there's the package — a distribution unit that has a pubspec.yaml and can be published to pub.dev. A package combines code, metadata, dependencies, and tests. This concept is similar to a crate in Rust or an npm package in JavaScript.

By understanding this hierarchy — file, library, then package — you can determine where code should be placed and how it's imported by other code.

Data Types, Null Safety, and Sound Typing

Dart is a language with sound typing: a variable's type is guaranteed by the compiler, and type errors are detected before the program runs. Combined with sound null safety, the compiler distinguishes nullable and non-nullable types, so null-pointer errors that are common in other languages can be prevented at compile time.

A short example of the type system:

Static types and null safety
void main() {
  String nama = 'Dart';
  int versi = 3;
  print('$nama versi $versi');
}

In the code above, String nama states that nama will never be null. If you try to assign null, the compiler rejects it before the application runs — that's the essence of sound typing, which we'll fully dissect in episode 6.

Dart's types fall into three main groups: int, double, and bool for basic values, String for text, and collection types like List, Set, and Map. All these types obey the same null safety and sound typing rules, so the principles you learn apply consistently across your whole program.

Async, Future, and Stream

Operations like reading files, making HTTP calls, or waiting for network input must not block the main thread. Dart solves this with two fundamental tools:

  • Future: a representation of a value that will be available in the future, similar to a promise in JavaScript.
  • Stream: a flow of data that can produce many values over time, like events from a socket.

Both are consumed with async and await. An example of using Future:

Future with async await
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 1));
  return 'data siap';
}
 
void main() async {
  var hasil = await fetchData();
  print(hasil);
}

await fetchData() delays execution without blocking the thread, because the runtime suspends that function. The full details of async and Stream will be covered in episode 7.

Conclusion

Key takeaways:

  • Dart uses JIT during development for hot reload and AOT at release time for performance.
  • dart compile exe produces a native binary without a runtime; dart compile js for web.
  • Dart programs are built from libraries, imports, and packages with pubspec.yaml.
  • Sound typing and null safety move detection of many bugs to compile time.
  • Future represents a single future value; Stream represents many values over time.

In the next episode 3, we'll install and run Dart hands-on — SDK verification, the dart repl REPL, the dart run command, creating a project with dart create, and adding your first dependency to pubspec.yaml. Get your terminal ready to start executing.