---
title: "SubComposeLayout and BoxWithConstraints internals in Jetpack Compose"
description: "In this article, you'll dive deep into SubcomposeLayout, the internal mechanisms that power it, how BoxWithConstraints leverages it."
language: "en"
publishedAt: "2025-10-12T22:42:24Z"
updatedAt: "2025-10-12T22:42:24Z"
authors:
  - name: "Jaewoong Eum"
    url: "https://www.revenuecat.com/blog/author/jaewoong-eum"
category: "Engineering"
categoryUrl: "https://www.revenuecat.com/blog/engineering"
readingTime: 11
canonical: "https://www.revenuecat.com/blog/engineering/subcomposelayout-internals"
---

# SubComposeLayout and BoxWithConstraints internals in Jetpack Compose

In this article, you'll dive deep into SubcomposeLayout, the internal mechanisms that power it, how BoxWithConstraints leverages it.

## Table of contents

- [Understanding the core problem: Why SubcomposeLayout exists](#understanding-the-core-problem-why-subcomposelayout-exists)
- [The SubcomposeLayout API surface](#the-subcomposelayout-api-surface)
- [Internal mechanisms of SubcomposeLayout](#internal-mechanisms-of-subcomposelayout)
  - [The three-tier node organization](#the-three-tier-node-organization)
  - [The subcompose execution flow](#the-subcompose-execution-flow)
  - [The slot reuse optimization](#the-slot-reuse-optimization)
  - [The measure policy implementation](#the-measure-policy-implementation)
- [How BoxWithConstraints leverages SubcomposeLayout](#how-boxwithconstraints-leverages-subcomposelayout)
- [Real-world use cases](#real-world-use-cases)
- [Performance considerations](#performance-considerations)
- [Conclusion](#conclusion)

Jetpack Compose has revolutionized Android UI development with its declarative paradigm, where UI is a function of state. At its core, Compose follows a well-defined execution flow: **Composition → Measurement → Layout**. During composition, you declare what your UI should look like. During measurement, the system determines the size of each component. Finally, during layout, components are positioned on the screen.

But what if you need to break this flow? What if you need to know the measurement constraints before deciding what to compose? This is where `**SubcomposeLayout**` comes in, a versatile primitive that allows you to subcompose content during the measure pass, enabling you to use measurement information to drive composition decisions.

In this article, you’ll dive deep into `SubcomposeLayout`, exploring how it works under the hood, the internal mechanisms that power it, how `BoxWithConstraints` leverages it to provide constraint-aware composition, and exploring real-world use cases including [RevenueCat’s Android SDK](https://github.com/RevenueCat/purchases-android) and Compose Material3 library.

## **Understanding the core problem: Why SubcomposeLayout exists**

In a standard Compose layout, you compose your content first, then measure it. This works perfectly for most cases, but some scenarios require you to know the constraints before deciding what to compose:

**Scenario 1: Adaptive Layouts** You want to show a different UI structure based on available width, a wide layout for tablets and a narrow layout for phones. You can’t make this decision until you know the constraints.

**Scenario 2: Lazy Lists** LazyColumn needs to compose only the visible items. It can’t know which items are visible until it measures the available space.

**Scenario 3: Constraint-Based Composition** You need to compose different children based on the exact constraints passed by the parent, or you need to measure one child and use its size to determine what other children to compose.

The traditional Compose flow can’t solve these problems because composition happens before measurement. `SubcomposeLayout` inverts this relationship, allowing you to compose during measurement.

## **The SubcomposeLayout API surface**

If you look into the API of `SubcomposeLayout`, you’ll see it’s designed as a direct analogue to the standard Layout composable, but with a important difference in its measure policy:

```kotlin
@Composable
fun SubcomposeLayout(
    modifier: Modifier = Modifier,
    measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult,
)
```

The measure policy receives a `SubcomposeMeasureScope`, which extends `MeasureScope` with one important additional capability:

```kotlin
interface SubcomposeMeasureScope : MeasureScope {
    fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable>
}
```

This single function is the gateway to dynamic composition. When you call `subcompose()` during measurement, it composes the given content immediately and returns a list of `Measurable` children that you can then measure and lay out. The `slotId` parameter is used to identify and track different composition slots, enabling recomposition, reuse, and proper state management.

## **Internal mechanisms of SubcomposeLayout**

The real magic happens inside `LayoutNodeSubcompositionsState`, a complex state management system that orchestrates the entire subcomposition process. This class is stored directly on the `LayoutNode`, maintaining a 1-1 mapping between the state and the node.

### **The three-tier node organization**

If you explore the internal implementation, you’ll discover that `SubcomposeLayout` organizes its child nodes into three distinct regions within the `root.foldedChildren` list:

```kotlin
[Active nodes] [Reusable nodes] [Precomposed nodes]
```

**Active nodes**: These are the slots currently being used in the latest measure pass. They’re tracked in the `slotIdToNode` map for quick lookup by slot ID.

**Reusable nodes**: These are nodes that were previously active but are no longer being used. Instead of disposing them immediately (which would destroy their composition state), `SubcomposeLayout` keeps them in a reusable pool. The count is tracked via `reusableCount`.

**Precomposed nodes**: These are nodes that have been composed ahead of time (via the `precompose()` function) but haven’t been used in a measure pass yet. They’re tracked in the `precomposeMap` and counted via `precomposedCount`.

This three-tier organization is the foundation of `SubcomposeLayout`‘s performance optimization strategy.

### **The subcompose execution flow**

When you call `subcompose(slotId, content)` during measurement, the internal machinery follows a sophisticated lookup and allocation strategy. Let’s trace through the code in `LayoutNodeSubcompositionsState.subcompose()`:

```kotlin
fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> {
    makeSureStateIsConsistent()
    val layoutState = root.layoutState
    checkPrecondition(
        layoutState == LayoutState.Measuring ||
            layoutState == LayoutState.LayingOut ||
            layoutState == LayoutState.LookaheadMeasuring ||
            layoutState == LayoutState.LookaheadLayingOut
    ) {
        "subcompose can only be used inside the measure or layout blocks"
    }

    val node =
        slotIdToNode.getOrPut(slotId) {
            val precomposed = precomposeMap.remove(slotId)
            if (precomposed != null) {
                precomposedCount--
                precomposed
            } else {
                takeNodeFromReusables(slotId) ?: createNodeAt(currentIndex)
            }
        }

\/\/ Position the node correctly in the children listif (root.foldedChildren.getOrNull(currentIndex) !== node) {
        val itemIndex = root.foldedChildren.indexOf(node)
        requirePrecondition(itemIndex >= currentIndex) {
            "Key \"$slotId\" was already used. If you are using LazyColumn\/Row please make " +
                "sure you provide a unique key for each item."
        }
        if (currentIndex != itemIndex) {
            move(itemIndex, currentIndex)
        }
    }
    currentIndex++

    subcompose(node, slotId, pausable = false, content)

    return if (layoutState == LayoutState.Measuring || layoutState == LayoutState.LayingOut) {
        node.childMeasurables
    } else {
        node.childLookaheadMeasurables
    }
}

```

The execution follows this precise sequence:

**Step 1: State validation –** The function first ensures the internal state is consistent and verifies that we’re actually in a measure or layout pass. You cannot call `subcompose()` during composition, it’s strictly a measurement-time operation.

**Step 2: Node lookup and allocation –** The system checks `slotIdToNode` to see if this slot ID already has an active node. If not, it follows a fallback chain:

1. Check if the slot was precomposed (`precomposeMap`)
1. Try to reuse a compatible node from the reusable pool (`takeNodeFromReusables()`)
1. Create a brand new LayoutNode (`createNodeAt()`)
**Step 3: Position management –** Once we have a node, we need to ensure it’s at the correct position in the `foldedChildren` list. The `currentIndex` tracks where we are in the active region. If the node is found at a different index, it’s moved to the correct position. This maintains the invariant that active nodes are always at the front of the list, in the order they were subcomposed.

**Step 4: Composition** – The `subcompose(node, slotId, pausable, content)` internal function handles the actual composition into the node. It manages the ReusableComposition lifecycle, handles content changes, and applies paused compositions if applicable.

**Step 5: Return measurables** – Finally, the function returns the appropriate measurables based on whether we’re in a lookahead pass or a regular measure pass.

### **The slot reuse optimization**

One of the most sophisticated aspects of `SubcomposeLayout` is its slot reuse mechanism. When a slot is no longer needed, instead of immediately disposing it, the system can keep it in the reusable pool. This is managed by `disposeOrReuseStartingFromIndex()`:

```kotlin
fun disposeOrReuseStartingFromIndex(startIndex: Int) {
    reusableCount = 0
    val foldedChildren = root.foldedChildren
    val lastReusableIndex = foldedChildren.size - precomposedCount - 1
    var needApplyNotification = false
    if (startIndex <= lastReusableIndex) {
\/\/ construct the set of available slot ids
        reusableSlotIdsSet.clear()
        for (i in startIndex..lastReusableIndex) {
            val slotId = getSlotIdAtIndex(foldedChildren, i)
            reusableSlotIdsSet.add(slotId)
        }

        slotReusePolicy.getSlotsToRetain(reusableSlotIdsSet)
\/\/ iterating backwards so it is easier to remove itemsvar i = lastReusableIndex
        Snapshot.withoutReadObservation {
            while (i >= startIndex) {
                val node = foldedChildren[i]
                val nodeState = nodeToNodeState[node]!!
                val slotId = nodeState.slotId
                if (slotId in reusableSlotIdsSet) {
                    reusableCount++
                    if (nodeState.active) {
                        node.resetLayoutState()
                        nodeState.reuseComposition(forceDeactivate = false)

                        if (nodeState.composedWithReusableContentHost) {
                            needApplyNotification = true
                        }
                    }
                } else {
                    ignoreRemeasureRequests {
                        nodeToNodeState.remove(node)
                        nodeState.composition?.dispose()
                        root.removeAt(i, 1)
                    }
                }
\/\/ remove it from slotIdToNode so it is not considered active
                slotIdToNode.remove(slotId)
                i--
            }
        }
    }

    if (needApplyNotification) {
        Snapshot.sendApplyNotifications()
    }

    makeSureStateIsConsistent()
}
```

This function is called after measurement completes. Any nodes from `startIndex` onwards are no longer active. The system:

1. Collects all the slot IDs that could potentially be reused
1. Calls the `slotReusePolicy.getSlotsToRetain()` to let the policy decide which slots to keep
1. For kept slots, marks them as reusable and deactivates their composition
1. For discarded slots, disposes the composition and removes the node entirely
The deactivation is handled by `NodeState.reuseComposition()`:

```kotlin
private fun NodeState.reuseComposition(forceDeactivate: Boolean) {
    if (!forceDeactivate && composedWithReusableContentHost) {
\/\/ Deactivation through ReusableContentHost is controlled with the active flag
        active = false
    } else {
\/\/ Otherwise, create a new instance to avoid state change notifications
        activeState = mutableStateOf(false)
    }

    if (pausedComposition != null) {
\/\/ Cancelling disposes composition, so no additional work is needed.
        cancelPausedPrecomposition()
    } else if (forceDeactivate) {
        record(SLOperation.ReuseForceSyncDeactivation)
        composition?.deactivate()
    } else {
        val outOfFrameExecutor = outOfFrameExecutor
        if (outOfFrameExecutor != null) {
            record(SLOperation.ReuseScheduleOutOfFrameDeactivation)
            deactivateOutOfFrame(outOfFrameExecutor)
        } else {
            if (!composedWithReusableContentHost) {
                record(SLOperation.ReuseSyncDeactivation)
                composition?.deactivate()
            } else {
                record(SLOperation.ReuseDeactivationViaHost)
            }
        }
    }
}
```

The deactivation strategy depends on whether the composition used `ReusableContentHost`. If it did, changing the `active` flag to false will trigger `ReusableContentHost` to deactivate its content. Otherwise, the composition is directly deactivated either synchronously or scheduled out-of-frame via the `OutOfFrameExecutor`.

When a reusable slot is needed again, `takeNodeFromReusables()` implements a smart matching algorithm:

```kotlin
private fun takeNodeFromReusables(slotId: Any?): LayoutNode? {
    if (reusableCount == 0) {
        return null
    }
    val foldedChildren = root.foldedChildren
    val reusableNodesSectionEnd = foldedChildren.size - precomposedCount
    val reusableNodesSectionStart = reusableNodesSectionEnd - reusableCount
    var index = reusableNodesSectionEnd - 1
    var chosenIndex = -1
\/\/ first try to find a node with exactly the same slotIdwhile (index >= reusableNodesSectionStart) {
        if (getSlotIdAtIndex(foldedChildren, index) == slotId) {
\/\/ we have a node with the same slotId
            chosenIndex = index
            break
        } else {
            index--
        }
    }
    if (chosenIndex == -1) {
\/\/ try to find a first compatible slotId from the end of the section
        index = reusableNodesSectionEnd - 1
        while (index >= reusableNodesSectionStart) {
            val node = foldedChildren[index]
            val nodeState = nodeToNodeState[node]!!
            if (
                nodeState.slotId === ReusedSlotId ||
                    slotReusePolicy.areCompatible(slotId, nodeState.slotId)
            ) {
                nodeState.slotId = slotId
                chosenIndex = index
                break
            }
            index--
        }
    }
    return if (chosenIndex == -1) {
\/\/ no compatible nodes foundnull
    } else {
        if (index != reusableNodesSectionStart) {
\/\/ we need to rearrange the items
            move(index, reusableNodesSectionStart, 1)
        }
        reusableCount--
        val node = foldedChildren[reusableNodesSectionStart]
        val nodeState = nodeToNodeState[node]!!
\/\/ create a new instance to avoid change notifications
        nodeState.record(SLOperation.Reused)
        nodeState.activeState = mutableStateOf(true)
        nodeState.forceReuse = true
        nodeState.forceRecompose = true
        node
    }
}

```

The matching algorithm has two phases:

1. **Exact match**: First, it tries to find a reusable node with the exact same slot ID. This is ideal because the content is likely very similar.
1. **Compatible match**: If no exact match is found, it falls back to finding a compatible slot using the `slotReusePolicy.areCompatible()` method.
When a reusable node is taken, it’s marked with `forceReuse = true` and `forceRecompose = true`. The `forceReuse` flag tells the composition system to use `setContentWithReuse()` instead of the regular `setContent()`, which allows the Compose runtime to maximize reuse of composition state. The `forceRecompose` flag ensures the content is recomposed even if the composition already exists.

### **The measure policy implementation**

The actual measure policy is created by `createMeasurePolicy()`, which returns a specialized object that handles both regular and lookahead measure passes:

```kotlin
fun createMeasurePolicy(
    block: SubcomposeMeasureScope.(Constraints) -> MeasureResult
): MeasurePolicy {
    return object : LayoutNode.NoIntrinsicsMeasurePolicy(error = NoIntrinsicsMessage) {
        override fun MeasureScope.measure(
            measurables: List<Measurable>,
            constraints: Constraints,
        ): MeasureResult {
            scope.layoutDirection = layoutDirection
            scope.density = density
            scope.fontScale = fontScale
            if (!isLookingAhead && root.lookaheadRoot != null) {
\/\/ Approach pass
                currentApproachIndex = 0
                val result = approachMeasureScope.block(constraints)
                val indexAfterMeasure = currentApproachIndex
                return createMeasureResult(result) {
                    currentApproachIndex = indexAfterMeasure
                    result.placeChildren()
\/\/ dispose
                    disposeUnusedSlotsInApproach()
                    disposeOrReuseStartingFromIndex(currentIndex)
                }
            } else {
\/\/ Lookahead pass, or the main pass if not in a lookahead scope.
                currentIndex = 0
                val result = scope.block(constraints)
                val indexAfterMeasure = currentIndex
                return createMeasureResult(result) {
                    currentIndex = indexAfterMeasure
                    result.placeChildren()
                    if (root.lookaheadRoot == null) {
\/\/ If this is in lookahead scope, we need to dispose *after*\/\/ approach placement, to give approach pass the opportunity to\/\/ transfer the ownership of subcompositions before disposing.
                        disposeOrReuseStartingFromIndex(currentIndex)
                    }
                }
            }
        }
    }
}
```

This implementation handles three distinct measure scenarios:

- **Regular measure pass** (when not in a lookahead scope): Resets `currentIndex` to 0, calls the user’s measure block, then disposes or reuses any unused slots after placement.
- **Lookahead pass** (when `isLookingAhead == true`): Similar to regular measure, but defers disposal until after the approach pass completes. This gives the approach pass an opportunity to take ownership of subcompositions.
- **Approach pass** (when `!isLookingAhead && root.lookaheadRoot != null`): Uses a separate `currentApproachIndex` and `approachMeasureScope`. This pass can compose items that weren’t composed in lookahead, and it manages its own set of precomposed slots tracked in `approachPrecomposeSlotHandleMap`.
The reason for wrapping the result in `createMeasureResult()` is to ensure that disposal happens during `placeChildren()`, not during measurement. This is critical because placement is the final phase where we know definitively which children are actually being used.

## **How BoxWithConstraints leverages SubcomposeLayout**

`BoxWithConstraints` is perhaps the most elegant demonstration of `SubcomposeLayout`‘s feature. Its entire implementation is remarkably concise:

```kotlin
@Composable
@UiComposable
fun BoxWithConstraints(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable @UiComposable BoxWithConstraintsScope.() -> Unit,
) {
    val measurePolicy = maybeCachedBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
    SubcomposeLayout(modifier) { constraints ->
        val scope = BoxWithConstraintsScopeImpl(this, constraints)
        @Suppress("ComposableLambdaInMeasurePolicy")
        val measurables = subcompose(Unit) { scope.content() }
        with(measurePolicy) { measure(measurables, constraints) }
    }
}
```

Let’s trace the execution flow when BoxWithConstraints is measured:

**Step 1: Measure begins –** The parent calls measure on the `BoxWithConstraints`‘ `LayoutNode`, passing the constraints it wants to impose.

**Step 2: Scope creation –** Inside the measure lambda, a `BoxWithConstraintsScopeImpl` is created, wrapping the constraints and providing access to them as Dp values:

```kotlin
private data class BoxWithConstraintsScopeImpl(
    private val density: Density,
    override val constraints: Constraints,
) : BoxWithConstraintsScope, BoxScope by BoxScopeInstance {
    override val minWidth: Dp
        get() = with(density) { constraints.minWidth.toDp() }

    override val maxWidth: Dp
        get() =
            with(density) {
                if (constraints.hasBoundedWidth) constraints.maxWidth.toDp() else Dp.Infinity
            }

    override val minHeight: Dp
        get() = with(density) { constraints.minHeight.toDp() }

    override val maxHeight: Dp
        get() =
            with(density) {
                if (constraints.hasBoundedHeight) constraints.maxHeight.toDp() else Dp.Infinity
            }
}

```

This scope implements `BoxWithConstraintsScope`, which extends `BoxScope`, giving the content access to both box alignment modifiers and constraint information.

**Step 3: Subcomposition with constraint access** – The important moment: `subcompose(Unit) { scope.content() }` is called. This composes the user’s content lambda **right now**, during measurement, with the scope as the receiver. Because the scope contains the constraints, the user’s composable code can read `maxWidth`, `maxHeight`, and other constraint properties to make composition decisions:

```kotlin
BoxWithConstraints {
    if (maxWidth > 600.dp) {
        WideScreenLayout()
    } else {
        NarrowScreenLayout()
    }
}
```

When the user’s lambda executes, `maxWidth` is a property access on the `BoxWithConstraintsScopeImpl` instance, which reads from the `constraints` that were passed to the measure lambda. This is how the “future” (measurement constraints) informs the “past” (composition decisions).

**Step 4: Measure and layout –** After subcomposition returns the measurables, `BoxWithConstraints` delegates to a standard `Box` measure policy to handle the actual sizing and alignment logic. The Box measure policy measures each child and positions them according to the `contentAlignment` and any individual `align` modifiers.

This implementation is pretty clever, but simple. By using `SubcomposeLayout`, `BoxWithConstraints` doesn’t need to implement any complex state management, node tracking, or reuse logic, it’s all handled by the `SubcomposeLayout` infrastructure. The entire implementation is just: create a scope, subcompose with that scope, measure the results.

## **Real-world use cases**

`SubcomposeLayout` is the foundational primitive behind many essential Compose components:

**LazyColumn and LazyRow**: These use `SubcomposeLayout` to compose only the visible items based on the available space and scroll position. As you scroll, items are subcomposed just-in-time, measured, and laid out. When they scroll off-screen, they’re moved to the reusable pool for recycling.

**TabRow**: `TabRow` uses `SubcomposeLayout` to measure the tabs first, then subcompose the indicator based on the measured tab positions and sizes.

**Scaffold**: The Scaffold layout uses subcomposition to measure the top bar and bottom bar first, then subcompose the content area with the remaining space.

**Adaptive layouts**: Material3 Adaptive use `BoxWithConstraints` (and by extension, `SubcomposeLayout`) to show different layouts based on screen size and orientation.

Another great example from [RevenueCat’s Android SDK](https://github.com/RevenueCat/purchases-android) is the use of `BoxWithConstraints` to build UI components with dynamic widths. Since [RevenueCat’s paywall UI is ](https://www.revenuecat.com/blog/engineering/server-driven-android/)**[server-driven](https://www.revenuecat.com/blog/engineering/server-driven-android/)**, it needs to handle a wide range of flexible layouts, configured through a web-based, Figma-like dashboard.

For instance, one of the paywall templates allows developers to dynamically configure the number of purchase options directly from the dashboard. Depending on the setup, this could be just one or two options, or even ten.

![](https://cdn.sanity.io/images/c3qnx9b0/production/a3e973e5242e36fc3ef88fb464e8d3595dada385-536x250.png)

In this case, we use `BoxWithConstraints` to calculate the width of each item, ensuring that all items have equal widths and can be either centered on the screen or stretched to fill the available space.

```kotlin
    fun BoxWithConstraintsScope.packageWidth(numberOfPackages: Float): Dp {
        val packages = packagesToDisplay(numberOfPackages)
        val totalPadding = Template4UIConstants.packagesHorizontalPadding * 2
        val totalSpaceBetweenPackages = Template4UIConstants.packageHorizontalSpacing * (packages - 1)
        val availableWidth = maxWidth - totalPadding - totalSpaceBetweenPackages
        return availableWidth \/ packages
    }

    BoxWithConstraints {
        val numberOfPackages = state.templateConfiguration.packages.all.size
        val packageWidth = packageWidth(numberOfPackages.toFloat())
        Row(
            ..
        ) {
            state.templateConfiguration.packages.all.forEach { packageInfo ->
                SelectPackageButton(
                    state,
                    packageInfo,
                    viewModel,
                    Modifier.width(packageWidth), \/\/ calculated width size
                )
            }
        }
    }
```

## **Performance considerations**

However, you should be aware of the performance characteristics of using `SubcomposeLayout` and `BoxWithConstraints` Subcomposing during measurement is inherently more expensive than standard composition. When you call `subcompose()`, the Compose runtime must:

1. Set up a new composition or reuse an existing one
1. Run the composition lambda
1. Apply state changes
1. Materialize the composition into LayoutNodes
1. Return the measurables
This all happens synchronously during the measure pass. If you’re not careful, you can create measure passes that take too long, leading to dropped frames. The slot reuse mechanism mitigates this by avoiding full recomposition when content is similar, but reuse isn’t free, it still requires reactivation and state reconciliation.

Another limitation is that `SubcomposeLayout` doesn’t support intrinsic measurements. If you look at the measure policy implementation, you’ll see it extends `LayoutNode.NoIntrinsicsMeasurePolicy`, which throws an exception if intrinsic measurements are requested:

```kotlin
private val NoIntrinsicsMessage =
    "Asking for intrinsic measurements of SubcomposeLayout " +
        "layouts is not supported. This includes components that are built on top of " +
        "SubcomposeLayout, such as lazy lists, BoxWithConstraints, TabRow, etc. To mitigate " +
        "this:\
" +
        "- if intrinsic measurements are used to achieve 'match parent' sizing, consider " +
        "replacing the parent of the component with a custom layout which controls the order in " +
        "which children are measured, making intrinsic measurement not needed\
" +
        "- adding a size modifier to the component, in order to fast return the queried " +
        "intrinsic measurement."
```

The reason is fundamental: intrinsic measurement asks “what size would this layout want to be?” without actually measuring. But `SubcomposeLayout` doesn’t know what content it will compose until it receives the actual constraints, so it can’t answer intrinsic measurement queries.

## **Conclusion**

In this article, you’ve learned how `SubcomposeLayout` works under the hood, exploring its sophisticated three-tier node organization, slot reuse mechanism, and measure-time composition flow. You’ve seen how BoxWithConstraints leverages this infrastructure to provide a simple but powerful API for constraint-aware composition.

By understanding these internal mechanisms, you can make better decisions about when to use `SubcomposeLayout`-based components and how to optimize their performance. Whether you’re building adaptive layouts with `BoxWithConstraints`, creating custom lazy layouts, or designing new measurement-driven composition patterns, `SubcomposeLayout` provides the foundation for breaking the traditional composition-measurement boundary.

As always, happy coding!

— **[Jaewoong](https://github.com/skydoves)**

---

## Related posts

- [Exploring Modifier.Node for creating custom Modifiers in Jetpack Compose](https://www.revenuecat.com/blog/engineering/compose-custom-modifier)
- [Server-driven UI SDK on Android: how RevenueCat enables remote paywalls without app updates](https://www.revenuecat.com/blog/engineering/server-driven-android)
- [remember vs rememberSaveable: deep dive into state management and recomposition in Jetpack Compose](https://www.revenuecat.com/blog/engineering/remember-vs-remembersaveable)
