Mithril’s Tiny Virtual‑DOM: Keeping Large Lists Fast
Learn how Mithril’s lightweight virtual‑DOM diffing keeps dynamic lists snappy, with a practical example, trade‑offs, and actionable tips for real‑world apps.
29 Dec 2025, 00:12 UTC

Problem: The Classic DOM Reflow Bottleneck
When an app renders thousands of items that change frequently—think a live‑updating feed or a spreadsheet—each change can trigger a full DOM re‑render. Browsers must walk the tree, recalculate styles, and repaint, which quickly becomes a performance nightmare.
Thesis: Tiny Virtual‑DOM, Big Speed
Mithril’s virtual‑DOM is only a few kilobytes in size, but its diff algorithm is designed for speed. By comparing the old and new vnode trees and updating only the nodes that actually changed, Mithril keeps the number of DOM operations to a minimum, delivering near‑native frame rates even with large lists.
Key Feature: Fine‑Grained Diffing with Explicit Keys
The algorithm walks the two trees in parallel. For each node it checks the key property. If the keys match, Mithril reuses the existing DOM node; if they differ, it replaces or moves the node. This deterministic key lookup is the core of Mithril’s efficiency.
Because the diff is shallow—Mithril expects most component trees to be only a few levels deep—each comparison is a handful of JavaScript operations, far cheaper than the layout work browsers normally do.
Worked Example: Toggling a Property on a Random List Item
// items is an array of 10,000 objects
let items = Array.from({length: 10000}, (_, i) => ({ id: i, active: false }));
const ListItem = {
view(vnode) {
const { id, active } = vnode.attrs;
return m("li", {
key: id,
class: active ? "active" : "",
}, `Item ${id}`);
}
};
const List = {
view() {
return m("ul", items.map(item => m(ListItem, item)));
}
};
const ToggleRandom = {
view() {
return m("button", {
onclick: () => {
const idx = Math.floor(Math.random() * items.length);
items[idx].active = !items[idx].active;
m.redraw(); // schedule a diff
}
}, "Toggle Random Item");
}
};
m.mount(document.body, {
view() {
return [m(ToggleRandom), m(List)];
}
});
What happens when the button is clicked?
- Mithril receives the new vnode tree from
List. - It walks the old and new
ulchildren, comparing thekeyof eachli. - Only the
liwhoseidmatches the toggled item receives a DOM update; all otherlinodes are reused unchanged.
To confirm this, open Chrome DevTools, record a performance trace before and after the click, and inspect the Elements panel. You should see only one li node’s class attribute change, with no new nodes added.
Trade‑Offs and Limitations
- Stable Keys Are Mandatory – If you omit the
keyor use a non‑unique key, Mithril will treat each node as new, leading to unnecessary DOM replacements and potential state leakage. - Shallow Trees Only – Mithril’s diff is tuned for shallow component trees. Deeply nested structures can still incur noticeable overhead because each level adds a comparison step.
- No Built‑In Router or Form Handling – For complex apps you’ll need external libraries for routing or advanced form features.
Actionable Takeaway
To harness Mithril’s performance for dynamic lists:
- Keep components stateless – Let data flow in via attributes and avoid internal mutable state unless absolutely necessary.
- Assign explicit, stable keys – Use a unique identifier from your data (e.g.,
item.id) as thekeyon each vnode. - Don’t mutate the vnode data directly – Treat the data passed to
vnode.attrsas immutable; create new objects when updating. - Profile with DevTools – Measure frame rates and inspect the Elements panel to verify that only the intended nodes are updated.
By following these guidelines, you’ll keep your Mithril app snappy, even when rendering tens of thousands of items that change on the fly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.