Building High-Performance Electron Apps

Electron is arguably the most ubiquitous platform for building desktop apps. It’s also frequently criticized—for poor performance, high memory and CPU usage, bloated file sizes, and an often clunky relationship with native UI. These complaints have given Electron a negative reputation among users, often leading to pushback whenever an app is announced as being built with Electron.
Having spent the majority of my career working with Electron, I’ve come to love it as a platform. It enables developers to do so much—but I’ve also encountered many of its pitfalls firsthand.
The key to performance is deliberate engineering focus—and that starts with understanding. In this post, I’ll walk through the lessons I’ve learned about building high-performance desktop apps.
Common Challenges
Working with Electron means inheriting the full weight of Chromium—and with it, a set of well-known performance challenges.
- Heavy resource usage: Electron essentially bundles an entire browser at runtime, which can demand large amounts of RAM and CPU. Users will notice this almost immediately—especially on laptops where battery drain is obvious. For example, early versions of Notion and Slack faced sluggishness in large workspaces and documents.
- Slow startup times: Loading a JavaScript app and initializing a browser can make apps slow to launch. App startup times can be infamously slow. I’ve heard countless sighs as users launch an Electron app and wait for it to boot up.
- Large bundle sizes: Distributables often weigh in at hundreds of megabytes—much larger than their native counterparts. These packages can include many modules, unnecessary polyfills, or even duplicate versions of libraries. Ask me how I know.
- Memory leaks and bloat: Poorly managed state or DOM in a long-running app can lead to significant memory leaks. Since JavaScript has managed memory, these issues might not show up in short-lived web apps or during testing, but they accumulate in desktop apps that run for days or weeks.
- Native integration quirks: Electron provides cross-platform APIs, but matching native UI isn’t trivial. Details like Apple’s inertia scrolling or macOS menu bar behavior can impact perceived quality. Performance-related quirks—such as GPU acceleration or App Nap on macOS—can also introduce tricky bugs.
Why do these issues exist? Like many JavaScript tools, Electron is unopinionated—you can do just about anything. That freedom means performance is entirely developer-driven. Chromium, and browsers in general, are built for rendering web pages—not for handling gigabytes of data or running apps 24/7. So, web practices that might be fine in a browser can make a desktop app feel bloated and sluggish.
Performance Optimizations
With Electron, there’s a mindset shift. Performance isn’t a one-off fix—it’s a mindset. Electron is unopinionated and doesn’t hand us performance; we have to earn it. The maintainers say it themselves: performance is “largely your responsibility.” That means deliberate engineering focus, intentional design, and knowing when to be lazy.
Here’s how I approach it.
Optimize startup
Like in life, first impressions matter. The first thing your user will notice is how long your app takes to launch. So be lazy—aggressively lazy—especially at startup.
- Only load what’s necessary. Split out your unused code into smaller chunks and load it on demand.
- Defer non-critical modules. Don’t block the UI with things the user won’t need right away.
- Resist the temptation to load data up front—it’ll get especially worse as the dataset grows.
Instead, focus on getting something on the screen fast. Then pull in the rest quietly in the background.
Slack’s engineering team had a great strategy here: they don’t load everything. They preload channels with recent activity or use keyboard cues (⌘ + up/down) to guide what data to fetch.
Tip: target modern Chromium. You control the browser—so drop legacy polyfills, enable tree shaking, and check your bundler setup. Linear, for example, ditched Parcel for Rollup + code splitting and shaved up to 30% off their load time.
Profile and Pre-warm Your Startup
Don’t just eyeball the startup—measure. Profile your startup. Religiously measure what’s going on in the first few seconds.
VS Code is the poster child here. They obsess over their “first second.” Ever notice how the code shows up as plain text almost immediately—even before syntax highlighting loads?
Common optimizations:
- Remove blocking network calls
- Show a shell early, then fill in content later
- My favorite one: store user data in a local cache (like IndexedDB) and update it in the background. For repeat users, it’ll be instant.
The “pre-warmed” startup is my favorite approach. It makes your app feel instant and gives you more time to do the work you need. And since most users are repeat users, the majority will feel nearly instant responsiveness.
Stay Smooth: threads, memory, and native languages
Electron is a multi-process platform. Take advantage of this.
- Don’t block the main thread. Heavy work—like file I/O, parsing, or data processing—should be sent to web workers or Node.js child processes.
- Keep the UI responsive. Any amount of freeze makes the experience awful. Offload anything that might cause lag. VS Code again is the prime example. They offload nearly every major action to a separate thread to keep the UI responsive.
Memory matters too. Unlike web apps that live for minutes, desktop apps often live for days. So focus on memory cleanup.
- Release DOM elements when not needed.
- Unsubscribe from unused data.
- Check long-lived listeners.
- Clean up especially in React. Use conditional rendering instead of hiding with CSS, and remember cleanup functions in
useEffects
. - Use Chrome’s memory profiler to catch leaks.
- Add telemetry to monitor memory usage in production.
For compute-heavy tasks, take it a step further by moving outside of Javascript entirely—into native code. Rust is my go-to for this. I use it for real-time Markdown parsing. 1Password uses it for encryption, database, and sync logic. Figma’s document engine is written in C++ and compiled to WebAssembly.
This is easier than it sounds. Electron and Node allow you to bundle native modules via NAPI-RS
, or WebAssembly via wasm-bindgen
. This gives you near-native performance with far less garbage collector overhead.
“The entire point of Electron is that you can pair your web app with any native code you want to write—specifically with C++, Objective-C, or Rust.” - Felix Rieseberg
Channel Discipline
IPC (inter-process communication) is powerful—and easy to misuse.
A pattern I’ve seen (and strongly recommend against) is using IPC between components in the same renderer process. IPC is much slower than a direct function call, and this approach is usually a legacy of older decisions—things like module federation.
A few rules to remember:
- Don’t use IPC unless you need to. Consider workers or other alternatives first.
- Prefer shared state, context, or stores for intra-render communication.
- Avoid synchronous IPC—it can easily block the UI.
- Clean up and organize channels. Poor lifecycle hygiene causes weird side effects.
IPC is core to Electron, but like anything powerful, it can become a real headache if misused.
Perceived Performance
Your app doesn’t just need to be fast—it needs to feel fast.
Give users immediate feedback, even if the work isn’t done. Show a skeleton screen. Preload what they’re likely to click next. Reduce typing latency. Prioritize interactions.
Slack (again) does this well—they preload messages for recently active channels so that switching feels instant. Keyboard shortcuts are used as hints for where to fetch next.
Skeleton loaders are standard now, but you can go further with pre-warmed startup: cache content and show something right away. The real work happens backstage. Think predictively—what can you do to make the app feel fast?
Update and Update
Keep Electron up to date. It sounds obvious, but it’s easy to forget. Every new version brings:
- Chromium and Node.js performance improvements
- Fixes for long-standing bugs
- New APIs that might make your code faster or simpler
Leverage the work the maintainers are doing. Don’t overlook the free wins. Sometimes the best optimization is just doing the maintenance.
End
Building a performant Electron app is not about escaping Electron’s limitations, but engineering with them. I’ve spent more than my fair share of time working around weird performance issues. Hopefully, what I’ve learned can help lead to better products everywhere.
I’ll end it here with one lesson:
The lesson is: Electron is just a tool. It can power clunky, memory-hungry apps—or some of the best, smoothest experiences out there. The difference is a focus on performance and user experience.
Remember performance is a feature.