The wrong question
"Is Flutter fast enough?" I hear this all the time. Most of the time it's the wrong question.
At FlutterCon 2026 I sat through two talks that look unrelated at first. One was about bringing Rust into Flutter via FFI to process images extremely fast. The other was about why an innocent-looking screen drains your GPU and battery.
Put together, they give me one clear line: measure first, then decide. Sometimes the answer is Rust. More often it's that you have a ticker running somewhere that never goes to sleep.
Talk 1: when Dart really is too slow
The speaker had an automated pipeline that generates images. Out came Full HD or 2K images, and they had to be upscaled to 4K. With the Dart image package, that single step took around 10 seconds on a MacBook M4. At thousands of images a day, that hurts.
He had two options. Improve the package himself, or hook in native code. He went with Rust and built the package Pixer.
The numbers from the talk:
- Upscaling Full HD to 4K: about 6 seconds with
image, about 70 milliseconds with Pixer. Roughly 100x. - Upscaling plus JPEG encoding: around 1000x, according to the speaker.
Where does that come from? Rust compiles via LLVM into very efficient machine code. On top of that: SIMD instructions, multithreading for resizing, and well-optimized algorithms from the Rust ecosystem. He also said the image package isn't written in a very compiler-friendly way in places.
Why Rust and not C++ or Swift/Kotlin?
His reasoning made sense to me:
- Rust has a real package manager in Cargo and gives you memory safety. C and C++ have neither.
- Native Swift and Kotlin versions would mean writing the library multiple times. With Rust there's one codebase for every platform.
- He deliberately skipped Flutter Rust Bridge to keep control. His advice for beginners: learn the manual route first (
cbindgengenerates C headers from Rust,ffigengenerates Dart bindings from those), then move to the bridge if you like.
Where FFI bites
Three things stood out.
Few crossings. Every call across the Dart/Rust boundary costs something. Pixer uses a builder pattern: you chain operations like resize or blur, but nothing happens yet. Only when you save does everything go to Rust in one single call. The C surface has only about ten functions.
Memory. The image lives on the Rust heap, Dart only holds a small pointer. The garbage collector sees just that tiny object and never frees the 12 to 100 MB of image data. The fix: Finalizable plus a NativeFinalizer that calls free on the Rust side. Add the externalSize parameter so the GC knows the real footprint and reacts to it.
Distribution. Building and bundling native libraries per platform is painful. Pixer uses build hooks and downloads prebuilt binaries from GitHub Releases, with a hash check. Nobody needs a Rust toolchain. The binaries are about 2 MB on ARM and 5 MB on x86. If you only need PNG, you can compile just those codecs through Cargo features.
When FFI makes sense, according to the talk
When Dart has a known bottleneck and an optimized native solution exists. Typical cases: crypto, image, audio, video, large data on the device. Not great: very frequent but trivial calls, where crossing the boundary costs more than it saves.
And the most important sentence: don't use Rust for Rust's sake. Benchmark and profile your Dart code first, and go native only for a confirmed problem.
Talk 2: when Dart isn't slow, the GPU is sweating
The second talk flipped the question. The example was a ClickUp-style screen: a dialog with a blurred background and a CupertinoTextField. Looks simple. Isn't.
What blur really costs
Blur needs information from neighboring pixels. But the GPU renders in tiles. So it has to finish the background, write it to memory, read it back, blur it, and then put the UI on top. A frame on a current iPhone is around 24 MB according to the talk, and going out and back doubles the memory traffic. One render pass turns into several.
Computed once, that's no drama. In Xcode's energy profile the screen even looked relaxed while idle, because no new frames were being requested.
The real culprit: a ticker
As soon as the text field got focus, GPU load spiked. The reason is the cursor, which fades in and out. It runs on an AnimationController, and an active ticker requests frames at the full display rate, up to 120 per second on some devices. The UI might change eight times a second, but the blur gets computed 120 times.
An earlier Flutter PR cut the cursor updates to 8 Hz. That helps CPU and rebuilds, but the ticker still requests full-rate frames. The result: heat, battery drain, throttling, and in the worst case dropped frames.
The line I took away: a demo running at 120 FPS isn't automatically sustainable. Fast and cheap are two different things.
What you can do about it
- Control your tickers. Not every animation needs full frame rate. The FixTicker package offers a ticker-like API that schedules frames through timers.
- Turn off the cursor fade. On
CupertinoTextFieldyou can switch the cursor to blinking instead of fading, if you can live with the look. - Stop tickers behind sheets. Flutter mutes tickers behind fully opaque routes, but not behind non-opaque ones like sheets. That's on you.
- Use blur carefully. One backdrop blur can be reused for several elements. If you animate blur, it has to end at exactly 0.0. A tiny leftover value still costs an extra pass. And cost doesn't rise linearly with sigma but in steps, because higher values allow more downsampling.
- No Opacity over complex trees. It forces offscreen rendering. Paint with a transparent color or pass opacity straight to images instead.
- Swap out ShaderMask. For fades at list edges, a gradient overlay often does the job when the background is known.
- Keep layers tight. Smaller areas mean smaller textures.
Profiling
The tools: DevTools for CPU and UI, RepaintBoundary to trace paints, and on iOS Metal System Trace and Metal Frame Capture in Xcode. There you see the individual passes: background, downsample, vertical blur, horizontal blur, compositing. The workflow in the talk: profile build, enable GPU frame capture in the scheme, capture a frame, look at the dependencies.
The speakers also showed an experimental package that estimates frame cost in widget tests, plus three skills for AI agents: what makes a frame expensive, what keeps drawing, and how to profile. An open Flutter PR would let tickers request frames only when something actually changes. According to the talk it has been merged twice and reverted twice.
My case: the seat-plan algorithm in DragonBoat Hub
For me Rust isn't a theory topic. I use it in DragonBoat Hub, specifically for the algorithm that calculates the seating in a dragon boat. It assigns paddlers to seats based on weight, experience and balance, and it runs many attempts until the best lineup is found.
The history of that algorithm is an honest example of "measure first, rebuild second":
- JavaScript: The original is DragonTrim by Christian, written in JS.
- Dart port: In spring I ported it to Dart so it could run directly inside the Serverpod server.
- JS as a sidecar: At the end of April I threw the Dart port out again and ran the JS original as a sidecar process.
- Rust sidecar: Today a Rust port runs as the sidecar. It is checked against the JS original with a differential test and returns byte-identical answers for the same request.
I re-measured this on 17 October 2026 on an Apple M4 Pro. The input was a realistic 20-seat boat with 20 paddlers, propulsion balancing on and 100 attempts, like in production:
| Implementation | Median |
|---|---|
| Dart port (deep variant) | 1031.6 ms |
| Dart port (base variant) | 278.8 ms |
| JS original, Node | 103.7 ms |
| JS, Bun | 61.9 ms |
| Rust, single thread | 7.7 ms |
| Rust, all cores | 1.1 ms |
Over local HTTP it's 1.7 ms for the Rust sidecar against 97.8 ms for the Node sidecar.
Two notes so the picture doesn't get skewed. First: I used to say the Dart port took "several seconds". Measured on my laptop it's about one second. On a small server it may take longer, but I haven't measured that. Second: one second isn't dramatic on its own. But 1 millisecond instead of 100 or 1000 changes what you can do with it, and the load per request on the server shrinks accordingly.
That fits what the Pixer talk showed: Rust pays off for real compute work with a clear bottleneck. It also fits my order below. I didn't jump straight to Rust. I went through Dart and JS first and measured each step. The real point for me: the differential test against the JS original is the safety net, not my trust in the new code. As long as both return the same answers, I can swap the implementation.
Rust doesn't only run on the server in my setup, though. The scoring of how good a boat's lineup is lives in its own Rust crate, and I link exactly that crate into the Flutter app via flutter_rust_bridge. The reason isn't speed, it's consistency. The app used to have a Dart re-implementation of the scoring, and the two sides had drifted apart: some penalties were set to zero in the app while they cost 10000 on the server. The number a coach saw in the app wasn't the number the optimizer was minimising. Today app and server calculate with the same code, and a parity test keeps both bit-identical across 1206 optimizer runs.
The search itself stays on the server. The app only gets the scoring half, because libraries for parallel search and HTTP have no business on a phone. That's why the crate is split in two, and the compiler makes sure it doesn't get mixed up again. How Serverpod fits in is covered in my comparison with Supabase and Firebase.
My take: order beats technique
I build apps with Flutter, and for most of my projects performance was never the source of trouble. That's my experience, not a quote from the talks. The order I take away from all this:
- Does it run smoothly on a real, older device? If yes, stop.
- If not: profile build, DevTools. Is it UI thread work or the raster side?
- Raster side? Blur, opacity, ShaderMask, a forgotten ticker. For normal apps, that's where most of the wins are.
- UI thread doing real heavy lifting? Isolates first, then a better package, and only then FFI.
- Rust only for a confirmed bottleneck like image, audio or video processing, where a mature native ecosystem exists.
What I find interesting is that AI agents lower the barrier for step 5. The speaker says himself that you can delegate this kind of work with simple instructions today. That matches what I wrote in AI in app development. But easier to build doesn't mean easier to maintain. Every native dependency is another build you have to watch on every platform.
One more thing: the native vs. cross-platform debate often treats performance as the deciding factor. Both talks show that with Flutter you can go native when you need to. That takes the edge off the argument. You can find a direct comparison of two frameworks in Flutter vs React Native 2026.
Conclusion
Flutter is fast enough in the vast majority of cases. When it isn't, the cause is often not the language but frames: too many of them, too many passes.
Rust via FFI is a strong tool for real compute load, up to 100x to 1000x for images in the talk. But it's the last step, not the first. Measure first, then build.
Frequently asked questions (FAQ)
Mostly yes. The talks show that bottlenecks almost always sit in specific places: heavy computation or expensive render effects. Both can be fixed in a targeted way without switching frameworks.
When you have a measured bottleneck in Dart and a mature native solution exists, for example in image, audio or video processing, or cryptography. With very many small calls, the crossing itself can cost more than you gain.
The speaker recommends beginners learn the manual route with cbindgen and ffigen first. Then you understand what the bridge does for you behind the scenes and can use it deliberately.
A common reason is an active ticker or a constant animation requesting frames at full display rate. Combined with blur or offscreen opacity, every one of those frames gets expensive. Profiling with Xcode and DevTools shows what is actually running.
Always measure in profile mode on a real device. DevTools for CPU and UI, and on iOS Metal System Trace and Frame Capture for the GPU side. Optimize only once you know the bottleneck.
This article was inspired by two talks at FlutterCon 2026: one on Rust and FFI for image processing with Pixer, and one on the GPU cost of blur and tickers in Flutter.



