Five Algorithms and Data Structures Hidden Inside Jetpack Compose

skydovesJaewoong Eum (skydoves)||12 min read

Five Algorithms and Data Structures Hidden Inside Jetpack Compose

Jetpack Compose is a UI toolkit on the surface, but its internals draw from decades of computer science research. The runtime uses a data structure borrowed from text editors to store composition state. The modifier system applies an algorithm from version control to diff node chains. The state management layer implements a concurrency model from database engines. These are not theoretical exercises. They are practical solutions to problems that Compose solves every frame.

In this article, you'll explore five algorithms and data structures embedded in Compose's internals: the gap buffer that powers the slot table, Myers' diff algorithm applied to modifier chains, snapshot isolation borrowed from database MVCC, bit packing used to compress flags and parameters, and positional memoization that makes remember work without explicit keys.

Gap Buffer: The text editor trick inside the slot table

When you type in a text editor, characters are inserted at the cursor position. The naive approach, shifting every subsequent character one position to the right, costs O(n) per keystroke. Text editors solved this decades ago with the gap buffer: an array that maintains a block of unused space (the "gap") at the cursor position. Inserting a character fills one slot of the gap at O(1) cost. Moving the cursor shifts the gap to a new location, but sequential edits at nearby positions are fast because the gap is already there.

Compose's slot table faces the same problem. During composition, the runtime inserts groups and their associated data slots as composable functions execute. The SlotWriter maintains two parallel gap buffers: one for groups (structural metadata) and one for slots (stored values like remembered state). Each buffer tracks a gapStart position and a gapLen count.

When the writer needs to insert at a position away from the current gap, it moves the gap there first. The operation shifts array elements to reposition the empty space (simplified):

private fun moveGroupGapTo(index: Int) {
    if (groupGapStart == index) return

    if (index < groupGapStart) {
        groups.copyInto(
            destination = groups,
            destinationOffset = index + groupGapLen,
            startIndex = index,
            endIndex = groupGapStart,
        )
    } else {
        groups.copyInto(
            destination = groups,
            destinationOffset = groupGapStart,
            startIndex = groupGapStart + groupGapLen,
            endIndex = index + groupGapLen,
        )
    }
    this.groupGapStart = index
}

When index < gapStart, the gap moves backward: elements between the target position and the old gap are shifted right to make room. When index > gapStart, the gap moves forward: elements after the old gap are shifted left. Once the gap is in position, insertions fill the empty space at O(1) per operation.

This is why initial composition is fast. The runtime inserts groups sequentially from start to end, so the gap is always at the insertion point and never needs to move. During recomposition, the writer moves the gap to the modified section once, then performs all local updates at O(1) each.

Myers' Diff: The shortest edit script for modifier chains

When you run git diff, the algorithm behind it is Eugene Myers' 1986 diff algorithm. Given two sequences, it finds the minimum number of insertions and deletions needed to transform one into the other. Compose uses this same algorithm to diff Modifier.Node chains when modifiers change during recomposition, determining which nodes to insert, remove, or reuse.

This article continues for subscribers

Subscribe to Dove Letter for full access to 40+ deep-dive articles about Android and Kotlin development.

Become a Sponsor