Dart: What Is Lost in the East Is Gained in the West
A month ago, if I had ranked the three and a half languages I held in lowest esteem, Dart would probably have made the list. Dart was a programming language Google launched back in 2011, aiming to be a structured web programming language and harboring the ambition to replace the JavaScript that people use every day and curse every day. I remember watching a GOTO Conference keynote about Dart on YouTube around 2014[1], which led me to pay some attention to it. My reaction at the time was: oh great, yet another language that “compiles to JavaScript”—and heaven knows how many of those we needed. The front-end ecosystem around 2014 was nowhere near as diverse as it is today—nowadays a programming language without a corresponding “compiles to JavaScript” path is like a girl without makeup: she is too embarrassed to leave the house. Back then almost no one used TypeScript; among “compiles to JavaScript” languages, the most popular were CoffeeScript and the critically admired but commercially underwhelming Elm. Of course, Google launched Dart more out of engineering-practice considerations: it had already learned a valuable lesson from GWT (Google Web Toolkit) and Closure Compiler (used to optimize JS and eliminate dead code)—with a static type system, JavaScript performance optimization and large-project engineering could be raised to another level.
After watching that keynote, I gave Dart a quick try and found the language unremarkable, without much character. The only memorable thing was actually a line from the keynote: In Dart, you are innocent until proven guilty.
Later, Dart gradually matured and for a time seemed poised to become the second built-in web language in the then-ascendant Chrome. The media, always eager to fan the flames, even called it a JavaScript killer. To test the market’s reaction, Google released the Dartium project, a version of Chrome that included the Dart VM. Then, for reasons that remain unclear, in 2015 the Chrome team ultimately decided not to build support for Dart into Chrome[2]. Dartium, still in its infancy, died a quiet death, and the attention Dart had gathered quickly dissipated. In the years that followed, apart from Google teams that continued using Dart for their own front-end applications, industry adoption of Dart was pitifully small. Meanwhile, TypeScript—a superset of JavaScript—steadily gained popularity thanks to its friendliness to the JavaScript ecosystem and its powerful, flexible type system, and gradually became the front-end community’s preferred “compiles to JavaScript” language. With one retreating and the other advancing, the front-end world seemed to be pronouncing a death sentence on Dart.
But the world is strange: often, just when the mountains and rivers run out and there is nowhere left to go, a turning point lies hidden. Dart did not become the dragon-slaying warrior its creators had envisioned, but many of its design choices inadvertently became the decisive factors in Flutter’s later choice of Dart. Steve Jobs said:
You can’t connect the dots looking forward; you can only connect them looking backwards. So you have to trust that the dots will somehow connect in your future.
It really does feel as if fate had a hand in it all along.

Let us compare Dart and TypeScript and look at what they share:
- Both were designed primarily with front-end development as the language’s main goal.
- Both considered JavaScript lacking and hoped a new language could solve JavaScript’s many problems.
- Both believed a strong type system helps build safe, complex front-end projects.
- Both recognized that the type system should help developers rather than hinder them (you are innocent until proven guilty).
- Both made themselves object-oriented languages, including static type systems, interfaces, abstract classes, generics, mixins, and so on.
From these shared premises, Dart and TypeScript began to diverge:
- Dart created a new language from scratch, while TypeScript tried to be a superset of JavaScript.
- Dart abandoned JavaScript’s vast ecosystem, while TypeScript embraced the existing ecosystem.
- Dart leaned more toward building UIs, while TypeScript leaned more toward building large applications.
- Dart supports JIT and AOT compilation, and its AOT compilation target can be machine code or JavaScript; TypeScript can only compile to JavaScript.
- Dart might support WebAssembly in the future (similar to Go—whether it is meaningful is another matter); TypeScript has no such possibility.
From the perspective of replacing JavaScript, TypeScript did almost everything right, while Dart got it wrong from the very first step. Creating a new language from scratch and disregarding JavaScript’s vast, vibrant ecosystem was an act of “destroy before rebuilding,” of “letting go in order to gain,” and it required the kind of courage Liang Jingru sings about. Most of the time, a high-stakes gamble on “destruction” means “defeat,” not “rebuilding”; and “letting go” does not necessarily mean “gaining.” Dart was lucky enough to hold on until Flutter appeared, fulfilling the line: “Who has not faced violent storms and driving rain? Keep faith until the clouds part and the moon shines through.”
Developer Experience and Runtime Efficiency
Before I had much exposure to Dart, I disliked the language on the assumption that it was a case of “Why must Yu exist when Zhuge Liang does?” After trying Flutter and being “forced” to use Dart, I began to appreciate it little by little. Dart is quite different from many languages I had used before: some languages prioritize development-time efficiency, such as Python, JavaScript, and Elixir, but pay for it with runtime efficiency; others prioritize runtime efficiency but damage development efficiency, such as C, Go, and Rust. In our usual way of thinking, development efficiency and runtime efficiency are like fish and bear’s paw—you cannot have both. Dart is one of the rare languages that tries to have both[3].
In January and February of this year, I spent a fair amount of spare time studying Rust. Rust is the kind of language you fall in love with once you get past the initial learning curve, but developing Rust programs can be maddening—not because of fighting the compiler, but because of waiting for compilation. Perhaps it was because I used too many build scripts (my code used gRPC); for a few thousand lines of code, an incremental build often took tens of seconds. That is an awful experience—longer compile times mean a longer feedback loop. Someone might offer the strained rebuttal that instant feedback encourages developers to start coding without thinking carefully and to develop the bad habit of writing and debugging simultaneously—but you still have to admit that instant feedback is a revolution in development efficiency.
In contrast to Rust, Dart is a language that is highly efficient during development and has decent performance as well. When we talk about the performance of a language, we usually mean:
- Smaller code size
- Faster startup time
- Higher throughput
- Lower latency
These factors mean different things at development time and at runtime, and we often consider only runtime needs while neglecting development-time needs:
- Smaller code size: at development time, code size does not matter.
- Faster startup time: at development time, startup time matters a great deal, especially the startup time spent reloading and the time needed to restore the previous running state.
- Higher throughput: at development time, throughput also does not matter.
- Lower latency: at development time, what matters more is the latency between modifying the program and seeing the change take effect.
Dart provides distinctly different solutions for these two very different sets of needs:
- Development time: JIT compilers, such as the Dart VM and dartdevc.
- Runtime: AOT compilers, such as dart2native and dart2js.

The purpose of a JIT compiler is simple: compile the code you just wrote into code for the target platform as quickly as possible. Because speed is the priority, it sacrifices many parsing, analysis, and optimization steps. For developers, JIT brings lower development latency; for users, JIT offers few benefits—efficiency is not high, cold start is slow, and it is not particularly friendly to users. An AOT compiler, by contrast, must go through every step from a compiler-construction course, and some steps may be repeated back and forth many times (as in Rust). This is very friendly to users, greatly improving cold-start speed and runtime efficiency; however, AOT is very unfriendly to developers—just look at the extra startup time and runtime in the figure below:

For example, consider the same “compile to JavaScript” scenario. At development time, dartdevc translates each Dart file into JavaScript separately, so the code size is large and not well optimized; at runtime, dart2js compiles all the dependencies of the current project together, performs tree shaking, and optimizes the generated code according to the call tree. In the end, the code shipped to users is very compact, and execution efficiency is the best possible.
In this way, from a product perspective, the interests of both developers and developers’ users are taken into account, and the user experience is wonderful—but the Dart team’s workload is much greater. A new language feature must be added to different compilers, and optimization strategies for different scenarios must be considered. As Dart’s support for native platforms grows stronger and the number of supported platforms increases, this work will become ever more tedious. I think this is also why most languages take care of only one end of the spectrum.
Language Features
Most of the time, the Dart used in Flutter is for drawing UIs, and anyone with a programming background can pick up that part of the syntax within ten minutes by looking at examples. But since I was using Dart because I was trying Flutter, I still needed to get a rough understanding of Dart’s language features.
There is not much to say about Dart’s object-oriented features. If you come from a Java/C# background, its interfaces, generics, and type system are easy to understand; they are largely the same. If you come from the front-end world with a Kotlin/Swift background, or if you started on the backend and are a devotee of Rust/Scala/Haskell, then you will most likely be somewhat disappointed by Dart’s type system. At the language level, Dart does not fully support ADTs (algebraic data types); it has product types (classes) but no sum types (tagged unions), which makes it awkward to elegantly express complex data structures involving an “or” relationship. Pattern matching, which is often the twin sibling of sum types, is also unsupported in Dart.
Dart’s concurrency model is very appealing—at least it suits my taste. It is heavily influenced by Erlang and provides isolates similar to Erlang processes. In Dart, each isolate has its own stack and heap; isolates “share nothing” and can pass data only by sending and receiving messages. Each isolate performs GC independently, which is very similar to Erlang’s GC; as a result, memory allocation and reclamation require no locks, largely avoiding Java’s STW problem. Communication between isolates in Dart is shown in the figure below—anyone familiar with the Erlang VM will probably smile knowingly:

An isolate can be created with Isolate.spawn, and its lifecycle can later be ended with isolate.kill(). Each isolate has a receiving port, similar to an Erlang process’s mailbox, which can be used to receive messages. Unlike Erlang, Dart’s isolates do not have mechanisms like links and monitors for monitoring isolate status. I still have not quite figured out how, when two isolates are communicating and one dies, the other gets notified (perhaps via ping?).
Inside each Dart isolate runs an event loop that handles events on that isolate. As in JavaScript, every asynchronous event in Dart is a future object, and the language itself provides async/await as syntactic sugar. In a web environment, an isolate is executed by a web worker; in a native environment, an isolate may be run by a thread, but note that the same thread cannot handle two different isolates at the same time.
Another interesting Dart feature is the snapshot. As the name suggests, a snapshot allows Dart to save and serialize the current VM context so that execution can later be resumed from the snapshot. Snapshots are currently used mainly to speed up Dart application startup, but in the future they might be used in many interesting scenarios: for example, reproducing bugs—a complex bug could be easily reproduced for a programmer by saving a snapshot.

Runtime
In the development environment, the Dart runtime includes the Common Front-End (CFE) and the VM. The Dart CFE provides a code compilation service (compiler) and a code analysis service (analyzer); the code analysis service is what serves code editors such as VS Code and Android Studio. Dart’s code analysis service is excellent: whether it is type inference, autocompletion, or code navigation, compared with the languages I use more often, Elixir and Rust, its response speed is first-rate and it never stutters. This makes writing Dart code in VS Code very comfortable. Especially since Dart 2.5, the CFE’s code analysis service has also embedded TensorFlow Lite for machine-learning-based code autocompletion. This is an ultimate killer feature designed from user needs—I cannot think of any other programming language front end that cares so much about user experience—and I am certain that more programming languages will quickly follow suit in this area. I believe that as investment in machine-learning capabilities for CFEs across languages increases, writing code will become easier and easier.

When running Dart code, the Dart source is translated by the CFE into a kernel binary and handed to the VM for execution:

Almost all JIT languages have a similar process.
However—
Dart has another mode of operation: the way we run code on a device emulator when running Flutter:

At first glance, isn’t Android/iOS interaction with the emulator similar? You cross-compile code for the target system on the user’s operating system and sync it to the target system to run. But here is where Dart JIT’s convenience shows: after the first full compilation, changes to the code require compiling and transmitting only the modified parts, and the VM is responsible for updating the modified code on the target system. Thus the program can be updated while preserving its existing state—this is the killer feature Flutter is proud of: hot reload. You can see that Dart borrowed deeply from Erlang’s code server hot-reload capability and expressed hot reload to the fullest in client-side development.
Server-Side Support
True enough, Dart is a front-end language, and many of its syntax features were born for the front end, such as UI as code:

But Dart’s AOT compilation also gives it considerable potential for backend development. Unlike JavaScript, whose runtime is confined to a single process, in Dart you can safely perform highly concurrent operations by using isolates. As discussed above, this benefits from its Erlang-like actor model. From this angle, Dart is more than capable of crushing Python and Ruby, which are trapped by the GIL (Global Interpreter Lock). Today, several API frameworks written in Dart have emerged, such as Aqueduct[6] (shown below), Angel[7], and others.

In addition, Dart has Google’s native support for HTTP/2 and gRPC (legitimate or not, it is still Google’s own child), which makes it relatively easy to write high-performance gRPC services in Dart. Dart has fairly mature FFI support, which allows it to interact conveniently with C/C++/Rust and extend its capabilities.
Dart’s efforts in front-end rendering—such as support for SIMD and GPUs—also give it significant potential in high-performance computing.
In the broader server-side development ecosystem, Dart is still a baby waiting to be fed. But from the perspective of the language itself, it has ample potential; it just depends on which direction it continues to develop.
In terms of performance, although Dart can hardly match C/C++/Rust, its ceiling can approach—or even reach—the heights of Google’s eldest son, Go. How close it gets specifically will depend on what strategic position Google assigns to Flutter/Dart.
Conclusion
This week of experiments with Flutter greatly increased my fondness for Dart. Despite its various shortcomings, I have to say that it has a unique charm. Sometimes we really must admit that everyone has “unknown unknowns”—you don’t know what you don’t know. Programming languages are much like people: before you put one in the right place, it can seem as useless as the “medicine that prevents chapped hands” mentioned by Zhuangzi. That is why we often sigh: Fast horses are common, but Bo Le, who recognizes them, is rare. Thanks to Flutter, I had the chance to study Dart at a superficial level and thereby fill in some blind spots in my understanding. And thanks to Flutter, although Dart never got the chance to defeat JavaScript and become the world-famous Harry Potter, it survived the flames, gathered its strength, and finally grew into the one who wielded the sword of Gryffindor and struck down Nagini—Neville Longbottom.
References
- Dart: A Language For Structured Web Programming: https://www.youtube.com/watch?v=UZOIAz-eR34
- Google will not integrate its Dart programming language into Chrome: https://techcrunch.com/2015/03/25/google-will-not-integrate-its-dart-programming-language-into-chrome/
- Dart: Productive, Fast, Multi-Platform - Pick 3: https://www.youtube.com/watch?v=J5DQRPRBiFI
- Introduction to Dart VM: https://mrale.ph/dartvm/
- Announcing Dart 2.5: Super-charged development: https://medium.com/dartlang/announcing-dart-2-5-super-charged-development-328822024970
- Aqueduct framework: https://aqueduct.io/
- Angel framework: https://angel-dart.dev/
- How to Call a Rust Function from Dart Using FFI: https://itnext.io/how-to-call-a-rust-function-from-dart-using-ffi-f48f3ea3af2c