Sitemap
Paul Thommithazhe

Paul Thommithazhe

Firmware | Embedded Engineer | | Former SDE

🔍 How Flutter Efficiently Updates UI: Inside the Widget Diffing Algorithm

3 min readJun 27, 2025

--

By Paul Thommithazhe
Software Engineer | Kotlin , Jetpack Compose , Flutter Developer

Press enter or click to view image in full size

📅 As of mid-2025, Flutter continues to amaze with its efficient rendering pipeline — at the heart of which lies the powerful widget diffing mechanism that allows Flutter to rebuild only what’s needed without unnecessary overhead.

Flutter is famous for its smooth performance and beautiful UI — but have you ever wondered how it updates only the necessary parts of the screen when you call setState()?

This magic lies in Flutter’s Widget Diffing System. In this post, we’ll dive deep into how Flutter compares widget trees and updates the UI without rebuilding everything.

🧠 Why Flutter Recreates Widgets

In Flutter, widgets are immutable. That means whenever something changes, the framework doesn’t update an existing widget — it creates a new one.

But recreating widgets doesn’t mean repainting the entire screen. Instead, Flutter uses a clever mechanism to diff the new widget tree with the old one, and update only what’s necessary.

🔁 Step-by-Step: What Happens When You Call setState()

setState(() {
counter++;
});

Behind the scenes, this happens:

  1. setState() is called → marks the Element as dirty
  2. Flutter waits until the end of the frame
  3. Then it rebuilds all dirty widgets by calling their build() methods
  4. It compares the new widget tree with the old one
  5. If the widgets match (by type and key), Flutter reuses the element
  6. The render object gets updated (if needed)
  7. If widgets are different, Flutter destroys the old and creates a new one

🔍 The Core Function: updateChild()

This is where diffing happens:

Element updateChild(Element? oldChild, Widget? newWidget, dynamic slot) {
if (newWidget == null) {
oldChild?.deactivate();
return null;
}
  if (oldChild != null) {
if (oldChild.widget.runtimeType == newWidget.runtimeType &&
oldChild.widget.key == newWidget.key) {
oldChild.update(newWidget);
return oldChild;
} else {
oldChild.deactivate();
}
}
  final newChild = inflateWidget(newWidget, slot);
return newChild;
}

✅ If same type & key:

  • Call Element.update() → reuse element and render object

❌ If different:

  • Deactivate old element → create new one from scratch

🧩 Example 1: Reused

Text('Hello') → Text('World')

✅ Same type, no key → reuse Element

🔑 Keys: Your Control Over Diffing

Text('A', key: ValueKey('a')) → Text('B', key: ValueKey('b'))

❌ Different keys → treated as completely different widgets → destroys and rebuilds

Get Paul Thommithazhe’s stories in your inbox

Join Medium for free to get updates from this writer.

Use keys in:

  • Lists
  • Forms
  • Animated widgets

⚙️ Efficiency Through Shallow Comparison

Flutter doesn’t do deep checks like React’s shouldComponentUpdate. It only checks:

  • runtimeType
  • key

This makes it fast and predictable.

🧱 Behind the Scenes: The Trees

  1. Widget Tree — immutable description of the UI
  2. Element Tree — long-lived objects managing lifecycle
  3. Render Tree — actual layout, paint, and GPU commands

Diffing happens between widget trees, but elements and render objects are reused to avoid extra work.

🚀 Final Thoughts

Flutter’s diffing mechanism is deceptively simple yet highly effective. By only checking widget types and keys, and reusing elements when possible, Flutter achieves the high performance it’s known for — without a virtual DOM or native widget tree.

Want fine-grained control? Use keys wisely. Want better performance? Understand the diffing rules.

Thanks for reading! Let me know in the comments if you’d like a breakdown of how render objects update too! 🚀

--

--

Written by Paul Thommithazhe

Firmware | Embedded Engineer | | Former SDE