Repository: arkivanov/Decompose
Pull request #731 (2024-07-01 18:52:48)

Allow duplicate configurations as an experimental feature


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a `DecomposeExperimentFlags` class to manage experimental features.
  - Added a `ChildKey` class to handle child components with unique keys.
  - Enhanced child navigation with key-based configuration and duplication handling.

- **Bug Fixes**
  - Improved state restoration when handling duplicated child configurations.

- **Chores**
  - Updated and expanded test cases to cover new experimental features and ensure functionality.
  - Refined comments and documentation for better clarity on new and existing features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

coderabbitai[bot]: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: summarize review in progress by coderabbit.ai -->

> [!NOTE]
> Currently processing new changes in this PR. This may take a few minutes, please wait...
> 
> <details>
> <summary>Commits</summary>
> 
> Files that changed from the base of the PR and between 82275b37d04d9d0f9491384295d7fdc3631e9ab1 and 3f9168dc9d1a66239e8972732fa78e899583b92c.
> 
> </details>
> 
> 
> <details>
> <summary>Files selected for processing (28)</summary>
> 
> * decompose/api/android/decompose.api (3 hunks)
> * decompose/api/decompose.klib.api (4 hunks)
> * decompose/api/jvm/decompose.api (3 hunks)
> * decompose/src/commonMain/kotlin/com/arkivanov/decompose/Child.kt (1 hunks)
> * decompose/src/commonMain/kotlin/com/arkivanov/decompose/ChildKey.kt (1 hunks)
> * decompose/src/commonMain/kotlin/com/arkivanov/decompose/DecomposeExperimentFlags.kt (1 hunks)
> * decompose/src/commonMain/kotlin/com/arkivanov/decompose/GettingList.kt (1 hunks)
> * decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt (1 hunks)
> * decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt (7 hunks)
> * decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt (2 hunks)
> * decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt (3 hunks)
> * decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt (2 hunks)
> * decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt (18 hunks)
> * decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt (2 hunks)
> * decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt (4 hunks)
> * decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt (18 hunks)
> * decompose/src/webTest/kotlin/com/arkivanov/decompose/router/stack/webhistory/DefaultWebHistoryControllerTest.kt (3 hunks)
> * extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt (5 hunks)
> * extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt (3 hunks)
> * extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt (4 hunks)
> * extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (4 hunks)
> * extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/MovableStackAnimation.kt (1 hunks)
> * extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimation.kt (1 hunks)
> * extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/predictiveback/PredictiveBackAnimation.kt (2 hunks)
> * extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (5 hunks)
> * sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/cards/CardsContent.kt (6 hunks)
> * sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/CustomNavigationContent.kt (1 hunks)
> * sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt (2 hunks)
> 
> </details>
> 
> ```ascii
>  _________________________________________________________________
> < This is Sparta! And I'm here to kick bugs into the pit of doom. >
>  -----------------------------------------------------------------
>   \
>    \   \
>         \ /\
>         ( )
>       .( o ).
> ```

<!-- end of auto-generated comment: summarize review in progress by coderabbit.ai --><!-- tips_start -->

---

> [!TIP]
> <details>
> <summary>Early access features: enabled</summary>
> 
> We are currently testing the following features in early access:
> 
> - **OpenAI `gpt-4o` model for code reviews and chat**: OpenAI claims that this model is better at understanding and generating code than the previous models. We seek your feedback over the next few weeks before making it generally available.
> 
> Note:
> - You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.
> - Please join our [Discord Community](https://discord.com/invite/GsXnASn26c) to provide feedback and report issues.
> - OSS projects are currently opted into early access features by default.
> 
> </details>

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

<details>
<summary>Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
	- `I pushed a fix in commit <commit_id>.`
	- `Generate unit testing code for this file.`
	- `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
	- `@coderabbitai generate unit testing code for this file.`
	-	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
	- `@coderabbitai generate interesting stats about this repository and render them as a table.`
	- `@coderabbitai show all the console.log statements in this repository.`
	- `@coderabbitai read src/utils.ts and generate unit testing code.`
	- `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
	- `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (invoked as PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.


Additionally, you can add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.

### CodeRabbit Configration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://coderabbit.ai/docs) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](https://discord.com/invite/GsXnASn26c) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Patch:
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index eab4d273..0120a510 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -5,30 +5,39 @@ public abstract interface class com/arkivanov/decompose/Cancellation {
 public abstract class com/arkivanov/decompose/Child {
 	public abstract fun getConfiguration ()Ljava/lang/Object;
 	public abstract fun getInstance ()Ljava/lang/Object;
+	public abstract fun getKey ()Ljava/lang/Object;
 }
 
 public final class com/arkivanov/decompose/Child$Created : com/arkivanov/decompose/Child {
 	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;)V
+	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
 	public final fun component1 ()Ljava/lang/Object;
 	public final fun component2 ()Ljava/lang/Object;
-	public final fun copy (Ljava/lang/Object;Ljava/lang/Object;)Lcom/arkivanov/decompose/Child$Created;
-	public static synthetic fun copy$default (Lcom/arkivanov/decompose/Child$Created;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Lcom/arkivanov/decompose/Child$Created;
+	public final fun component3 ()Ljava/lang/Object;
+	public final synthetic fun copy (Ljava/lang/Object;Ljava/lang/Object;)Lcom/arkivanov/decompose/Child;
+	public final fun copy (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Lcom/arkivanov/decompose/Child$Created;
+	public static synthetic fun copy$default (Lcom/arkivanov/decompose/Child$Created;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Lcom/arkivanov/decompose/Child$Created;
 	public fun equals (Ljava/lang/Object;)Z
 	public fun getConfiguration ()Ljava/lang/Object;
 	public fun getInstance ()Ljava/lang/Object;
+	public fun getKey ()Ljava/lang/Object;
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 }
 
 public final class com/arkivanov/decompose/Child$Destroyed : com/arkivanov/decompose/Child {
 	public fun <init> (Ljava/lang/Object;)V
+	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;)V
 	public final fun component1 ()Ljava/lang/Object;
-	public final fun copy (Ljava/lang/Object;)Lcom/arkivanov/decompose/Child$Destroyed;
-	public static synthetic fun copy$default (Lcom/arkivanov/decompose/Child$Destroyed;Ljava/lang/Object;ILjava/lang/Object;)Lcom/arkivanov/decompose/Child$Destroyed;
+	public final fun component2 ()Ljava/lang/Object;
+	public final synthetic fun copy (Ljava/lang/Object;)Lcom/arkivanov/decompose/Child;
+	public final fun copy (Ljava/lang/Object;Ljava/lang/Object;)Lcom/arkivanov/decompose/Child$Destroyed;
+	public static synthetic fun copy$default (Lcom/arkivanov/decompose/Child$Destroyed;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Lcom/arkivanov/decompose/Child$Destroyed;
 	public fun equals (Ljava/lang/Object;)Z
 	public fun getConfiguration ()Ljava/lang/Object;
 	public synthetic fun getInstance ()Ljava/lang/Object;
 	public fun getInstance ()Ljava/lang/Void;
+	public fun getKey ()Ljava/lang/Object;
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 }
@@ -49,6 +58,12 @@ public abstract interface class com/arkivanov/decompose/ComponentContextFactoryO
 	public abstract fun getComponentContextFactory ()Lcom/arkivanov/decompose/ComponentContextFactory;
 }
 
+public final class com/arkivanov/decompose/DecomposeExperimentFlags {
+	public static final field INSTANCE Lcom/arkivanov/decompose/DecomposeExperimentFlags;
+	public final fun getDuplicateConfigurationsEnabled ()Z
+	public final fun setDuplicateConfigurationsEnabled (Z)V
+}
+
 public final class com/arkivanov/decompose/DeeplinkUtilsKt {
 	public static final fun handleDeepLink (Landroid/app/Activity;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
 }
@@ -270,6 +285,7 @@ public final class com/arkivanov/decompose/router/stack/ChildStack {
 	public fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)V
 	public synthetic fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
 	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;)V
+	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
 	public final fun component1 ()Lcom/arkivanov/decompose/Child$Created;
 	public final fun component2 ()Ljava/util/List;
 	public final fun copy (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)Lcom/arkivanov/decompose/router/stack/ChildStack;
diff --git a/decompose/api/decompose.klib.api b/decompose/api/decompose.klib.api
index 73a2af0d..d0ff66aa 100644
--- a/decompose/api/decompose.klib.api
+++ b/decompose/api/decompose.klib.api
@@ -126,6 +126,7 @@ final class <#A: out kotlin/Any, #B: out kotlin/Any> com.arkivanov.decompose.rou
 }
 final class <#A: out kotlin/Any, #B: out kotlin/Any> com.arkivanov.decompose.router.stack/ChildStack { // com.arkivanov.decompose.router.stack/ChildStack|null[0]
     constructor <init>(#A, #B) // com.arkivanov.decompose.router.stack/ChildStack.<init>|<init>(1:0;1:1){}[0]
+    constructor <init>(#A, #B, kotlin/Any) // com.arkivanov.decompose.router.stack/ChildStack.<init>|<init>(1:0;1:1;kotlin.Any){}[0]
     constructor <init>(com.arkivanov.decompose/Child.Created<#A, #B>, kotlin.collections/List<com.arkivanov.decompose/Child.Created<#A, #B>> = ...) // com.arkivanov.decompose.router.stack/ChildStack.<init>|<init>(com.arkivanov.decompose.Child.Created<1:0,1:1>;kotlin.collections.List<com.arkivanov.decompose.Child.Created<1:0,1:1>>){}[0]
     final fun component1(): com.arkivanov.decompose/Child.Created<#A, #B> // com.arkivanov.decompose.router.stack/ChildStack.component1|component1(){}[0]
     final fun component2(): kotlin.collections/List<com.arkivanov.decompose/Child.Created<#A, #B>> // com.arkivanov.decompose.router.stack/ChildStack.component2|component2(){}[0]
@@ -233,6 +234,11 @@ final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNav
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/pushToFront(#A, crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/pushToFront|pushToFront@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(0:0;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/replaceAll(kotlin/Array<out #A>..., crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/replaceAll|replaceAll@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(kotlin.Array<out|0:0>...;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/replaceCurrent(#A, crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/replaceCurrent|replaceCurrent@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(0:0;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
+final object com.arkivanov.decompose/DecomposeExperimentFlags { // com.arkivanov.decompose/DecomposeExperimentFlags|null[0]
+    final var duplicateConfigurationsEnabled // com.arkivanov.decompose/DecomposeExperimentFlags.duplicateConfigurationsEnabled|{}duplicateConfigurationsEnabled[0]
+        final fun <get-duplicateConfigurationsEnabled>(): kotlin/Boolean // com.arkivanov.decompose/DecomposeExperimentFlags.duplicateConfigurationsEnabled.<get-duplicateConfigurationsEnabled>|<get-duplicateConfigurationsEnabled>(){}[0]
+        final fun <set-duplicateConfigurationsEnabled>(kotlin/Boolean) // com.arkivanov.decompose/DecomposeExperimentFlags.duplicateConfigurationsEnabled.<set-duplicateConfigurationsEnabled>|<set-duplicateConfigurationsEnabled>(kotlin.Boolean){}[0]
+}
 final val com.arkivanov.decompose.router.slot/child // com.arkivanov.decompose.router.slot/child|@com.arkivanov.decompose.value.Value<com.arkivanov.decompose.router.slot.ChildSlot<0:0,0:1>>{0§<kotlin.Any>;1§<kotlin.Any>}child[0]
     final fun <#A1: kotlin/Any, #B1: kotlin/Any> (com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.slot/ChildSlot<#A1, #B1>>).<get-child>(): com.arkivanov.decompose/Child.Created<#A1, #B1>? // com.arkivanov.decompose.router.slot/child.<get-child>|<get-child>@com.arkivanov.decompose.value.Value<com.arkivanov.decompose.router.slot.ChildSlot<0:0,0:1>>(){0§<kotlin.Any>;1§<kotlin.Any>}[0]
 final val com.arkivanov.decompose.router.stack/active // com.arkivanov.decompose.router.stack/active|@com.arkivanov.decompose.value.Value<com.arkivanov.decompose.router.stack.ChildStack<0:0,0:1>>{0§<kotlin.Any>;1§<kotlin.Any>}active[0]
@@ -258,12 +264,17 @@ sealed class <#A: out kotlin/Any, #B: out kotlin/Any> com.arkivanov.decompose/Ch
         abstract fun <get-configuration>(): #A // com.arkivanov.decompose/Child.configuration.<get-configuration>|<get-configuration>(){}[0]
     abstract val instance // com.arkivanov.decompose/Child.instance|{}instance[0]
         abstract fun <get-instance>(): #B? // com.arkivanov.decompose/Child.instance.<get-instance>|<get-instance>(){}[0]
+    abstract val key // com.arkivanov.decompose/Child.key|{}key[0]
+        abstract fun <get-key>(): kotlin/Any // com.arkivanov.decompose/Child.key.<get-key>|<get-key>(){}[0]
     constructor <init>() // com.arkivanov.decompose/Child.<init>|<init>(){}[0]
     final class <#A1: out kotlin/Any, #B1: out kotlin/Any> Created : com.arkivanov.decompose/Child<#A1, #B1> { // com.arkivanov.decompose/Child.Created|null[0]
         constructor <init>(#A1, #B1) // com.arkivanov.decompose/Child.Created.<init>|<init>(1:0;1:1){}[0]
+        constructor <init>(#A1, #B1, kotlin/Any) // com.arkivanov.decompose/Child.Created.<init>|<init>(1:0;1:1;kotlin.Any){}[0]
         final fun component1(): #A1 // com.arkivanov.decompose/Child.Created.component1|component1(){}[0]
         final fun component2(): #B1 // com.arkivanov.decompose/Child.Created.component2|component2(){}[0]
-        final fun copy(#A1 = ..., #B1 = ...): com.arkivanov.decompose/Child.Created<#A1, #B1> // com.arkivanov.decompose/Child.Created.copy|copy(1:0;1:1){}[0]
+        final fun component3(): kotlin/Any // com.arkivanov.decompose/Child.Created.component3|component3(){}[0]
+        final fun copy(#A1 = ..., #B1 = ..., kotlin/Any = ...): com.arkivanov.decompose/Child.Created<#A1, #B1> // com.arkivanov.decompose/Child.Created.copy|copy(1:0;1:1;kotlin.Any){}[0]
+        final fun copy(#A1, #B1): com.arkivanov.decompose/Child<#A1, #B1> // com.arkivanov.decompose/Child.Created.copy|copy(1:0;1:1){}[0]
         final fun equals(kotlin/Any?): kotlin/Boolean // com.arkivanov.decompose/Child.Created.equals|equals(kotlin.Any?){}[0]
         final fun hashCode(): kotlin/Int // com.arkivanov.decompose/Child.Created.hashCode|hashCode(){}[0]
         final fun toString(): kotlin/String // com.arkivanov.decompose/Child.Created.toString|toString(){}[0]
@@ -271,11 +282,16 @@ sealed class <#A: out kotlin/Any, #B: out kotlin/Any> com.arkivanov.decompose/Ch
             final fun <get-configuration>(): #A1 // com.arkivanov.decompose/Child.Created.configuration.<get-configuration>|<get-configuration>(){}[0]
         final val instance // com.arkivanov.decompose/Child.Created.instance|{}instance[0]
             final fun <get-instance>(): #B1 // com.arkivanov.decompose/Child.Created.instance.<get-instance>|<get-instance>(){}[0]
+        final val key // com.arkivanov.decompose/Child.Created.key|{}key[0]
+            final fun <get-key>(): kotlin/Any // com.arkivanov.decompose/Child.Created.key.<get-key>|<get-key>(){}[0]
     }
     final class <#A1: out kotlin/Any> Destroyed : com.arkivanov.decompose/Child<#A1, kotlin/Nothing> { // com.arkivanov.decompose/Child.Destroyed|null[0]
         constructor <init>(#A1) // com.arkivanov.decompose/Child.Destroyed.<init>|<init>(1:0){}[0]
+        constructor <init>(#A1, kotlin/Any) // com.arkivanov.decompose/Child.Destroyed.<init>|<init>(1:0;kotlin.Any){}[0]
         final fun component1(): #A1 // com.arkivanov.decompose/Child.Destroyed.component1|component1(){}[0]
-        final fun copy(#A1 = ...): com.arkivanov.decompose/Child.Destroyed<#A1> // com.arkivanov.decompose/Child.Destroyed.copy|copy(1:0){}[0]
+        final fun component2(): kotlin/Any // com.arkivanov.decompose/Child.Destroyed.component2|component2(){}[0]
+        final fun copy(#A1 = ..., kotlin/Any = ...): com.arkivanov.decompose/Child.Destroyed<#A1> // com.arkivanov.decompose/Child.Destroyed.copy|copy(1:0;kotlin.Any){}[0]
+        final fun copy(#A1): com.arkivanov.decompose/Child<#A1, kotlin/Nothing> // com.arkivanov.decompose/Child.Destroyed.copy|copy(1:0){}[0]
         final fun equals(kotlin/Any?): kotlin/Boolean // com.arkivanov.decompose/Child.Destroyed.equals|equals(kotlin.Any?){}[0]
         final fun hashCode(): kotlin/Int // com.arkivanov.decompose/Child.Destroyed.hashCode|hashCode(){}[0]
         final fun toString(): kotlin/String // com.arkivanov.decompose/Child.Destroyed.toString|toString(){}[0]
@@ -283,6 +299,8 @@ sealed class <#A: out kotlin/Any, #B: out kotlin/Any> com.arkivanov.decompose/Ch
             final fun <get-configuration>(): #A1 // com.arkivanov.decompose/Child.Destroyed.configuration.<get-configuration>|<get-configuration>(){}[0]
         final val instance // com.arkivanov.decompose/Child.Destroyed.instance|{}instance[0]
             final fun <get-instance>(): kotlin/Nothing? // com.arkivanov.decompose/Child.Destroyed.instance.<get-instance>|<get-instance>(){}[0]
+        final val key // com.arkivanov.decompose/Child.Destroyed.key|{}key[0]
+            final fun <get-key>(): kotlin/Any // com.arkivanov.decompose/Child.Destroyed.key.<get-key>|<get-key>(){}[0]
     }
 }
 // Targets: [js, wasmJs]
diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api
index 83717bbf..9280659b 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -5,30 +5,39 @@ public abstract interface class com/arkivanov/decompose/Cancellation {
 public abstract class com/arkivanov/decompose/Child {
 	public abstract fun getConfiguration ()Ljava/lang/Object;
 	public abstract fun getInstance ()Ljava/lang/Object;
+	public abstract fun getKey ()Ljava/lang/Object;
 }
 
 public final class com/arkivanov/decompose/Child$Created : com/arkivanov/decompose/Child {
 	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;)V
+	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
 	public final fun component1 ()Ljava/lang/Object;
 	public final fun component2 ()Ljava/lang/Object;
-	public final fun copy (Ljava/lang/Object;Ljava/lang/Object;)Lcom/arkivanov/decompose/Child$Created;
-	public static synthetic fun copy$default (Lcom/arkivanov/decompose/Child$Created;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Lcom/arkivanov/decompose/Child$Created;
+	public final fun component3 ()Ljava/lang/Object;
+	public final synthetic fun copy (Ljava/lang/Object;Ljava/lang/Object;)Lcom/arkivanov/decompose/Child;
+	public final fun copy (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Lcom/arkivanov/decompose/Child$Created;
+	public static synthetic fun copy$default (Lcom/arkivanov/decompose/Child$Created;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Lcom/arkivanov/decompose/Child$Created;
 	public fun equals (Ljava/lang/Object;)Z
 	public fun getConfiguration ()Ljava/lang/Object;
 	public fun getInstance ()Ljava/lang/Object;
+	public fun getKey ()Ljava/lang/Object;
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 }
 
 public final class com/arkivanov/decompose/Child$Destroyed : com/arkivanov/decompose/Child {
 	public fun <init> (Ljava/lang/Object;)V
+	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;)V
 	public final fun component1 ()Ljava/lang/Object;
-	public final fun copy (Ljava/lang/Object;)Lcom/arkivanov/decompose/Child$Destroyed;
-	public static synthetic fun copy$default (Lcom/arkivanov/decompose/Child$Destroyed;Ljava/lang/Object;ILjava/lang/Object;)Lcom/arkivanov/decompose/Child$Destroyed;
+	public final fun component2 ()Ljava/lang/Object;
+	public final synthetic fun copy (Ljava/lang/Object;)Lcom/arkivanov/decompose/Child;
+	public final fun copy (Ljava/lang/Object;Ljava/lang/Object;)Lcom/arkivanov/decompose/Child$Destroyed;
+	public static synthetic fun copy$default (Lcom/arkivanov/decompose/Child$Destroyed;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Lcom/arkivanov/decompose/Child$Destroyed;
 	public fun equals (Ljava/lang/Object;)Z
 	public fun getConfiguration ()Ljava/lang/Object;
 	public synthetic fun getInstance ()Ljava/lang/Object;
 	public fun getInstance ()Ljava/lang/Void;
+	public fun getKey ()Ljava/lang/Object;
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 }
@@ -49,6 +58,12 @@ public abstract interface class com/arkivanov/decompose/ComponentContextFactoryO
 	public abstract fun getComponentContextFactory ()Lcom/arkivanov/decompose/ComponentContextFactory;
 }
 
+public final class com/arkivanov/decompose/DecomposeExperimentFlags {
+	public static final field INSTANCE Lcom/arkivanov/decompose/DecomposeExperimentFlags;
+	public final fun getDuplicateConfigurationsEnabled ()Z
+	public final fun setDuplicateConfigurationsEnabled (Z)V
+}
+
 public final class com/arkivanov/decompose/DefaultComponentContext : com/arkivanov/decompose/ComponentContext {
 	public fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;)V
 	public fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backhandler/BackHandler;)V
@@ -250,6 +265,7 @@ public final class com/arkivanov/decompose/router/stack/ChildStack {
 	public fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)V
 	public synthetic fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
 	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;)V
+	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
 	public final fun component1 ()Lcom/arkivanov/decompose/Child$Created;
 	public final fun component2 ()Ljava/util/List;
 	public final fun copy (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)Lcom/arkivanov/decompose/router/stack/ChildStack;
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Child.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Child.kt
index 8d14111a..ca3b5296 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Child.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Child.kt
@@ -5,14 +5,42 @@ sealed class Child<out C : Any, out T : Any> {
     abstract val configuration: C
     abstract val instance: T?
 
-    data class Created<out C : Any, out T : Any>(
+    @ExperimentalDecomposeApi
+    abstract val key: Any
+
+    data class Created<out C : Any, out T : Any> @ExperimentalDecomposeApi constructor(
         override val configuration: C,
         override val instance: T,
-    ) : Child<C, T>()
 
-    data class Destroyed<out C : Any>(
+        @property:ExperimentalDecomposeApi
+        override val key: Any,
+    ) : Child<C, T>() {
+        constructor(configuration: C, instance: T) : this(
+            configuration = configuration,
+            instance = instance,
+            key = configuration,
+        )
+
+        @Deprecated(message = "For binary compatibility", level = DeprecationLevel.HIDDEN)
+        fun copy(configuration: @UnsafeVariance C, instance: @UnsafeVariance T): Child<C, T> =
+            copy(configuration = configuration, instance = instance)
+    }
+
+    data class Destroyed<out C : Any> @ExperimentalDecomposeApi constructor(
         override val configuration: C,
+
+        @property:ExperimentalDecomposeApi
+        override val key: Any,
     ) : Child<C, Nothing>() {
+        constructor(configuration: C) : this(
+            configuration = configuration,
+            key = configuration,
+        )
+
         override val instance: Nothing? = null
+
+        @Deprecated(message = "For binary compatibility", level = DeprecationLevel.HIDDEN)
+        fun copy(configuration: @UnsafeVariance C): Child<C, Nothing> =
+            copy(configuration = configuration)
     }
 }
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/DecomposeExperimentFlags.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/DecomposeExperimentFlags.kt
new file mode 100644
index 00000000..72d3bda2
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/DecomposeExperimentFlags.kt
@@ -0,0 +1,7 @@
+package com.arkivanov.decompose
+
+@ExperimentalDecomposeApi
+object DecomposeExperimentFlags {
+
+    var duplicateConfigurationsEnabled: Boolean = false
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/GettingList.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/GettingList.kt
index 2de29f76..c60aecd6 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/GettingList.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/GettingList.kt
@@ -8,3 +8,6 @@ internal class GettingList<out T>(
     override fun get(index: Int): T =
         get.invoke(index)
 }
+
+internal inline fun <T, R> List<T>.mapped(crossinline mapper: (T) -> R): List<R> =
+    GettingList(size = size) { mapper(get(it)) }
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt
index c63206eb..bf6ec7e9 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt
@@ -10,3 +10,14 @@ fun Any.hashString(): String =
 internal expect val KClass<*>.uniqueName: String?
 
 internal val Lifecycle.isDestroyed: Boolean get() = state == Lifecycle.State.DESTROYED
+
+internal fun <T : Any, C : Any> List<T>.keyed(configuration: (T) -> C): Map<Any, T> {
+    val numbers = HashMap<C, Int>()
+
+    return associateBy { item ->
+        val config = configuration(item)
+        val number = (numbers[config] ?: 0) + 1
+        numbers[config] = number
+        config to number
+    }
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
index 8fb53e2b..f3877233 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
@@ -91,7 +91,8 @@ fun <Ctx : GenericComponentContext<Ctx>, C : Any, T : Any, E : Any, N : NavState
  * The navigation state is not saved if `null` is returned.
  * @param restoreState a function that restores the navigation state from the provided [SerializableContainer].
  * If `null` is returned then [initialState] is used instead.
- * The restored navigation state must have the same amount of child configurations and in the same order.
+ * The restored navigation state must have the same amount of child configurations and in the same order,
+ * otherwise the behaviour is undefined.
  * The restored child [Statuses][ChildNavState.Status] can be any, e.g. a previously active child may become
  * destroyed, etc.
  * @param navTransformer a function that transforms the current navigation state to a new one using the provided
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
index 17b3addb..34eec122 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
@@ -1,6 +1,9 @@
 package com.arkivanov.decompose.router.children
 
 import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.DecomposeExperimentFlags
+import com.arkivanov.decompose.keyed
+import com.arkivanov.decompose.mapped
 import com.arkivanov.decompose.router.children.ChildItem.Created
 import com.arkivanov.decompose.router.children.ChildItem.Destroyed
 import com.arkivanov.decompose.router.children.ChildNavState.Status
@@ -29,14 +32,31 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
 
     val children: List<Child<C, T>>
         get() =
-            items.map { item ->
-                val instance = item.instance
-                if (instance != null) {
-                    Child.Created(configuration = item.configuration, instance = instance)
-                } else {
-                    Child.Destroyed(configuration = item.configuration)
-                }
+            if (DecomposeExperimentFlags.duplicateConfigurationsEnabled) {
+                getChildrenExperimental()
+            } else {
+                getChildrenDefault()
+            }
+
+    private fun getChildrenDefault(): List<Child<C, T>> =
+        items.map { item ->
+            val instance = item.instance
+            if (instance != null) {
+                Child.Created(configuration = item.configuration, instance = instance)
+            } else {
+                Child.Destroyed(configuration = item.configuration)
             }
+        }
+
+    private fun getChildrenExperimental(): List<Child<C, T>> =
+        items.keyed { it.configuration }.map { (key, item) ->
+            val instance = item.instance
+            if (instance != null) {
+                Child.Created(key = key, configuration = item.configuration, instance = instance)
+            } else {
+                Child.Destroyed(key = key, configuration = item.configuration)
+            }
+        }
 
     @Suppress("UNCHECKED_CAST")
     private val retainedInstance = retainedInstanceSupplier { RetainedInstance<C, T>() } as RetainedInstance<C, T>
@@ -64,6 +84,14 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
     }
 
     private fun restore(navState: N, savedStates: List<SerializableContainer?>) {
+        if (DecomposeExperimentFlags.duplicateConfigurationsEnabled) {
+            restoreExperimental(navState, savedStates)
+        } else {
+            restoreDefault(navState, savedStates)
+        }
+    }
+
+    private fun restoreDefault(navState: N, savedStates: List<SerializableContainer?>) {
         val retainedChildren = retainedInstance.items.associateByTo(HashMap(), Created<C, *>::configuration)
         retainedInstance.items.clear()
 
@@ -87,6 +115,35 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
         retainedChildren.values.forEach { it.instanceKeeperDispatcher.destroy() }
     }
 
+    private fun restoreExperimental(navState: N, savedStates: List<SerializableContainer?>) {
+        val retainedChildren = HashMap<Int, Created<C, T>>()
+        retainedInstance.items.forEachIndexed(retainedChildren::put)
+        val restoreRetainedChildren = navState.children.mapped { it.configuration } == retainedInstance.items.mapped { it.configuration }
+        retainedInstance.items.clear()
+
+        navState.children.zip(savedStates).forEachIndexed { index, (childNavState, savedState) ->
+            items +=
+                restoreItem(
+                    status = childNavState.status,
+                    getDestroyedItem = { Destroyed(configuration = childNavState.configuration, savedState = savedState) },
+                    getCreatedItem = {
+                        childItemFactory(
+                            configuration = childNavState.configuration,
+                            savedState = savedState,
+                            instanceKeeperDispatcher = retainedChildren
+                                .takeIf { restoreRetainedChildren }
+                                ?.remove(index)
+                                ?.instanceKeeperDispatcher,
+                        ).also {
+                            retainedInstance.items += it
+                        }
+                    }
+                )
+        }
+
+        retainedChildren.values.forEach { it.instanceKeeperDispatcher.destroy() }
+    }
+
     private inline fun restoreItem(
         status: Status,
         getDestroyedItem: () -> Destroyed<C>,
@@ -127,18 +184,26 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
     }
 
     private fun switch(newStates: List<ChildNavState<C>>) {
+        if (DecomposeExperimentFlags.duplicateConfigurationsEnabled) {
+            switchExperimental(newStates)
+        } else {
+            switchDefault(newStates)
+        }
+    }
+
+    private fun switchDefault(newStates: List<ChildNavState<C>>) {
         val newConfigurations = newStates.mapTo(HashSet(), ChildNavState<C>::configuration)
         check(newConfigurations.size == newStates.size) {
             "Configurations must be unique: ${newStates.map(ChildNavState<C>::configuration)}."
         }
 
         val oldItems = items.associateBy(ChildItem<C, *>::configuration)
-        val newItems = prepareNewItems(newStates = newStates, oldItems = oldItems)
-        destroyOldItems(newConfigurations = newConfigurations, oldItems = oldItems.values)
+        val newItems = prepareNewItemsDefault(newStates = newStates, oldItems = oldItems)
+        destroyOldItemsDefault(newConfigurations = newConfigurations, oldItems = oldItems.values)
         processNewItems(newItems = newItems)
     }
 
-    private fun prepareNewItems(
+    private fun prepareNewItemsDefault(
         newStates: List<ChildNavState<C>>,
         oldItems: Map<C, ChildItem<C, T>>,
     ): List<Pair<ChildItem<C, T>, Status>> {
@@ -182,7 +247,7 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
         return newItems
     }
 
-    private fun destroyOldItems(
+    private fun destroyOldItemsDefault(
         newConfigurations: Set<C>,
         oldItems: Collection<ChildItem<C, T>>,
     ) {
@@ -194,6 +259,70 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
         }
     }
 
+    private fun switchExperimental(newStates: List<ChildNavState<C>>) {
+        val newKeyedStates = newStates.keyed(ChildNavState<C>::configuration)
+        val oldKeyedItems = items.keyed(ChildItem<C, *>::configuration)
+        val newItems = prepareNewItemsExperimental(newStates = newKeyedStates, oldItems = oldKeyedItems)
+        destroyOldItemsExperimental(newKeys = newKeyedStates.keys, oldItems = oldKeyedItems)
+        processNewItems(newItems = newItems)
+    }
+
+    private fun prepareNewItemsExperimental(
+        newStates: Map<Any, ChildNavState<C>>,
+        oldItems: Map<Any, ChildItem<C, T>>,
+    ): List<Pair<ChildItem<C, T>, Status>> {
+        val newItems = ArrayList<Pair<ChildItem<C, T>, Status>>(newStates.size)
+
+        newStates.forEach { (key, state) ->
+            newItems +=
+                when (val child = oldItems[key]) {
+                    is Created -> child to state.status
+
+                    is Destroyed ->
+                        when (state.status) {
+                            Status.DESTROYED -> child to state.status
+
+                            Status.CREATED,
+                            Status.STARTED,
+                            Status.RESUMED ->
+                                Pair(
+                                    first = childItemFactory(configuration = state.configuration, savedState = child.savedState)
+                                        .apply { lifecycleRegistry.create() },
+                                    second = state.status,
+                                )
+                        }
+
+                    null ->
+                        when (state.status) {
+                            Status.DESTROYED -> Destroyed(configuration = state.configuration) to state.status
+
+                            Status.CREATED,
+                            Status.STARTED,
+                            Status.RESUMED ->
+                                Pair(
+                                    first = childItemFactory(configuration = state.configuration)
+                                        .apply { lifecycleRegistry.create() },
+                                    second = state.status,
+                                )
+                        }
+                }
+        }
+
+        return newItems
+    }
+
+    private fun destroyOldItemsExperimental(
+        newKeys: Set<Any>,
+        oldItems: Map<Any, ChildItem<C, T>>,
+    ) {
+        for ((key, item) in oldItems) {
+            val child = item as? Created ?: continue
+            if (key !in newKeys) {
+                child.destroy()
+            }
+        }
+    }
+
     private fun processNewItems(newItems: List<Pair<ChildItem<C, T>, Status>>) {
         items.clear()
         retainedInstance.items.clear()
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt
index 53c88f4c..fb757897 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt
@@ -1,6 +1,7 @@
 package com.arkivanov.decompose.router.stack
 
 import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.GettingList
 
 /**
@@ -15,15 +16,26 @@ data class ChildStack<out C : Any, out T : Any>(
 ) {
 
     /**
-     * Creates [ChildStack] with only one child with the specified [configuration] and [instance].
+     * Creates [ChildStack] with only one child with the specified [configuration], [instance] and [key].
      */
-    constructor(configuration: C, instance: T) : this(
+    @ExperimentalDecomposeApi
+    constructor(configuration: C, instance: T, key: Any) : this(
         active = Child.Created(
             configuration = configuration,
-            instance = instance
+            instance = instance,
+            key = key,
         ),
     )
 
+    /**
+     * Creates [ChildStack] with only one child with the specified [configuration] and [instance].
+     */
+    constructor(configuration: C, instance: T) : this(
+        configuration = configuration,
+        instance = instance,
+        key = configuration,
+    )
+
     /**
      * Returns the full stack of component configurations, ordered from tail to head.
      */
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt
index e3a1d5de..0fb41e77 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt
@@ -11,6 +11,9 @@ fun <C : Any> StackNavigator<C>.navigate(transformer: (stack: List<C>) -> List<C
  * Pushes the provided [configuration] at the top of the stack.
  *
  * Decompose will throw an exception if the provided [configuration] is already present in the stack.
+ * You can also try enabling the experimental
+ * [Duplicate Configurations][com.arkivanov.decompose.DecomposeExperimentFlags.duplicateConfigurationsEnabled] feature
+ * to avoid the error.
  *
  * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
  */
@@ -23,7 +26,9 @@ inline fun <C : Any> StackNavigator<C>.push(configuration: C, crossinline onComp
  * [configuration] is already on top of the stack.
  *
  * Decompose will throw an exception if the provided [configuration] is already present in the
- * back stack (not at the top of the stack).
+ * back stack (not at the top of the stack). You can also try enabling the experimental
+ * [Duplicate Configurations][com.arkivanov.decompose.DecomposeExperimentFlags.duplicateConfigurationsEnabled] feature
+ * to avoid the error.
  *
  * This can be useful when pushing a component on button click, to avoid pushing the same component
  * if the user clicks the same button quickly multiple times.
diff --git a/deps.versions.toml b/deps.versions.toml
index 5f8556f6..4b98aa43 100644
--- a/deps.versions.toml
+++ b/deps.versions.toml
@@ -1,6 +1,6 @@
 [versions]
 
-decompose = "3.2.0-alpha01"
+decompose = "3.1.0"
 kotlin = "2.0.0"
 essenty = "2.1.0"
 reaktive = "1.2.3"
diff --git a/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt b/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt
index 151ab089..01bdc114 100644
--- a/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt
+++ b/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt
@@ -109,14 +109,14 @@ class StackRouterView @JvmOverloads constructor(
         @Suppress("UNCHECKED_CAST")
         val currentChild = currentChild as ActiveChild<C, T>?
 
-        if (currentChild?.child?.configuration == activeChild.configuration) {
+        if (currentChild?.child?.key == activeChild.key) {
             return
         }
 
         val currentChildView = currentChild?.let { findChildView(it.key) }
 
-        if ((currentChildView != null) && stack.backStack.any { it.configuration == currentChild.child.configuration }) {
-            inactiveChildren[currentChild.key] = InactiveChild(currentChild.child.configuration, currentChildView.saveHierarchyState())
+        if ((currentChildView != null) && stack.backStack.any { it.key == currentChild.child.key }) {
+            inactiveChildren[currentChild.key] = InactiveChild(currentChild.child.key, currentChildView.saveHierarchyState())
         }
         currentChild?.lifecycle?.destroy()
 
@@ -126,7 +126,7 @@ class StackRouterView @JvmOverloads constructor(
 
         val newChildView = findNewChildView()
 
-        val activeChildKey = activeChild.configuration.hashString()
+        val activeChildKey = activeChild.key.hashString()
 
         newChildView.key = activeChildKey
 
@@ -148,7 +148,7 @@ class StackRouterView @JvmOverloads constructor(
         this.currentChild = ActiveChild(activeChildKey, activeChild, childViewLifecycle)
 
         inactiveChildren.values.removeAll { child ->
-            stack.backStack.none { it.configuration === child.configuration }
+            stack.backStack.none { it.key == child.key }
         }
     }
 
@@ -165,7 +165,7 @@ class StackRouterView @JvmOverloads constructor(
     )
 
     private class InactiveChild(
-        val configuration: Any,
+        val key: Any,
         val savedState: SparseArray<Parcelable>
     )
 
diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt
index 815b9bae..ad26a0b0 100644
--- a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt
+++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt
@@ -33,7 +33,7 @@ fun <C : Any, T : Any> Pages(
     modifier: Modifier = Modifier,
     scrollAnimation: PagesScrollAnimation = PagesScrollAnimation.Disabled,
     pager: Pager = defaultHorizontalPager(),
-    key: (Child<C, T>) -> Any = { it.configuration.hashString() },
+    key: (Child<C, T>) -> Any = { it.key.hashString() },
     pageContent: @Composable PagerScope.(index: Int, page: T) -> Unit,
 ) {
     val state by pages.subscribeAsState()
@@ -61,7 +61,7 @@ fun <C : Any, T : Any> Pages(
     modifier: Modifier = Modifier,
     scrollAnimation: PagesScrollAnimation = PagesScrollAnimation.Disabled,
     pager: Pager = defaultHorizontalPager(),
-    key: (Child<C, T>) -> Any = { it.configuration.hashString() },
+    key: (Child<C, T>) -> Any = { it.key.hashString() },
     pageContent: @Composable PagerScope.(index: Int, page: T) -> Unit,
 ) {
     val selectedIndex = pages.selectedIndex
@@ -95,7 +95,7 @@ fun <C : Any, T : Any> Pages(
     ) { pageIndex ->
         val item = pages.items[pageIndex]
 
-        val pageRef = remember(item.configuration) { Ref(item.instance) }
+        val pageRef = remember(item.key) { Ref(item.instance) }
         if (item.instance != null) {
             pageRef.value = item.instance
         }
diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt
index 81b8314e..d5d8015c 100644
--- a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt
+++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt
@@ -7,6 +7,7 @@ import androidx.compose.runtime.saveable.SaveableStateHolder
 import androidx.compose.runtime.saveable.rememberSaveableStateHolder
 import androidx.compose.ui.Modifier
 import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.extensions.compose.stack.animation.LocalStackAnimationProvider
 import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation
 import com.arkivanov.decompose.extensions.compose.stack.animation.emptyStackAnimation
@@ -15,6 +16,7 @@ import com.arkivanov.decompose.hashString
 import com.arkivanov.decompose.router.stack.ChildStack
 import com.arkivanov.decompose.value.Value
 
+@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 fun <C : Any, T : Any> Children(
     stack: ChildStack<C, T>,
@@ -24,13 +26,13 @@ fun <C : Any, T : Any> Children(
 ) {
     val holder = rememberSaveableStateHolder()
 
-    holder.retainStates(stack.getConfigurations())
+    holder.retainStates(stack.getKeys())
 
     val animationProvider = LocalStackAnimationProvider.current
     val anim = animation ?: remember(animationProvider, animationProvider::provide) ?: emptyStackAnimation()
 
     anim(stack = stack, modifier = modifier) { child ->
-        holder.SaveableStateProvider(child.configuration.hashString()) {
+        holder.SaveableStateProvider(child.key.hashString()) {
             content(child)
         }
     }
@@ -53,11 +55,12 @@ fun <C : Any, T : Any> Children(
     )
 }
 
-private fun ChildStack<*, *>.getConfigurations(): Set<String> =
-    items.mapTo(HashSet()) { it.configuration.hashString() }
+@OptIn(ExperimentalDecomposeApi::class)
+private fun ChildStack<*, *>.getKeys(): Set<String> =
+    items.mapTo(HashSet()) { it.key.hashString() }
 
 @Composable
-private fun SaveableStateHolder.retainStates(currentKeys: Set<Any>) {
+private fun SaveableStateHolder.retainStates(currentKeys: Set<String>) {
     val keys = remember(this) { Keys(currentKeys) }
 
     DisposableEffect(this, currentKeys) {
diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt
index b725fbb5..bba80774 100644
--- a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt
+++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt
@@ -10,8 +10,10 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.pointerInput
 import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.stack.ChildStack
 
+@OptIn(ExperimentalDecomposeApi::class)
 internal abstract class AbstractStackAnimation<C : Any, T : Any>(
     private val disableInputDuringAnimation: Boolean,
 ) : StackAnimation<C, T> {
@@ -28,22 +30,22 @@ internal abstract class AbstractStackAnimation<C : Any, T : Any>(
         var currentStack by remember { mutableStateOf(stack) }
         var items by remember { mutableStateOf(getAnimationItems(newStack = currentStack, oldStack = null)) }
 
-        if (stack.active.configuration != currentStack.active.configuration) {
+        if (stack.active.key != currentStack.active.key) {
             val oldStack = currentStack
             currentStack = stack
             items = getAnimationItems(newStack = currentStack, oldStack = oldStack)
         }
 
         Box(modifier = modifier) {
-            items.forEach { (configuration, item) ->
-                key(configuration) {
+            items.forEach { (key, item) ->
+                key(key) {
                     Child(
                         item = item,
                         onFinished = {
                             if (item.direction.isExit) {
-                                items -= configuration
+                                items -= key
                             } else {
-                                items += (configuration to item.copy(otherChild = null))
+                                items += (key to item.copy(otherChild = null))
                             }
                         },
                         content = content,
@@ -73,12 +75,12 @@ internal abstract class AbstractStackAnimation<C : Any, T : Any>(
         )
     }
 
-    private fun getAnimationItems(newStack: ChildStack<C, T>, oldStack: ChildStack<C, T>?): Map<C, AnimationItem<C, T>> =
+    private fun getAnimationItems(newStack: ChildStack<C, T>, oldStack: ChildStack<C, T>?): Map<Any, AnimationItem<C, T>> =
         when {
             oldStack == null ->
                 listOf(AnimationItem(child = newStack.active, direction = Direction.ENTER_FRONT, isInitial = true))
 
-            (newStack.size < oldStack.size) && (newStack.active.configuration in oldStack.backStack) ->
+            (newStack.size < oldStack.size) && (newStack.active.key in oldStack.backStack) ->
                 listOf(
                     AnimationItem(child = newStack.active, direction = Direction.ENTER_BACK, otherChild = oldStack.active),
                     AnimationItem(child = oldStack.active, direction = Direction.EXIT_FRONT, otherChild = newStack.active),
@@ -89,13 +91,13 @@ internal abstract class AbstractStackAnimation<C : Any, T : Any>(
                     AnimationItem(child = oldStack.active, direction = Direction.EXIT_BACK, otherChild = newStack.active),
                     AnimationItem(child = newStack.active, direction = Direction.ENTER_FRONT, otherChild = oldStack.active),
                 )
-        }.associateBy { it.child.configuration }
+        }.associateBy { it.child.key }
 
     private val ChildStack<*, *>.size: Int
         get() = items.size
 
-    private operator fun <C : Any> Iterable<Child<C, *>>.contains(config: C): Boolean =
-        any { it.configuration == config }
+    private operator fun <C : Any> Iterable<Child<C, *>>.contains(key: Any): Boolean =
+        any { it.key == key }
 
     protected data class AnimationItem<out C : Any, out T : Any>(
         val child: Child.Created<C, T>,
diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/MovableStackAnimation.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/MovableStackAnimation.kt
index f13db480..31626c19 100644
--- a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/MovableStackAnimation.kt
+++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/MovableStackAnimation.kt
@@ -5,12 +5,14 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.remember
 import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.ExperimentalDecomposeApi
 
 internal class MovableStackAnimation<C : Any, T : Any>(
     disableInputDuringAnimation: Boolean,
     private val selector: (child: Child.Created<C, T>, otherChild: Child.Created<C, T>, direction: Direction) -> StackAnimator?,
 ) : AbstractStackAnimation<C, T>(disableInputDuringAnimation = disableInputDuringAnimation) {
 
+    @OptIn(ExperimentalDecomposeApi::class)
     @Composable
     override fun Child(
         item: AnimationItem<C, T>,
@@ -18,7 +20,7 @@ internal class MovableStackAnimation<C : Any, T : Any>(
         content: @Composable (child: Child.Created<C, T>) -> Unit,
     ) {
         val animator =
-            remember(item.child.configuration, item.otherChild?.configuration, item.direction) {
+            remember(item.child.key, item.otherChild?.key, item.direction) {
                 if (item.otherChild == null) {
                     EmptyStackAnimator
                 } else {
diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimation.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimation.kt
index 80414233..affb60b7 100644
--- a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimation.kt
+++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimation.kt
@@ -4,19 +4,21 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.ExperimentalDecomposeApi
 
 internal class SimpleStackAnimation<C : Any, T : Any>(
     disableInputDuringAnimation: Boolean,
     private val selector: (Child.Created<C, T>) -> StackAnimator?,
 ) : AbstractStackAnimation<C, T>(disableInputDuringAnimation = disableInputDuringAnimation) {
 
+    @OptIn(ExperimentalDecomposeApi::class)
     @Composable
     override fun Child(
         item: AnimationItem<C, T>,
         onFinished: () -> Unit,
         content: @Composable (child: Child.Created<C, T>) -> Unit,
     ) {
-        val animator = remember(item.child.configuration) { selector(item.child) ?: EmptyStackAnimator }
+        val animator = remember(item.child.key) { selector(item.child) ?: EmptyStackAnimator }
 
         animator(
             direction = item.direction,
diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/predictiveback/PredictiveBackAnimation.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/predictiveback/PredictiveBackAnimation.kt
index b8e68e3a..90874b73 100644
--- a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/predictiveback/PredictiveBackAnimation.kt
+++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/predictiveback/PredictiveBackAnimation.kt
@@ -66,20 +66,20 @@ private class PredictiveBackAnimation<C : Any, T : Any>(
 
     @Composable
     override fun invoke(stack: ChildStack<C, T>, modifier: Modifier, content: @Composable (child: Child.Created<C, T>) -> Unit) {
-        val activeConfigurations = remember { HashSet<C>() }
-        val handler = rememberHandler(stack = stack, isGestureEnabled = { activeConfigurations.size == 1 })
+        val activeKeys = remember { HashSet<Any>() }
+        val handler = rememberHandler(stack = stack, isGestureEnabled = { activeKeys.size == 1 })
         val animationProvider = LocalStackAnimationProvider.current
         val anim = animation ?: remember(animationProvider, animationProvider::provide) ?: emptyStackAnimation()
 
         val childContent =
             remember(content) {
                 movableContentOf<Child.Created<C, T>> { child ->
-                    key(child.configuration) {
+                    key(child.key) {
                         content(child)
 
                         DisposableEffect(Unit) {
-                            activeConfigurations += child.configuration
-                            onDispose { activeConfigurations -= child.configuration }
+                            activeKeys += child.key
+                            onDispose { activeKeys -= child.key }
                         }
                     }
                 }


Test patch:
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
index 7081711e..40d59143 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
@@ -1,5 +1,6 @@
 package com.arkivanov.decompose.router.children
 
+import com.arkivanov.decompose.DecomposeExperimentFlags
 import com.arkivanov.decompose.DefaultComponentContext
 import com.arkivanov.decompose.router.TestInstance
 import com.arkivanov.decompose.router.children.ChildNavState.Status.CREATED
@@ -13,6 +14,7 @@ import com.arkivanov.essenty.lifecycle.destroy
 import com.arkivanov.essenty.lifecycle.doOnDestroy
 import kotlin.test.Test
 import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
 import kotlin.test.assertFalse
 
 @Suppress("TestFunctionName")
@@ -216,4 +218,100 @@ class ChildrenBasicTest : ChildrenTestBase() {
 
         children.assertChildren(1 to 1, 2 to 2)
     }
+
+    @Test
+    fun WHEN_add_duplicated_children_THEN_duplicated_children_added() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
+
+        navigate { it + listOf(1 by RESUMED, 2 by RESUMED, 3 by RESUMED) }
+
+        children.assertChildren(1 to null, 2 to 2, 3 to 3, 4 to 4, 1 to 1, 2 to 2, 3 to 3)
+    }
+
+    @Test
+    fun GIVEN_duplicated_children_WHEN_remove_duplicated_children_from_end_THEN_duplicated_children_removed() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+
+        val children by context.children(
+            initialState = stateOf(
+                1 by DESTROYED,
+                2 by CREATED,
+                3 by STARTED,
+                4 by RESUMED,
+                1 by RESUMED,
+                2 by RESUMED,
+                3 by RESUMED,
+            ),
+        )
+
+        val instances = children.map { it.instance }
+
+        navigate { it.dropLast(3) }
+
+        children.assertChildren(1 to null, 2 to 2, 3 to 3, 4 to 4)
+        assertEquals(instances.dropLast(3), children.map { it.instance })
+    }
+
+    @Test
+    fun GIVEN_duplicated_children_WHEN_remove_duplicated_children_from_end_THEN_duplicated_instances_removed_from_end() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+
+        val children by context.children(
+            initialState = stateOf(
+                1 by DESTROYED,
+                2 by CREATED,
+                3 by STARTED,
+                4 by RESUMED,
+                1 by RESUMED,
+                2 by RESUMED,
+                3 by RESUMED,
+            ),
+        )
+
+        val instances = children.instances()
+
+        navigate { it.dropLast(3) }
+
+        assertEquals(instances.dropLast(3), children.instances())
+    }
+
+    @Test
+    fun GIVEN_duplicated_children_WHEN_remove_duplicated_children_from_start_THEN_duplicated_children_removed() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+
+        val children by context.children(
+            initialState = stateOf(
+                1 by CREATED,
+                2 by STARTED,
+                3 by RESUMED,
+                1 by RESUMED,
+                2 by RESUMED,
+            ),
+        )
+
+        navigate { it.drop(2) }
+
+        children.assertChildren(3 to 3, 1 to 1, 2 to 2)
+    }
+    @Test
+    fun GIVEN_duplicated_children_WHEN_remove_duplicated_children_from_start_THEN_duplicated_instances_removed_from_end() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+
+        val children by context.children(
+            initialState = stateOf(
+                1 by CREATED,
+                2 by STARTED,
+                3 by RESUMED,
+                1 by RESUMED,
+                2 by RESUMED,
+            ),
+        )
+
+        val instances = children.instances()
+
+        navigate { it.drop(2) }
+
+        assertEquals(listOf(instances[2], instances[0], instances[1]), children.instances())
+    }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
index fe34fcea..20306a5e 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
@@ -1,5 +1,6 @@
 package com.arkivanov.decompose.router.children
 
+import com.arkivanov.decompose.DecomposeExperimentFlags
 import com.arkivanov.decompose.router.children.ChildNavState.Status.RESUMED
 import com.arkivanov.decompose.router.children.ChildNavState.Status.DESTROYED
 import com.arkivanov.decompose.router.children.ChildNavState.Status.CREATED
@@ -280,4 +281,48 @@ class ChildrenLifecycleTest : ChildrenTestBase() {
         assertEquals(Lifecycle.State.DESTROYED, component3.lifecycle.state)
         assertEquals(Lifecycle.State.DESTROYED, component4.lifecycle.state)
     }
+
+    @Test
+    fun GIVEN_first_and_last_children_duplicated_WHEN_last_child_removed_THEN_last_child_destroyed() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val children by context.children(initialState = stateOf(1 by CREATED, 2 by STARTED, 1 by RESUMED))
+        val child = children.last().requireInstance()
+
+        navigate { it.dropLast(1) }
+
+        assertEquals(Lifecycle.State.DESTROYED, child.lifecycle.state)
+    }
+
+    @Test
+    fun GIVEN_first_and_last_children_duplicated_first_child_created_WHEN_last_child_removed_THEN_first_child_created() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val children by context.children(initialState = stateOf(1 by CREATED, 2 by STARTED, 1 by RESUMED))
+        val child = children.first().requireInstance()
+
+        navigate { it.dropLast(1) }
+
+        assertEquals(Lifecycle.State.CREATED, child.lifecycle.state)
+    }
+
+    @Test
+    fun GIVEN_first_and_last_children_duplicated_WHEN_first_child_removed_THEN_last_child_destroyed() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val children by context.children(initialState = stateOf(1 by CREATED, 2 by STARTED, 1 by RESUMED))
+        val child = children.last().requireInstance()
+
+        navigate { it.drop(1) }
+
+        assertEquals(Lifecycle.State.DESTROYED, child.lifecycle.state)
+    }
+
+    @Test
+    fun GIVEN_first_and_last_children_duplicated_and_last_child_resumed_WHEN_first_child_removed_THEN_first_child_resumed() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val children by context.children(initialState = stateOf(1 by CREATED, 2 by STARTED, 1 by RESUMED))
+        val child = children.first().requireInstance()
+
+        navigate { it.drop(1) }
+
+        assertEquals(Lifecycle.State.RESUMED, child.lifecycle.state)
+    }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
index 1ca246db..1e3103f6 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
@@ -1,10 +1,11 @@
 package com.arkivanov.decompose.router.children
 
+import com.arkivanov.decompose.DecomposeExperimentFlags
 import com.arkivanov.decompose.DefaultComponentContext
 import com.arkivanov.decompose.router.TestInstance
-import com.arkivanov.decompose.router.children.ChildNavState.Status.RESUMED
-import com.arkivanov.decompose.router.children.ChildNavState.Status.DESTROYED
 import com.arkivanov.decompose.router.children.ChildNavState.Status.CREATED
+import com.arkivanov.decompose.router.children.ChildNavState.Status.DESTROYED
+import com.arkivanov.decompose.router.children.ChildNavState.Status.RESUMED
 import com.arkivanov.decompose.router.children.ChildNavState.Status.STARTED
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
 import com.arkivanov.decompose.value.getValue
@@ -22,7 +23,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_created_to_destroyed_THEN_instance_destroyed() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = children.getByConfig(config = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = children.getByConfig(2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         navigate { listOf(1 by DESTROYED, 2 by DESTROYED, 3 by STARTED, 4 by RESUMED) }
 
@@ -32,7 +33,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_started_to_destroyed_THEN_instance_destroyed() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = children.getByConfig(config = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = children.getByConfig(3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by DESTROYED, 4 by RESUMED) }
 
@@ -42,7 +43,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_started_to_created_THEN_instance_not_destroyed() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = children.getByConfig(config = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = children.getByConfig(3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by CREATED, 4 by RESUMED) }
 
@@ -52,7 +53,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_resumed_to_destroyed_THEN_instance_destroyed() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = children.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = children.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by DESTROYED) }
 
@@ -62,7 +63,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_resumed_to_created_THEN_instance_not_destroyed() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = children.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = children.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by CREATED) }
 
@@ -72,7 +73,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_resumed_to_started_THEN_instance_not_destroyed() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = children.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = children.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by STARTED) }
 
@@ -82,11 +83,11 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_created_to_destroyed_WHEN_child_switched_to_created_THEN_instance_not_retained() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val oldInstance = children.getByConfig(config = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val oldInstance = children.getByConfig(2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
         navigate { listOf(1 by DESTROYED, 2 by DESTROYED, 3 by STARTED, 4 by RESUMED) }
 
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED) }
-        val newInstance = children.getByConfig(config = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val newInstance = children.getByConfig(2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         assertNotSame(oldInstance, newInstance)
     }
@@ -94,11 +95,11 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_started_to_destroyed_WHEN_child_switched_to_started_THEN_instance_not_retained() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val oldInstance = children.getByConfig(config = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val oldInstance = children.getByConfig(3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by DESTROYED, 4 by RESUMED) }
 
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED) }
-        val newInstance = children.getByConfig(config = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val newInstance = children.getByConfig(3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         assertNotSame(oldInstance, newInstance)
     }
@@ -106,11 +107,11 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_resumed_to_destroyed_WHEN_child_switched_to_resumed_THEN_instance_not_retained() {
         val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val oldInstance = children.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val oldInstance = children.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by DESTROYED) }
 
         navigate { listOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED) }
-        val newInstance = children.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val newInstance = children.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         assertNotSame(oldInstance, newInstance)
     }
@@ -121,13 +122,13 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val oldInstance = oldChildren.getByConfig(config = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val oldInstance = oldChildren.getByConfig(2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, instanceKeeper = instanceKeeper)
         val newChildren by newContext.children()
-        val newInstance = newChildren.getByConfig(config = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val newInstance = newChildren.getByConfig(2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         assertSame(oldInstance, newInstance)
     }
@@ -138,7 +139,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = oldChildren.getByConfig(config = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = oldChildren.getByConfig(2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
@@ -154,13 +155,13 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val oldInstance = oldChildren.getByConfig(config = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val oldInstance = oldChildren.getByConfig(3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, instanceKeeper = instanceKeeper)
         val newChildren by newContext.children()
-        val newInstance = newChildren.getByConfig(config = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val newInstance = newChildren.getByConfig(3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         assertSame(oldInstance, newInstance)
     }
@@ -171,7 +172,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = oldChildren.getByConfig(config = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = oldChildren.getByConfig(3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
@@ -187,13 +188,13 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val oldInstance = oldChildren.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val oldInstance = oldChildren.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, instanceKeeper = instanceKeeper)
         val newChildren by newContext.children()
-        val newInstance = newChildren.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val newInstance = newChildren.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         assertSame(oldInstance, newInstance)
     }
@@ -204,7 +205,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = oldChildren.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = oldChildren.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
@@ -220,7 +221,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = oldChildren.getByConfig(config = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = oldChildren.getByConfig(2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
@@ -236,7 +237,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = oldChildren.getByConfig(config = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = oldChildren.getByConfig(3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
@@ -252,7 +253,7 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
         val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by CREATED, 3 by STARTED, 4 by RESUMED))
-        val instance = oldChildren.getByConfig(config = 4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val instance = oldChildren.getByConfig(4).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
@@ -261,4 +262,44 @@ class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
         assertTrue(instance.isDestroyed)
     }
+
+    @Test
+    fun WHEN_duplicated_children_recreated_THEN_instances_retained() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val oldStateKeeper = TestStateKeeperDispatcher()
+        val instanceKeeper = InstanceKeeperDispatcher()
+        val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
+        val oldChildren by oldContext.children(initialState = stateOf(1 by CREATED, 2 by STARTED, 1 by RESUMED))
+        val oldInstance1 = oldChildren.first().requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val oldInstance3 = oldChildren.last().requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+
+        val savedState = oldStateKeeper.save()
+        val newStateKeeper = TestStateKeeperDispatcher(savedState)
+        val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, instanceKeeper = instanceKeeper)
+        val newChildren by newContext.children()
+        val newInstance1 = newChildren.first().requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val newInstance3 = newChildren.last().requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+
+        assertSame(oldInstance1, newInstance1)
+        assertSame(oldInstance3, newInstance3)
+    }
+
+    @Test
+    fun WHEN_duplicated_children_recreated_THEN_instances_not_destroyed() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val oldStateKeeper = TestStateKeeperDispatcher()
+        val instanceKeeper = InstanceKeeperDispatcher()
+        val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
+        val oldChildren by oldContext.children(initialState = stateOf(1 by CREATED, 2 by STARTED, 1 by RESUMED))
+        val oldInstance1 = oldChildren.first().requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+        val oldInstance3 = oldChildren.last().requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
+
+        val savedState = oldStateKeeper.save()
+        val newStateKeeper = TestStateKeeperDispatcher(savedState)
+        val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, instanceKeeper = instanceKeeper)
+        newContext.children()
+
+        assertFalse(oldInstance1.isDestroyed)
+        assertFalse(oldInstance3.isDestroyed)
+    }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
index ed64c127..4adcca78 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
@@ -1,5 +1,6 @@
 package com.arkivanov.decompose.router.children
 
+import com.arkivanov.decompose.DecomposeExperimentFlags
 import com.arkivanov.decompose.DefaultComponentContext
 import com.arkivanov.decompose.consume
 import com.arkivanov.decompose.register
@@ -551,4 +552,42 @@ class ChildrenSavedStateTest : ChildrenTestBase() {
 
         assertEquals(31, restoredState2)
     }
+
+    @Test
+    fun GIVEN_first_and_last_children_duplicated_WHEN_recreated_THEN_states_restored() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val oldStateKeeper = TestStateKeeperDispatcher()
+        val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
+        val oldChildren by oldContext.children(initialState = stateOf(1 by CREATED, 2 by STARTED, 1 by RESUMED))
+        oldChildren.first().requireInstance().stateKeeper.register("key") { 10 }
+        oldChildren.last().requireInstance().stateKeeper.register("key") { 30 }
+
+        val savedState = oldStateKeeper.save()
+        val newStateKeeper = TestStateKeeperDispatcher(savedState)
+        val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
+        val newChildren by newContext.children()
+
+        val restoredState1 = newChildren.first().requireInstance().stateKeeper.consume<Int>("key")
+        val restoredState3 = newChildren.last().requireInstance().stateKeeper.consume<Int>("key")
+
+        assertEquals(10, restoredState1)
+        assertEquals(30, restoredState3)
+    }
+
+    @Test
+    fun GIVEN_first_and_last_children_duplicated_WHEN_children_recreated_THEN_states_restored() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val children by context.children(initialState = stateOf(1 by CREATED, 2 by STARTED, 1 by RESUMED))
+        children.first().requireInstance().stateKeeper.register("key") { 10 }
+        children.last().requireInstance().stateKeeper.register("key") { 30 }
+
+        navigate { listOf(1 by DESTROYED, 2 by STARTED, 1 by DESTROYED) }
+        navigate { listOf(1 by CREATED, 2 by STARTED, 1 by RESUMED) }
+
+        val restoredState1 = children.first().requireInstance().stateKeeper.consume<Int>("key")
+        val restoredState3 = children.last().requireInstance().stateKeeper.consume<Int>("key")
+
+        assertEquals(10, restoredState1)
+        assertEquals(30, restoredState3)
+    }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
index 882a99da..eb1742d3 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
@@ -2,6 +2,7 @@ package com.arkivanov.decompose.router.children
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.DecomposeExperimentFlags
 import com.arkivanov.decompose.DefaultComponentContext
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
 import com.arkivanov.decompose.value.Value
@@ -12,6 +13,7 @@ import com.arkivanov.essenty.lifecycle.resume
 import com.arkivanov.essenty.statekeeper.SerializableContainer
 import com.arkivanov.essenty.statekeeper.consumeRequired
 import kotlinx.serialization.Serializable
+import kotlin.test.AfterTest
 import kotlin.test.BeforeTest
 import kotlin.test.assertContentEquals
 
@@ -34,6 +36,11 @@ open class ChildrenTestBase {
         lifecycle.resume()
     }
 
+    @AfterTest
+    fun after() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = false
+    }
+
     protected fun ComponentContext.children(
         initialState: TestNavState = TestNavState(),
         saveState: (state: TestNavState) -> SerializableContainer? = { state ->
@@ -107,6 +114,9 @@ open class ChildrenTestBase {
     protected fun Child<*, Component>.requireInstance(): Component =
         requireNotNull(instance)
 
+    protected fun List<Child<Int, Component>>.instances(): List<Component?> =
+        map { it.instance }
+
     protected data class TestNavState(
         override val children: List<SimpleChildNavState<Int>> = emptyList(),
     ) : NavState<Int>
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
index 70fb296c..03912fbf 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
@@ -1,6 +1,7 @@
 package com.arkivanov.decompose.router.stack
 
 import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.DecomposeExperimentFlags
 import com.arkivanov.decompose.DefaultComponentContext
 import com.arkivanov.decompose.consume
 import com.arkivanov.decompose.register
@@ -16,6 +17,7 @@ import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import com.arkivanov.essenty.lifecycle.resume
 import kotlinx.serialization.Serializable
+import kotlin.test.AfterTest
 import kotlin.test.BeforeTest
 import kotlin.test.Test
 import kotlin.test.assertContentEquals
@@ -46,13 +48,18 @@ class ChildStackIntegrationTest {
         lifecycle.resume()
     }
 
+    @AfterTest
+    fun after() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = false
+    }
+
     @Test
     fun GIVEN_one_child_in_stack_WHEN_push_one_child_THEN_two_children_in_stack() {
         val stack by context.childStack(initialStack = listOf(Config(1)))
 
         navigation.push(Config(2))
 
-        stack.assertStack(1 to 1, 2 to 2)
+        stack.assertStack(1, 2)
     }
 
     @Test
@@ -63,7 +70,7 @@ class ChildStackIntegrationTest {
         navigation.push(Config(3))
         navigation.pop()
 
-        stack.assertStack(1 to 1, 2 to 2)
+        stack.assertStack(1, 2)
     }
 
     @Test
@@ -72,7 +79,7 @@ class ChildStackIntegrationTest {
 
         navigation.pop()
 
-        stack.assertStack(1 to 1, 2 to 2)
+        stack.assertStack(1, 2)
     }
 
     @Test
@@ -83,7 +90,7 @@ class ChildStackIntegrationTest {
         navigation.push(Config(3))
         backDispatcher.back()
 
-        stack.assertStack(1 to 1, 2 to 2)
+        stack.assertStack(1, 2)
     }
 
     @Test
@@ -92,7 +99,7 @@ class ChildStackIntegrationTest {
 
         backDispatcher.back()
 
-        stack.assertStack(1 to 1, 2 to 2)
+        stack.assertStack(1, 2)
     }
 
     @Test
@@ -101,7 +108,7 @@ class ChildStackIntegrationTest {
 
         navigation.replaceCurrent(Config(3))
 
-        stack.assertStack(1 to 1, 3 to 3)
+        stack.assertStack(1, 3)
     }
 
     @Test
@@ -110,7 +117,7 @@ class ChildStackIntegrationTest {
 
         navigation.navigate { it.reversed() }
 
-        stack.assertStack(3 to 3, 2 to 2, 1 to 1)
+        stack.assertStack(3, 2, 1)
     }
 
     @Test
@@ -121,7 +128,7 @@ class ChildStackIntegrationTest {
 
         navigation.navigate { it.reversed() }
 
-        stack.assertStack(3 to 3, 2 to 2, 1 to 1)
+        stack.assertStack(3, 2, 1)
     }
 
     @Test
@@ -142,8 +149,8 @@ class ChildStackIntegrationTest {
 
         backDispatcher.back()
 
-        stack.assertStack(1 to 1, 2 to 2)
-        stack.active.instance.stack.assertStack(1 to 1)
+        stack.assertStack(1, 2)
+        stack.active.instance.stack.assertStack(1)
     }
 
     @Test
@@ -165,7 +172,7 @@ class ChildStackIntegrationTest {
 
         backDispatcher.back()
 
-        stack.assertStack(1 to 1)
+        stack.assertStack(1)
     }
 
     @Test
@@ -186,7 +193,7 @@ class ChildStackIntegrationTest {
 
         backDispatcher.back()
 
-        stack.assertStack(1 to 1, 2 to 2)
+        stack.assertStack(1, 2)
     }
 
     @Test
@@ -207,7 +214,7 @@ class ChildStackIntegrationTest {
 
         backDispatcher.back()
 
-        stack.assertStack(1 to 1)
+        stack.assertStack(1)
     }
 
     @Test
@@ -221,7 +228,7 @@ class ChildStackIntegrationTest {
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
         val newStack by newContext.childStack()
 
-        newStack.assertStack(1 to 1, 2 to 2)
+        newStack.assertStack(1, 2)
     }
 
     @Test
@@ -235,7 +242,7 @@ class ChildStackIntegrationTest {
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
         val newStack by newContext.childStack(initialStack = listOf(Config(1)))
 
-        newStack.assertStack(1 to 1)
+        newStack.assertStack(1)
     }
 
     @Test
@@ -411,6 +418,28 @@ class ChildStackIntegrationTest {
         assertFalse(instance.isDestroyed)
     }
 
+    @Test
+    fun WHEN_push_duplicated_children_THEN_stack_contains_duplicated_children() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val stack by context.childStack(initialStack = listOf(Config(1), Config(2), Config(3)))
+
+        navigation.push(Config(1))
+        navigation.push(Config(2))
+
+        stack.assertStack(1, 2, 3, 1, 2)
+    }
+
+    @Test
+    fun GIVEN_stack_with_duplicated_children_WHEN_remove_duplicated_children_THEN_stack_contains_unique_children() {
+        DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
+        val stack by context.childStack(initialStack = listOf(Config(1), Config(2), Config(3), Config(1), Config(2)))
+
+        navigation.pop()
+        navigation.pop()
+
+        stack.assertStack(1, 2, 3)
+    }
+
     private fun ComponentContext.childStack(
         initialStack: List<Config> = emptyList(),
         persistent: Boolean = true,
@@ -426,8 +455,8 @@ class ChildStackIntegrationTest {
     private val ChildStack<Config, Component>.children: List<Pair<Int, Int>>
         get() = items.map { child -> child.configuration.id to child.instance.id }
 
-    private fun ChildStack<Config, Component>.assertStack(vararg children: Pair<Int, Int>) {
-        assertEquals(children.toList(), this.children)
+    private fun ChildStack<Config, Component>.assertStack(vararg children: Int) {
+        assertEquals(children.map { it to it }, this.children)
         assertEquals(Lifecycle.State.RESUMED, active.instance.lifecycle.state)
 
         assertEquals(
diff --git a/decompose/src/webTest/kotlin/com/arkivanov/decompose/router/stack/webhistory/DefaultWebHistoryControllerTest.kt b/decompose/src/webTest/kotlin/com/arkivanov/decompose/router/stack/webhistory/DefaultWebHistoryControllerTest.kt
index ebf8aced..2fdd695a 100644
--- a/decompose/src/webTest/kotlin/com/arkivanov/decompose/router/stack/webhistory/DefaultWebHistoryControllerTest.kt
+++ b/decompose/src/webTest/kotlin/com/arkivanov/decompose/router/stack/webhistory/DefaultWebHistoryControllerTest.kt
@@ -64,6 +64,21 @@ class DefaultWebHistoryControllerTest {
         assertStack(listOf("/0", "/1"))
     }
 
+    @Test
+    fun WHEN_router_push_same_config_THEN_url_pushed_to_history() {
+        if (isNodeJs()) {
+            return
+        }
+
+        val router = TestStackRouter(listOf(Config(0)))
+        attach(router)
+
+        router.push(Config(0))
+        window.runPendingOperations()
+
+        assertStack(listOf("/0", "/0"))
+    }
+
     @Test
     fun GIVEN_router_with_initial_stack_of_two_configs_WHEN_router_pop_THEN_history_changed_to_previous_page() {
         if (isNodeJs()) {
@@ -79,6 +94,21 @@ class DefaultWebHistoryControllerTest {
         history.assertStack(listOf("/0", "/1"), 0)
     }
 
+    @Test
+    fun GIVEN_router_with_initial_stack_of_two_same_configs_WHEN_router_pop_THEN_history_changed_to_previous_page() {
+        if (isNodeJs()) {
+            return
+        }
+
+        val router = TestStackRouter(listOf(Config(0), Config(0)))
+        attach(router)
+
+        router.pop()
+        window.runPendingOperations()
+
+        history.assertStack(listOf("/0", "/0"), 0)
+    }
+
     @Test
     fun GIVEN_router_push_WHEN_router_pop_THEN_history_changed_to_previous_page() {
         if (isNodeJs()) {
@@ -96,6 +126,23 @@ class DefaultWebHistoryControllerTest {
         assertStack(listOf("/0", "/1"), 0)
     }
 
+    @Test
+    fun GIVEN_router_push_same_config_WHEN_router_pop_THEN_history_changed_to_previous_page() {
+        if (isNodeJs()) {
+            return
+        }
+
+        val router = TestStackRouter(listOf(Config(0)))
+        attach(router)
+        router.push(Config(0))
+        window.runPendingOperations()
+
+        router.pop()
+        window.runPendingOperations()
+
+        assertStack(listOf("/0", "/0"), 0)
+    }
+
     @Test
     fun GIVEN_router_with_initial_stack_of_tree_configs_WHEN_router_pop_two_THEN_history_changed_to_previous_page() {
         if (isNodeJs()) {
diff --git a/extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt b/extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt
index 64fb2287..c39fa4ed 100644
--- a/extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt
+++ b/extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt
@@ -14,6 +14,8 @@ import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.FaultyDecomposeApi
 import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation
 import com.arkivanov.decompose.extensions.compose.stack.animation.fade
 import com.arkivanov.decompose.extensions.compose.stack.animation.plus
@@ -37,7 +39,7 @@ class ChildrenTest(
 
     @Test
     fun WHEN_active_child_and_no_back_stack_THEN_active_child_displayed() {
-        val state = mutableStateOf(routerState(activeConfig = Config.A))
+        val state = mutableStateOf(routerState(Config.A))
 
         setContent(state)
 
@@ -46,91 +48,169 @@ class ChildrenTest(
 
     @Test
     fun GIVEN_child_A_displayed_WHEN_push_child_B_THEN_child_B_displayed() {
-        val state = mutableStateOf(routerState(activeConfig = Config.A))
+        val state = mutableStateOf(routerState(Config.A))
         setContent(state)
 
-        state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+        state.setValueOnIdle(routerState(Config.A, Config.B))
 
         composeRule.onNodeWithText(text = "ChildB", substring = true).assertExists()
     }
 
+    @Test
+    fun GIVEN_child_A1_displayed_WHEN_push_child_A2_THEN_child_A2_displayed() {
+        val state = mutableStateOf(routerState("A1" to Config.A))
+        setContent(state)
+
+        state.setValueOnIdle(routerState("A1" to Config.A, "A2" to Config.A))
+
+        composeRule.onNodeWithText(text = "ChildA2", substring = true).assertExists()
+    }
+
     @Test
     fun GIVEN_child_B_displayed_and_child_A_in_back_stack_WHEN_pop_child_B_THEN_child_A_displayed() {
-        val state = mutableStateOf(routerState(activeConfig = Config.A))
+        val state = mutableStateOf(routerState(Config.A))
         setContent(state)
-        state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+        state.setValueOnIdle(routerState(Config.A, Config.B))
 
-        state.setValueOnIdle(routerState(activeConfig = Config.A))
+        state.setValueOnIdle(routerState(Config.A))
 
         composeRule.onNodeWithText(text = "ChildA", substring = true).assertExists()
     }
 
+    @Test
+    fun GIVEN_child_A2_displayed_and_child_A1_in_back_stack_WHEN_pop_child_A2_THEN_child_A1_displayed() {
+        val state = mutableStateOf(routerState("A1" to Config.A))
+        setContent(state)
+        state.setValueOnIdle(routerState("A1" to Config.A, "A2" to Config.A))
+
+
+        state.setValueOnIdle(routerState("A1" to Config.A))
+
+        composeRule.onNodeWithText(text = "ChildA1", substring = true).assertExists()
+    }
+
     @Test
     fun GIVEN_child_B_displayed_and_child_A_in_back_stack_WHEN_pop_child_B_THEN_state_restored_for_child_A() {
-        val state = mutableStateOf(routerState(activeConfig = Config.A))
+        val state = mutableStateOf(routerState(Config.A))
         setContent(state)
         composeRule.onNodeWithText(text = "ChildA=0").performClick()
-        state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+        state.setValueOnIdle(routerState(Config.A, Config.B))
 
-        state.setValueOnIdle(routerState(activeConfig = Config.A))
+        state.setValueOnIdle(routerState(Config.A))
 
         composeRule.onNodeWithText(text = "ChildA=1").assertExists()
     }
 
+    @Test
+    fun GIVEN_child_A2_displayed_and_child_A1_in_back_stack_WHEN_pop_child_A2_THEN_state_restored_for_child_A1() {
+        val state = mutableStateOf(routerState("A1" to Config.A))
+        setContent(state)
+        composeRule.onNodeWithText(text = "ChildA1=0").performClick()
+        state.setValueOnIdle(routerState("A1" to Config.A, "A2" to Config.A))
+
+        state.setValueOnIdle(routerState("A1" to Config.A))
+
+        composeRule.onNodeWithText(text = "ChildA1=1").assertExists()
+    }
+
     @Test
     fun GIVEN_child_B_displayed_and_child_A_in_back_stack_WHEN_pop_child_B_and_push_child_B_THEN_state_not_restored_for_child_B() {
-        val state = mutableStateOf(routerState(activeConfig = Config.A))
+        val state = mutableStateOf(routerState(Config.A))
         setContent(state)
 
-        state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+        state.setValueOnIdle(routerState(Config.A, Config.B))
         composeRule.onNodeWithText(text = "ChildB=0").performClick()
 
-        state.setValueOnIdle(routerState(activeConfig = Config.A))
-        state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+        state.setValueOnIdle(routerState(Config.A))
+        state.setValueOnIdle(routerState(Config.A, Config.B))
 
         composeRule.onNodeWithText(text = "ChildB=0").assertExists()
     }
 
+    @Test
+    fun GIVEN_child_A2_displayed_and_child_A1_in_back_stack_WHEN_pop_child_A2_and_push_child_A2_THEN_state_not_restored_for_child_A2() {
+        val state = mutableStateOf(routerState("A1" to Config.A))
+        setContent(state)
+
+        state.setValueOnIdle(routerState("A1" to Config.A, "A2" to Config.A))
+        composeRule.onNodeWithText(text = "ChildA2=0").performClick()
+
+        state.setValueOnIdle(routerState("A1" to Config.A))
+        state.setValueOnIdle(routerState("A1" to Config.A, "A2" to Config.A))
+
+        composeRule.onNodeWithText(text = "ChildA2=0").assertExists()
+    }
+
     @Test
     fun GIVEN_child_A_displayed_WHEN_push_child_B_THEN_child_A_disposed() {
-        val state = mutableStateOf(routerState(activeConfig = Config.A))
+        val state = mutableStateOf(routerState(Config.A))
         setContent(state)
 
-        state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+        state.setValueOnIdle(routerState(Config.A, Config.B))
 
         composeRule.onNodeWithText(text = "ChildA", substring = true).assertDoesNotExist()
     }
 
+    @Test
+    fun GIVEN_child_A1_displayed_WHEN_push_child_A2_THEN_child_A1_disposed() {
+        val state = mutableStateOf(routerState("A1" to Config.A))
+        setContent(state)
+
+        state.setValueOnIdle(routerState("A1" to Config.A, "A2" to Config.A))
+
+        composeRule.onNodeWithText(text = "ChildA1", substring = true).assertDoesNotExist()
+    }
+
     @Test
     fun GIVEN_child_B_displayed_and_child_A_in_back_stack_WHEN_pop_child_B_THEN_child_B_disposed() {
-        val state = mutableStateOf(routerState(activeConfig = Config.A))
+        val state = mutableStateOf(routerState(Config.A))
         setContent(state)
-        state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+        state.setValueOnIdle(routerState(Config.A, Config.B))
 
-        state.setValueOnIdle(routerState(activeConfig = Config.A))
+        state.setValueOnIdle(routerState(Config.A))
 
         composeRule.onNodeWithText(text = "ChildB", substring = true).assertDoesNotExist()
     }
 
+    @Test
+    fun GIVEN_child_A2_displayed_and_child_A1_in_back_stack_WHEN_pop_child_A2_THEN_child_A2_disposed() {
+        val state = mutableStateOf(routerState("A1" to Config.A))
+        setContent(state)
+        state.setValueOnIdle(routerState("A1" to Config.A, "A2" to Config.A))
+
+        state.setValueOnIdle(routerState("A1" to Config.A))
+
+        composeRule.onNodeWithText(text = "ChildA2", substring = true).assertDoesNotExist()
+    }
+
     private fun setContent(state: State<ChildStack<Config, Config>>) {
         composeRule.setContent {
             Children(stack = state.value, animation = animation) { child ->
-                when (child.configuration) {
-                    Config.A -> Child(name = "A")
-                    Config.B -> Child(name = "B")
-                }.let {}
+                Child(name = child.key.toString())
             }
         }
 
         composeRule.runOnIdle {}
     }
 
-    private fun routerState(activeConfig: Config, backstack: List<Config> = emptyList()): ChildStack<Config, Config> =
+    private fun routerState(vararg stack: Config): ChildStack<Config, Config> =
         ChildStack(
-            active = Child.Created(configuration = activeConfig, instance = activeConfig),
-            backStack = backstack.map { Child.Created(configuration = it, instance = it) },
+            active = stack.last().toChild(),
+            backStack = stack.dropLast(1).map { it.toChild() },
         )
 
+    private fun Config.toChild(): Child.Created<Config, Config> =
+        Child.Created(configuration = this, instance = this)
+
+    private fun routerState(vararg stack: Pair<Any, Config>): ChildStack<Config, Config> =
+        ChildStack(
+            active = stack.last().toChild(),
+            backStack = stack.dropLast(1).map { it.toChild() },
+        )
+
+    private fun Pair<Any, Config>.toChild(): Child.Created<Config, Config> =
+        Child.Created(configuration = second, instance = second, key = first)
+
     @Composable
     private fun Child(name: String) {
         var count by rememberSaveable { mutableStateOf(0) }
@@ -152,6 +232,7 @@ class ChildrenTest(
         fun parameters(): List<Array<out Any?>> =
             getParameters().map { arrayOf(it) }
 
+        @OptIn(FaultyDecomposeApi::class)
         private fun getParameters(): List<StackAnimation<Config, Config>?> =
             listOf(
                 null,