# How Decompose Handles Nested Component Scopes in Kotlin Multiplatform

> Discover how Decompose manages nested component scopes in Kotlin Multiplatform. Learn how it isolates child contexts, merges lifecycles, and separates state for a robust hierarchy.

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

---

**Decompose handles nested component scopes by creating isolated child contexts through the `childContext` extension function, which merges parent-child lifecycles while keeping state, retained instances, and back-handlers separate for each level of the component hierarchy.**

The Decompose library by Arkadii Ivanov provides a robust architecture for building component-based applications in Kotlin Multiplatform. When building complex UIs with nested navigation or deeply nested feature modules, understanding how Decompose manages **nested component scopes** is essential for maintaining proper lifecycle boundaries and state isolation.

## Understanding Component Contexts in Decompose

At the core of Decompose's architecture is the `ComponentContext` interface, defined in [`ComponentContext.kt`](https://github.com/arkivanov/decompose/blob/main/ComponentContext.kt). This context provides the essential services every component needs: lifecycle management, state preservation, instance retention, and back-button handling.

The [`GenericComponentContext.kt`](https://github.com/arkivanov/decompose/blob/main/GenericComponentContext.kt) file defines the generic base interface that includes:

- `lifecycle: Lifecycle` – The component's lifecycle state
- `stateKeeper: StateKeeper` – Handles saved state across process death
- `instanceKeeper: InstanceKeeper` – Retains objects across configuration changes
- `backHandler: BackHandler` – Manages back button interception

When a parent component needs to create a child, it cannot simply pass its own context. Doing so would cause lifecycle leaks and state collisions. Instead, Decompose provides a mechanism to create **nested scopes** that inherit from yet remain isolated from the parent.

## Creating Nested Component Scopes with childContext

The primary API for building nested component scopes is the `childContext` extension function, implemented in [`ComponentContextExt.kt`](https://github.com/arkivanov/decompose/blob/main/ComponentContextExt.kt).

### The childContext Function Signature

```kotlin
fun <Ctx : GenericComponentContext<Ctx>> Ctx.childContext(
    key: String,
    lifecycle: Lifecycle? = null,
    backHandlerPriority: Int = BackCallback.PRIORITY_DEFAULT,
): Ctx

```

This function is an extension on `GenericComponentContext`, meaning any component context can create a child context. The function returns the same concrete type (`Ctx`) as the receiver, enabling type-safe chaining for deep hierarchies.

### Lifecycle Merging and Isolation

When creating a nested scope, Decompose must ensure the child respects both its own lifecycle and the parent's. If the parent is destroyed, the child must also be destroyed, regardless of its own state.

The implementation handles this through `MergedLifecycle`:

```kotlin
lifecycle = if (lifecycle == null) this.lifecycle else MergedLifecycle(this.lifecycle, lifecycle)

```

If no custom lifecycle is provided, the child inherits the parent's lifecycle directly. If a custom lifecycle is supplied (useful for navigation-driven components), Decompose creates a `MergedLifecycle` that combines both. This ensures the child receives `ON_CREATE`, `ON_START`, `ON_RESUME` only when both parent and child lifecycles allow it, and receives `ON_PAUSE`, `ON_STOP`, `ON_DESTROY` when either parent or child transitions down.

### State and Instance Isolation

To prevent state collisions between siblings or parent-child pairs, Decompose isolates the `StateKeeper` and `InstanceKeeper` for each nested scope.

In [`ComponentContextExt.kt`](https://github.com/arkivanov/decompose/blob/main/ComponentContextExt.kt), the implementation calls:

```kotlin
stateKeeper = stateKeeper.child(key, lifecycle)
instanceKeeper = instanceKeeper.child(key, lifecycle)

```

The `child` method creates a namespaced sub-registry using the provided `key`. This means:

- Saved state for the child is stored under a unique key, preventing overwrite by the parent or siblings
- When the child's lifecycle is destroyed, the parent automatically cleans up the child's saved state
- Retained instances are similarly scoped, ensuring objects like ViewModels or presenters are kept only as long as the specific nested component exists

### Back-Handler Scoping

Back button handling in nested scopes requires careful ordering. When the user presses back, the most specific (deepest) active component should typically handle the event first.

Decompose achieves this through:

```kotlin
backHandler = backHandler.child(lifecycle, backHandlerPriority)

```

The `child` method creates a nested back-handler that registers with the parent using the specified priority. The `backHandlerPriority` parameter (defaulting to `BackCallback.PRIORITY_DEFAULT`) controls the order in which callbacks are invoked. Higher priority callbacks are invoked before lower priority ones, allowing child components to intercept back presses before parents handle them.

## Building Deep Component Hierarchies

Because `childContext` returns the same type it receives, you can chain calls to build arbitrarily deep trees:

```kotlin
val level1 = rootContext.childContext("level1")
val level2 = level1.childContext("level2")
val level3 = level2.childContext("level3")

```

Each level maintains its own isolated state and instances while respecting the merged lifecycle chain. If `rootContext` is destroyed, all levels automatically receive destruction events. If only `level2` is destroyed (perhaps via navigation), `level1` remains active while `level3` is cleaned up.

## Navigation and Nested Scopes

Navigation utilities in Decompose leverage `childContext` internally to ensure proper scoping. The `childStack`, `childPages`, and `childPanels` functions (found in the navigation modules) automatically invoke the component context factory to obtain fresh contexts for each navigated child.

As implemented in [`ComponentContextFactoryOwner.kt`](https://github.com/arkivanov/decompose/blob/main/ComponentContextFactoryOwner.kt), the factory exposes:

```kotlin
val componentContextFactory: ComponentContextFactory<Ctx>

```

This factory is used by navigation models to create contexts that are automatically scoped to the navigation entry's lifecycle, ensuring that when a screen is popped from the stack, its state and instances are properly disposed.

## Practical Implementation Example

Here is a complete example demonstrating nested component scopes in a real-world scenario:

```kotlin
class RootComponent(
    componentContext: ComponentContext,
) : Component {

    // First-level child scope with isolated state
    private val childCtx = componentContext.childContext(key = "child")

    // Nested child component that itself creates another child scope
    private val nested = ChildComponent(childCtx)

    // ...
}

class ChildComponent(
    componentContext: ComponentContext,
) : Component {

    // Second-level (nested) scope with its own state keeper and instance keeper
    private val innerCtx = componentContext.childContext(key = "inner")

    private val inner = InnerComponent(innerCtx)

    // ...
}

class InnerComponent(
    componentContext: ComponentContext,
) : Component {
    // Deepest level - still has isolated state and lifecycle
    // ...
}

```

In this hierarchy:

- `RootComponent` creates a child scope named `"child"` with its own `StateKeeper` and `InstanceKeeper`
- `ChildComponent` further creates its own child scope `"inner"`, isolated from both the root and its siblings
- Each scope respects the merged lifecycle chain, ensuring proper cleanup when any ancestor is destroyed
- Back button handling is scoped, allowing `InnerComponent` to intercept back presses before `ChildComponent` or `RootComponent` handle them

## Summary

Decompose handles **nested component scopes** through a sophisticated context inheritance system that balances isolation with proper lifecycle propagation:

- The `childContext` extension function in [`ComponentContextExt.kt`](https://github.com/arkivanov/decompose/blob/main/ComponentContextExt.kt) creates nested scopes while preserving the concrete context type for type-safe chaining
- `MergedLifecycle` combines parent and child lifecycles, ensuring children respect ancestor state changes while maintaining their own lifecycle independence
- Isolated `StateKeeper` and `InstanceKeeper` instances prevent state collisions between siblings and parent-child pairs, with automatic cleanup on destruction
- `BackHandler` scoping with priority support allows deep components to intercept back presses before ancestors handle them
- Navigation utilities like `childStack` and `childPages` leverage `ComponentContextFactoryOwner` to automatically scope navigated children

This architecture enables arbitrarily deep component trees while maintaining clear boundaries for state, lifecycle, and navigation concerns.

## Frequently Asked Questions

### How does Decompose prevent memory leaks when creating nested component scopes?

Decompose prevents memory leaks through the `MergedLifecycle` mechanism implemented in [`ComponentContextExt.kt`](https://github.com/arkivanov/decompose/blob/main/ComponentContextExt.kt). When a parent component is destroyed, its lifecycle transitions to `DESTROYED`, which automatically triggers the merged lifecycle to propagate destruction events to all child scopes. Additionally, the `StateKeeper` and `InstanceKeeper` child instances are tied to the child's lifecycle; when the child is destroyed, these registries automatically clean up their associated resources, preventing retained references from leaking memory.

### Can I customize the lifecycle of a nested component independently from its parent?

Yes, Decompose supports custom lifecycles for nested components through the optional `lifecycle` parameter in the `childContext` function. When you provide a custom `Lifecycle`, Decompose creates a `MergedLifecycle` that combines both the parent and custom lifecycles. This allows the child to respond to its own manual lifecycle events (such as those driven by navigation visibility changes) while still respecting the parent's lifecycle boundaries. The child will only be in the `RESUMED` state when both the parent and custom lifecycle allow it.

### What is the difference between fixed child components and navigation-driven child components in Decompose?

Fixed child components are created directly via `childContext` and typically live for the entire duration of the parent component, with their lifecycle directly tied to the parent's lifecycle (or a subset via custom lifecycle). Navigation-driven child components are created indirectly through navigation utilities like `childStack`, `childPages`, or `childPanels`, which internally use `ComponentContextFactoryOwner` to create scoped contexts automatically. These navigation-driven children have lifecycles managed by the navigation state (e.g., being destroyed when popped from a stack), while fixed children remain until the parent is destroyed or manually cleaned up.