derivedStateOf Internals: The Cost of Observation / Why derivedStateOf is expensive?

skydovesJaewoong Eum (skydoves)||7 min read

derivedStateOf Internals: The Cost of Observation / Why derivedStateOf is expensive?

The derivedStateOf API in Jetpack Compose provides a convenient mechanism for creating memoized state that automatically updates when its underlying dependencies change. While essential for performance optimization in many scenarios, it is often described as "expensive."

This study analyzes the internal implementation of DerivedSnapshotState to demystify this cost. We will show that the expense of derivedStateOf is not in the read operation, but in the complex machinery required to track dependencies, validate its cached value, and perform recalculations. By examining the isValid, currentRecord, and Snapshot.observe calls, this analysis will reveal the intricate dependency tracking, hashing, and transactional record-keeping that make derivedStateOf a precision tool to be used judiciously, not universally.

1. Introduction: The Promise and the Price

The public API is deceptively simple:

public fun <T> derivedStateOf(calculation: () -> T): State<T> =
    DerivedSnapshotState(calculation, null)

It promises to run a calculation lambda, cache the result, and only re-run the calculation when one of the State objects read inside it changes.

Let's see an example:

// In a @Composable function
val firstName by remember { mutableStateOf("John") }
val lastName by remember { mutableStateOf("Doe") }

// The intuitive but INCORRECT use of derivedStateOf
val fullName by remember { derivedStateOf { "$firstName $lastName" } }

The logic is sound: fullName is derived from firstName and lastName. However, this is an incorrect and inefficient use of the API.

As your reference correctly states, the right way to do this inside a Composable function is:

// The CORRECT and performant way inside a Composable
val firstName by remember { mutableStateOf("John") }
val lastName by remember { mutableStateOf("Doe") }

val fullName = "$firstName $lastName" 

So, if it's an optimization, why is it "expensive"? The cost lies in the sophisticated machinery required to fulfill this promise.

2. The Core Mechanism: Caching and Dependency Tracking

The DerivedSnapshotState does not store its value directly. It stores a record of its last calculated result and the dependencies that produced it.

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