# How ChildStack Manages Back Stack Navigation State in Decompose

> Learn how ChildStack manages back stack navigation state in Decompose. Understand its immutable list approach and state transformation for seamless navigation.

- Repository: [Arkadii Ivanov/decompose](https://github.com/arkivanov/decompose)
- Tags: internals
- Published: 2026-02-25

---

**`ChildStack` maintains back stack navigation state as an immutable list of configurations where the active child represents the top of the stack and preceding elements form the history, transformed through `StackNavState` and mapped to component instances via the `childStack` factory.**

In the Decompose navigation library by Arkadii Ivanov, `ChildStack` serves as the core data structure for representing hierarchical navigation history. Understanding how this class manages the relationship between active and inactive children is essential for building predictable, testable navigation flows in Kotlin Multiplatform applications.

## The ChildStack Data Structure

The foundation of back stack management resides in `[ChildStack.kt]`, which defines the `ChildStack` class as a simple but strict state holder.

`ChildStack` stores two critical properties:

- **`active: Child.Created<C, T>`** – Represents the currently visible (top) child component and its configuration
- **`backStack: List<Child.Created<C, T>>`** – Represents the history of inactive children maintained in order

The `items` property exposes the complete stack as a unified list via a `GettingList` implementation, combining `backStack` + `active` so UI layers can iterate over the full navigation history without managing indices manually.

```kotlin
// Conceptual structure from ChildStack.kt
data class ChildStack<out C : Any, out T : Any>(
    val active: Child.Created<C, T>,
    val backStack: List<Child.Created<C, T>> = emptyList()
) {
    val items: List<Child.Created<C, T>> = GettingList(backStack.size + 1) { index ->
        if (index == backStack.size) active else backStack[index]
    }
}

```

## Creating and Updating the Navigation Stack

Navigation transformations occur in `[ChildStackFactory.kt]` through the `childStack` factory functions. These functions initialize a `children` navigation source that manages the lifecycle of components based on configuration changes.

The factory accepts several key parameters:

1. **`initialStack`** – Supplies the starting list of configurations (ordered from bottom to top)
2. **`navTransformer`** – Defines how to transform the current `StackNavState` when navigation events occur (push, pop, replace)
3. **`serializer`** – Optional `KSerializer` for state persistence

Internally, the factory maintains a `StackNavState` class (defined within [`ChildStackFactory.kt`](https://github.com/arkivanov/decompose/blob/main/ChildStackFactory.kt)) that holds the immutable `configurations: List<C>` list. Every navigation event creates a new `StackNavState` instance with a modified configuration list, ensuring thread-safe, predictable state updates.

```kotlin
// From ChildStackFactory.kt - conceptual initialization
val stack: Value<ChildStack<Config, MyComponent>> = childStack(
    source = navigation,
    initialStack = { listOf(HomeConfig) },
    handleBackButton = true,
    childFactory = { config, componentContext -> 
        createComponent(config, componentContext) 
    }
)

```

## Mapping Configurations to ChildStack Instances

The bridge between raw configuration lists and typed component instances happens through the `stateMapper` parameter inside the `childStack` factory.

When configurations change, the navigation source creates child components for each configuration in the stack. The `stateMapper` receives this `List<Child.Created<C, T>>` and constructs the final `ChildStack`:

```kotlin
// Implementation detail from ChildStackFactory.kt
ChildStack(
    active = createdChildren.last(),
    backStack = createdChildren.dropLast(1),
)

```

This mapping ensures that:
- The **active** child is always the last element in the created children list (top of stack)
- The **back stack** contains everything preceding the active child in historical order
- The structure remains immutable; every navigation operation produces a new `ChildStack` instance

## Back Button Handling

Decompose provides declarative back button management through the `backTransformer` parameter when `handleBackButton = true`.

The transformer inspects the current `StackNavState` size to determine if a pop operation is valid:

- **If size > 1**: Returns a lambda executing `dropLast(1)` on the configurations list, removing the active configuration and exposing the previous one
- **If size == 1**: Returns `null`, preventing the back button from popping the root screen

```kotlin
// Back handling logic from ChildStackFactory.kt
backTransformer = { state ->
    if (state.configurations.size > 1) {
        { state.copy(configurations = state.configurations.dropLast(1)) }
    } else {
        null
    }
}

```

This approach keeps navigation logic pure and testable, with the actual back button dispatch handled by the Decompose framework.

## State Persistence Across Process Death

The back stack navigation state survives process death through the optional serialization layer. When provided with a `serializer`, the `childStack` factory:

1. **Saves state**: Serializes the `List<C>` configurations into a `SerializableContainer` using the provided `KSerializer`
2. **Restores state**: Deserializes the configuration list on recreation, passing it to `restoreStack` callback before creating components

This ensures users return to the exact same navigation depth and history after background termination, with the entire configuration stack (not just the active screen) recovered accurately.

## Compose UI Integration

The Compose-specific extension in `[ChildStack.kt]` (Compose module) renders the stack while managing UI state retention.

The composable accepts a `Value<ChildStack>` and uses `SaveableStateHolder` to preserve the UI state (scroll positions, text input, etc.) for every child in the stack, not just the active one. When a child leaves the back stack entirely, its associated saved state is automatically purged to prevent memory leaks.

```kotlin
// Compose usage from ChildStack.kt (Compose extension)
@Composable
fun MyApp() {
    ChildStack(
        stack = stack,
        animation = stackAnimation(slide()),
    ) { child ->
        // child.instance is the active component
        when (child.configuration) {
            is HomeConfig -> HomeScreen(child.instance)
            is DetailsConfig -> DetailsScreen(child.instance)
        }
    }
}

```

## Summary

- **`ChildStack`** in `[ChildStack.kt]` stores navigation state as `active` (top) plus `backStack` (history) using immutable data structures
- **`childStack`** factory in `[ChildStackFactory.kt]` transforms configuration lists through `StackNavState` and maps them to component instances
- **Back navigation** uses `dropLast(1)` on the configuration list when `handleBackButton` is enabled and the stack depth exceeds one
- **State persistence** serializes the configuration list via the provided `serializer`, restoring the complete back stack across process death
- **Compose integration** retains UI state for all children in the stack using `SaveableStateHolder`, cleaning up only when children are permanently removed

## Frequently Asked Questions

### How is the back stack represented internally in ChildStack?

The back stack is represented as a `List<Child.Created<C, T>>` stored in the `backStack` property of the `ChildStack` class defined in `[ChildStack.kt]`. This list contains all inactive children in historical order, while the `active` property holds the top child separately. The `items` property merges both into a single iterable view for convenience.

### What happens when the back button is pressed in a ChildStack?

When `handleBackButton = true`, the `backTransformer` in `[ChildStackFactory.kt]` checks if `state.configurations.size > 1`. If true, it returns a transformation that executes `dropLast(1)` on the configuration list, effectively removing the active configuration and restoring the previous one as active. If only one configuration remains, it returns `null` to prevent exiting the root screen.

### Can the back stack state survive process death?

Yes. By providing a `serializer` parameter to the `childStack` factory, the list of configurations is serialized into a `SerializableContainer` when the app enters the background. Upon restoration, the factory uses `restoreStack` to recreate the exact configuration list, reconstructing the entire navigation history including the back stack depth and specific configurations.

### How does Compose retain UI state for back stack entries?

The Compose extension `[ChildStack.kt]` uses `SaveableStateHolder` to retain UI state (such as scroll position or form input) for every child present in the stack. When a child moves from active to back stack, its state is preserved. When a configuration is permanently removed from the stack (popped), the associated saved state is automatically cleared to maintain memory efficiency.