macOS Performance Comparison: Flutter Desktop vs. Electron

13 min read

In the cross-platform world, Flutter and JavaScript are top choices among devs and engineering teams. This article explores the performance differences between Flutter and Electron–two solutions to get your application running on desktop. Both are popular options, but there are performance differences that are worth considering.

Gordon H.
Gordon H.
Published October 11, 2021
Flutter Desktop vs. Electron for desktop app development

This article covers the following:

  • The underlying engines and technologies powering Flutter and Electron.
  • Important performance considerations and how they relate to Flutter Desktop and Electron.
  • Real performance metrics demoing example Flutter Desktop and Electron applications on macOS.

Before reading on, keep in mind that this article isn’t intended to promote one solution over the other. The resources on modern computers and devices are enough that you would be hard-pressed to spot the difference between a well-made Flutter Desktop app compared to a well-made Electron app.

Performance is just one metric. Your team's developer productivity, experience, and knowledge are also important factors to consider when deciding which solution to use.

Disclaimer: The author of this article is a Flutter developer at getstream.io. JavaScript engineers reviewed this article for quality assurance. While all efforts were made to create an unbiased and fair comparison between these two technologies, there may be unintentional mistakes or incorrect measurements.

All of the code used for these tests are linked in the article; feel free to find mistakes or inconsistencies and help improve the quality of the results.

The Current State of Electron and Flutter Desktop

Electron

Electron has stood the test of time and proven itself to be a good solution to bring web applications to desktops. In fact, some of my favorite (and most used) desktop apps run on Electron, like Visual Studio Code, Figma, and Slack. Other popular apps include Discord, Skype, and Tusk.

Microsoft is moving Teams from Electron to Edge WebView2.

Rish Tandon's Microsoft Teams Tweet

Source: https://twitter.com/rishmsft/status/1408084132786151427?ref_src=twsrc%5Etfw

These are big companies and big apps. Lots of resources and engineering power have been devoted to improving Electron, NodeJS, V8 Engine, JavaScript, HTML, and the whole web. Take a look at the work Microsoft has done in optimizing VSCode to make it feel like a native application.

But for every good Electron app, there’s a bad one out there (no name dropping 👀). Electron applications are famous for their large executable size, heavy memory/resource usage, and, in some instances, long startup times and jank issues.

Flutter

Flutter has the ambitious goal to target a variety of devices from a single codebase. Flutter has proven itself to be a good solution for mobile (Android and iOS) development, with decent performance that has steadily improved since its v1 release. There have been some performance issues over the years, but most of those have been addressed.

Flutter Desktop, however, is still in beta at the time of writing. There are no big-name applications made with Flutter Desktop that we can experiment with and directly compare to Electron. Time will tell how good of a solution it is in a production scenario, but from my personal experience, the initial performance results seem promising. A Flutter application that runs well on mobile should, in theory, run just as well, if not better, on desktop.

A Quick Word on JavaScript V8 Engine and Dart Native

Let's discuss the languages and technologies driving Electron and Flutter.

JavaScript V8 Engine

Electron combines Chromium's rendering library with Node.js. The two share the same JavaScript engine, V8. To quote the V8 docs directly:

V8 is Google’s open-source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others.

V8 compiles and executes JavaScript source code, handles memory allocation for objects, and garbage collects objects it no longer needs. V8’s stop-the-world, generational, accurate garbage collector is one of the keys to V8’s performance.

The V8 engine is constantly improving, and while JavaScript is generally considered an interpreted language, modern JavaScript engines no longer just interpret JavaScript, they compile it. V8 internally compiles JavaScript with just-in-time compilation (JIT) to speed up the execution.

It's also possible to interface with native code using native modules (see this article for an example).

Dart Native

Per the Dart Native docs:

During development, a fast developer cycle is critical for iteration. The Dart VM offers a just-in-time compiler (JIT) with incremental recompilation (enabling hot reload), live metrics collections (powering DevTools), and rich debugging support.

When apps are ready to be deployed to production — whether you’re publishing to an app store or deploying to a production backend — the Dart AOT compiler enables ahead-of-time compilation to native ARM or x64 machine code. Your AOT-compiled app launches with consistent, short startup time.

The AOT-compiled code runs inside an efficient Dart runtime that enforces the sound Dart type system and manages memory using fast object allocation and a generational garbage collector.

The key takeaway is that Dart can give you a fast developer experience in the form of hot reload, and the benefit of a small compiled application to the target architecture when you create a release build.

Here are some other noteworthy Dart features and updates:

  • Dart recently added support for sound null safety, which enables the Dart AOT compiler to produce faster machine code (check out Dart and the performance benefit of sound types for more information).
  • Dart’s foreign function interface (FFI) lets you use existing C libraries for better portability with the option of using highly tuned C code for performance-critical tasks. Read about Dart 2.13 for more information about Dart FFI.

Dart is growing up to be a great language in isolation, but its popularity can definitely be attributed to the rise of Flutter. Google and the Flutter team maintain Dart, meaning the team can optimize the framework (Flutter) and the language (Dart) to help improve the developer experience.

Performance Considerations

The following sections compare performance considerations between Flutter and Electron. These are areas that could have an impact on the performance of the final application, or your developer experience and the amount of effort needed to avoid potential performance problems.

Both the Flutter and Electron docs cover performance for each platform, which you should view for up-to-date information.

Application Size, Dependencies, and Modules

One important factor to consider is the executable size of your compiled application. Here Flutter is the clear winner. Flutter compiles to machine code, which means that Dart can be smart about what to include and what to strip from a final build. See this list of closed PRs related to AOT build sizes for Dart. This is an area of continuous improvement and measurement.

Electron applications, on the other hand, run on Chromium. Basically, every Electron application is bundled with its own browser. That’s the magic that makes Electron possible, but also why you get an executable around 150mb (or more) with a simple “Hello, World!” app. A similar Flutter application would be around 30mb. (Later on, in our performance metrics, we'll take a look at some real numbers to evaluate bundle size.)

Ignoring the sheer size of an Electron application, there are also other important factors to consider:

  • With Electron, you need to be more mindful of what modules you include, as unused code will still be included in your final package (see the section on carelessly including modules).
  • Loading modules in Electron is a surprisingly expensive operation. You as the developer need to take care to defer the loading of big modules to only when they're needed (see the section titled loading and running code too soon).
  • Finally, it’s recommended that you bundle all of your application's code into a single bundle, as running require is an expensive operation (there are various JavaScript bundlers that can assist you with this, like Webpack, Parcel, and rollup.js to name a few).

If you're a hardcore JavaScript dev, you may roll your eyes and say, "easy", but these little headaches add up. Especially if you're a new developer.

Dart, on the other hand, does a good job of stripping away unneeded code and dependencies. If you start a new Flutter project, then the build settings to run on macOS (along with all the other platforms) will already be set up and waiting.

Flutter requires a lot less configuration and you’ll immediately benefit from all the AOT compilation benefits that Dart provides when you build your application to the target platform.

Isolates, Web Workers, and Processes

Both Flutter and Electron apps can offload expensive work from the Main/UI thread, which is necessary when you want to perform operations that may potentially be UI blocking:

  • In Dart, each thread is in its own isolate with its own memory, and the only way different isolates can communicate is by passing values through ports.
  • In Electron, you can use worker threads that enable the use of threads to execute JavaScript in parallel.

See Electron’s Blocking the main process and Flutter’s Isolates and event loops for more information about these processes.

While both frameworks can execute expensive work in a non-blocking way, it’s important to note that Electron spins up a couple of extra processes by default because it’s running Chromium.

Take a look at this screenshot from macOS Activity Monitor:

macOS Activity Monitor

Activity Monitor showcasing an Electron "Hello, World" application on macOS.

You’ll notice three extra helper processes (Helper, GPU, Renderer), each of which takes up some memory and processing power:

  • The main process creates and manages BrowserWindow instances and various application events.
  • The render process runs the user interface of your app (a web page), which is an instance of webContents.

Check out Cameron Notes’s excellent article, Deep dive into Electron’s main and renderer processes. This is a great place to start if you want more information.

Flutter, on the other hand, only runs a single process, and you have the option to spawn extra isolates to offload work from the main thread.

Flutter Hello World app activity monitor

Activity Monitor showcasing an Electron "Hello, World" application on macOS.

For more information on Flutter isolates, check out the following articles:

Rendering

These topics are out of the scope of this article. But whether you use Flutter or the newest JavaScript SDK, it's important to understand how your code is transformed into pixels on the screen.

If you care to learn more, here are some good resources:

Both Flutter and Chromium use Skia, a 2D graphics library, to handle rendering.

WebAssembly (Wasm)

A new binary format for machine code that was specifically designed with browsers in mind. Apps compiled to WebAssembly can run alongside JavaScript without compromising performance. Both Flutter and Electron apps can (and do) benefit from this technology.

From the MDN Web Docs:

WebAssembly is a new type of code that can be run in modern web browsers — it is a low-level assembly-like language with a compact binary format that runs with near-native performance and provides languages such as C/C++, C# and Rust with a compilation target so that they can run on the web. It is also designed to run alongside JavaScript, allowing both to work together.
... using the WebAssembly JavaScript APIs, you can load WebAssembly modules into a JavaScript app and share functionality between the two. This allows you to take advantage of WebAssembly's performance and power and JavaScript's expressiveness and flexibility in the same apps, even if you don't know how to write WebAssembly code.

This is incredibly exciting! See the following articles to learn more about Wasm:

Flutter and Chrome DevTools

Improving your application's performance is not a simple task, and sometimes finding performance bugs can be like finding a needle in a haystack. For this, you need the right tools to assist you. Luckily, both Flutter and Electron have access to fantastic DevTools.

For this category, Electron has the upper hand. Chrome DevTools is an advanced piece of software that has been in development for many years. The performance profiling, debugging, network monitoring, layout inspection, and everything else are all phenomenal.

Dart DevTools, while amazing, is still young. I personally love using it and it provides you with everything you need to debug a Flutter application, but it definitely still has room for improvement.

Performance Tests

Now that we've discussed important performance factors for each platform, let's run some applications and see how Flutter Desktop and Electron compare.

These tests were only performed on macOS Big Sur v11.5.2, on a MacBook Pro (16-inch, 2019), with the following specs:

  • 2.3 GHz 8-Core Intel Core i9
  • AMD Radeon Pro 5500M 4 GB
  • Intel UHD Graphics 630 1536 MB

Source code and installation instructions are available in the Github repository. You’re encouraged to run these tests for yourself and do your own experiments.
Please note that these are just demos.

You can optimize these examples in many different ways for both Flutter and Electron. This is meant to be an out-of-the-box performance comparison; what you get with minimal developer effort.

Additionally, running some of these performance tests resulted in profiling overhead. Real-world results may look different when not profiling the application.

You should also expect some variance between different runs. Ideally, you should run these tests multiple times and take the average (this was not done for this article; only the first run was selected).

⚠️ In these examples, Electron Forge was used to configure and run the Electron projects. NPM version 7.23.0 was used.
⚠️ Flutter version 2.5.1 was used on the stable channel.
⚠️ Note that Chromium limits the number of Frames Per Second (fps). See Chrome’s Unlimited frame rate article for more information.

“Hello World” App!

A simple application that displays "Hello, World!"

App Startup Time

Both Flutter and Electron open and paint to the screen in under one second, with Flutter being slightly faster.

"Hello, World!" app, Flutter vs Electron startup time

Executable Size

Here we can see that even for a simple application, an Electron app takes up a lot of space!

Flutter

  • Deployment Target 10.11 and above: 37.3mb
  • Deployment Target 11.0 and above: 22.7mb
    Electron
  • 183.9mb

Activity Monitor

Flutter uses the least amount of memory (~38mb compared to ~100mb). All those extra Electron processes add up.

Memory

Flutter hello world app, memory footprint

"Hello, World" Flutter app. Memory ~37.9mb

Electron hello world app, memory footprint

"Hello, World" Electron app. Memory: ~99.6mb (44.9 + 26.1 + 18 + 10.6)
CPU, GPU, and Energy impact are not shown in this example.

Profiling / Frames Per Second

A silly example, but included for fun. Here the application window is resized to trigger a re-render.

As expected, both Flutter and Electron run comfortably at 60fps . Please note, though, that Chromium limits the number of fps. That's why the time per frame is always around 16ms for Electron.

Flutter
Under 3ms per frame, 60fps.

Flutter, 60fps, with the average frame under 3ms

Electron
Around 16-17ms per frame, 60 fps.

Electron, 60fps, with the average frame time set to 16-17ms

Lottie Animations App

This example loads 150 Lottie animations and animates them all at the same time.

App Startup Time

Here, the Electron app opens half a second earlier than the Flutter app; however, the first content-full paint is at the same time. It should be noted that the Electron app has very noticeable frame drops for the first few seconds.

"Lottie Animations" app, Flutter vs Electron startup time

Executable Size

Here, loading extra modules could result in a significant increase in bundle size when using Electron (~80mb bigger compared to the "Hello, World" sample).

The Flutter app only increases by ~7mb after adding the Lottie package and animation files.

Flutter

  • Deployment Target 10.11 and above: 40.7mb
  • Deployment Target 11.0 and above: 29.1mb

Electron

  • 259.1mb

Activity Monitor

Flutter uses fewer resources in all the categories: CPU, GPU, Memory, and Energy.

The stand-out result, however, is the massive amount of memory used in the Electron version. Roughly ~2.2gb compared to Flutter at ~170mb.

CPU/GPU
Flutter has a lower CPU and GPU usage: ~130% (CPU) and ~13% (GPU), compared to Electron’s ~215% (CPU) and 41% (GPU).

Flutter Lottie animations app

"Lottie" Flutter app. CPU: ~130.6% and GPU: ~12.8%

Electron Lottie animations app

"Lottie" Electron app. CPU: ~215% (0.2 + 101.6 + 113.4) and GPU: ~41%

Memory

Flutter has a much lower memory usage at ~170mb compared to Electron at ~2.2gb.

Flutter lottie demo, memory footprint

"Lottie" Flutter app. Memory: ~168.5 MB

Electron lottie demo, memory footprint

"Lottie" Electron app. Memory: ~2261.3mb (2100 + 95.6 + 55.3 + 10.4)

Energy Impact

Flutter uses less energy, with an impact of ~130 compared to Electron at ~220.

Flutter lottie app, energy impact

"Lottie" Flutter app. Energy: ~131.6

Electron lottie app, energy impact

"Lottie" Electron app. Energy: ~220.7 (0.2 + 137.8 + 82.7)

Profiling / Frames Per Second

In this example, there’s a noticeable runtime difference between Flutter and Electron. On startup, Flutter runs immediately, and maintains a steady 40-45fps, while Electron shows significant frame drops for the first few seconds and then maintains 20-25fps.

Flutter
Ranging from 10–30ms per frame, with an average of approximately 40–45fps.

"Lottie Animations" app. Flutter Desktop profiling.

Electron
Ranging from 25–60ms per frame, with an average of approximately 20fps–25fps.

"Lottie Animations" app. Electron profiling.

High-Resolution Images App

This example loads 100 high-resolution images over the network, caches them, scales them down to a small size, and continuously rotates the images.

Note that on Flutter, the cached_network_image package is used to cache the images between runs.

App Startup Time

Both Flutter and Electron open in under one second, with the Flutter app opening and loading the images slightly faster. There are noticeable frame drops in the Electron app for the first few seconds.

"Rotating Images" app, Flutter Desktop vs Electron startup time.

Executable Size

The Electron app is the exact same size as the "Hello, world" example. The Flutter app is 5mb bigger, after including the image caching package.

Flutter

  • Deployment Target 11.0 and above: 27.7mb

Electron

  • 183.9mb

Activity Monitor

The results are a lot closer in this example, except for memory usage, where Flutter uses approximately 100mb less.

CPU/GPU

Not a big difference between Flutter and Electron. Flutter using slightly fewer resources.

Flutter Activity Monitor, images

"Images" app Flutter. CPU: ~13.6% and GPU: ~2.97%

Electron "images" app

"Images" app Electron. CPU: ~17.5 % (1.1 + 7.7 + 8.7) and GPU ~2.8%

Memory

Flutter using approximately 100mb less than Electron.

Flutter images app, memory footprint

"Images" app Flutter. Memory : ~73.5mb.

Electron images app, memory footprint

"Images" app Electron. Memory: ~173.5mb (74.2 + 54.4 + 29.1 + 15.8)

Energy Impact

Not a big difference between Flutter and Electron, between 12–17% energy impact.

Flutter images app, energy impact

"Images" app Flutter. Energy Impact: ~12.5

Electron images app, energy impact

"Images" app Electron. Energy Impact: ~17 (1.1 + 8.4 + 7.5)

Profiling / Frames Per Second

There are noticeable frame drops in the Electron version on App start. But after the first few seconds, both run at 60fps without issue.

Flutter

Aside from the occasional frame drop, it renders close to 1ms per frame. An easy 60fps.

"Images" app Flutter. 1ms per frame, 60fps.

Electron
Forced 16ms per frame, 60fps.

"Images" app Electron. Frames set to 16ms, 60fps. Noticeable frame drops on app start, however.

Conclusion

You have to make your own conclusion from the data presented 🙂. Both are excellent cross-platform choices if you want to bring your application to desktop.

A summary of the findings is provided below:

Electron

  • As an Electron app grows, it may struggle with startup time and time to first contentful paint. However, apps like VSCode and Figma prove that this can be avoided.
  • Electron is proven to be a working solution; if you can write a fast and performant web application, then you can do it on Electron as well.
  • Has a large installation size and memory usage.
  • You need to be more mindful of the modules you include, when they’re loaded, and how your application is packaged.
  • Wasm integration can drastically improve certain tasks.

Flutter

  • Flutter Desktop is still in Beta and unproven on a larger scale.
  • Flutter will have a smaller memory footprint and install size. These numbers are also likely to improve, especially as Dart continues to be optimized.
  • Targeting multiple platforms is easy with minimal developer configuration needed.
  • Flutter's platform integration and FFI are fantastic solutions.
  • Dart's null safety and its potential integration with Wasm are both icing on the Flutter cake.

For the performance experiments, it would’ve been fantastic to demo two big, and similar, Flutter and Electron applications side-by-side and to be able to compare more real-life scenarios.

This will be an interesting conversation once production Flutter desktop apps start surfacing.