How to Use Kotlin 2.4.20 Collection Equality Functions
Learn how to use Kotlin 2.4.20βs new allEqual, allDistinct, allEqualBy, and allDistinctBy functions to validate collections, arrays, sequences, and object properties.
On this page
Kotlin 2.4.20, released on September 7, 2026, adds four experimental collection functions that remove a surprisingly common bit of boilerplate: checking whether every element is equal or whether every element is unique. The new allEqual(), allEqualBy(), allDistinct(), and allDistinctBy() functions work with collections, sequences, and arrays. This tutorial shows how they work, when to use each one, and what to watch for before putting them into production code.Β
Why these four functions were added
Before Kotlin 2.4.20, checking whether all values in a collection were identical or whether every value was unique often required writing a longer expression or converting the data into another structure. That worked, but the intent of the code was not always obvious at a glance. Kotlin 2.4.20 adds dedicated operations for both questions and also provides By variants when the comparison should use a property of each object. The functions use structural equality, so they follow the same equality rules used by other Kotlin collection operations.Β
The four functions split into two simple questions. allEqual() asks whether every element has the same value, while allDistinct() asks whether no two elements are equal. The allEqualBy() and allDistinctBy() versions perform the same checks after extracting a value from each element with a selector function. That distinction becomes useful when the collection contains objects rather than primitive values.
Enable the experimental API first
The new functions are experimental in Kotlin 2.4.20, so they are not enabled silently in a normal project. You must explicitly opt in with ExperimentalStdlibApi, either in the source code or through the compiler configuration. This matters because an experimental API can change before it becomes stable, so using it is a deliberate project decision rather than an ordinary library upgrade.
For a small test, put the opt-in annotation directly above the function that uses the new API.
import kotlin.ExperimentalStdlibApi
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val numbers = listOf(4, 4, 4)
println(numbers.allEqual())
}
Run the program with Kotlin 2.4.20. The result should be true because every element in the list has the value 4. If your project is still using an older Kotlin version, update the Kotlin version in its build configuration first; Kotlin's official documentation identifies 2.4.20 as the stable release published on September 7, 2026.Β
Use allEqual when every value should match
The simplest new function is allEqual(). It answers whether every element in the receiver contains the same value, making it useful for validation rules such as checking whether a batch of responses has the same status or whether a group of records shares one state. An empty collection also needs to be considered when designing such a rule, because βevery element has the same valueβ does not identify a value when there are no elements. For business logic, decide explicitly whether an empty input should pass or fail before relying on the result.
import kotlin.ExperimentalStdlibApi
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val statuses = listOf("ready", "ready", "ready")
val mixed = listOf("ready", "failed", "ready")
println(statuses.allEqual())
println(mixed.allEqual())
}
The first call returns true, while the second returns false. The useful part is not the shorter syntax by itself; the function name tells another developer exactly what the validation is checking. That makes the condition easier to review than a hand-built expression whose purpose must first be inferred from its implementation.
Use allDistinct when duplicates are the problem
allDistinct() answers the opposite kind of question: does every element occur as a unique value? This is useful when validating identifiers, checking whether a generated set contains duplicates, or verifying that a collection represents one record for each expected value. The function performs the uniqueness check directly instead of making you create a separate set merely to compare its size. Kotlin documents allDistinct() as the operation for checking that every value in a collection is unique.Β
import kotlin.ExperimentalStdlibApi
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val ids = listOf("A101", "A102", "A103")
val duplicateIds = listOf("A101", "A102", "A101")
println(ids.allDistinct())
println(duplicateIds.allDistinct())
}
The first list contains three different identifiers, so the result is true. The second list contains A101 twice, so the result is false. This is particularly readable in validation code because the function describes the requirement directly: all values must be distinct.
Use the By variants for objects
Real applications rarely contain only strings and integers. More often, you have objects with several fields and need to compare just one field, such as an account identifier, participant identifier, or response status. That is where allEqualBy() and allDistinctBy() become useful. Each accepts a selector function that tells Kotlin which property should be used for the comparison.Β
import kotlin.ExperimentalStdlibApi
data class Response(
val participantId: String,
val answer: String,
val responseDate: String
)
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val responses = listOf(
Response("P001", "Yes", "2026-07-21"),
Response("P002", "Maybe", "2026-07-21"),
Response("P003", "No", "2026-07-21")
)
println(responses.allDistinctBy { it.participantId })
println(responses.allEqualBy { it.responseDate })
}
The first check asks whether every participant identifier is unique, which is true for these three records. The second asks whether all responses were submitted on the same date, which is also true. Notice that the objects themselves are not being compared for equality; the selector chooses the exact property that matters to the validation rule. That is the main reason the By variants are more useful than simply calling allDistinct() on the objects.
Choose the function from the question you are asking
| Function | Question it answers | Typical use |
|---|---|---|
allEqual() | Do all values match? | Check one shared status or value |
allDistinct() | Are all values unique? | Detect duplicate identifiers |
allEqualBy() | Does one selected property match for every object? | Check a shared field |
allDistinctBy() | Is one selected property unique for every object? | Validate unique IDs |
The choice becomes straightforward once the requirement is stated in plain language. If the requirement says βeveryone must have the same value,β use an allEqual function. If it says βnobody can have the same value,β use an allDistinct function. Add By when the values you care about are properties inside larger objects rather than the objects themselves.
Use the functions with arrays and sequences too
Kotlin 2.4.20 does not restrict these operations to ordinary collections. The new functions are available for collections, sequences, and arrays, which means you can apply the same style of validation without first converting your data into another container. This is useful when existing code already works with a sequence or an array and there is no reason to change its representation just to perform the check.Β
import kotlin.ExperimentalStdlibApi
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val array = arrayOf("paid", "paid", "paid")
val sequence = sequenceOf(10, 20, 30)
println(array.allEqual())
println(sequence.allDistinct())
}
The array returns true because every status is paid. The sequence returns true because its three numbers are different. The important practical benefit is consistency: the same intent can be expressed directly on the data structure you already have rather than introducing a conversion solely for validation.
Remember that equality is still Kotlin equality
These functions do not introduce a new definition of equality. Kotlin's documentation says they compare elements using structural equality, just like other collection operations. For ordinary values such as strings and numbers, that behavior is usually exactly what you expect. For custom classes, however, the result depends on how equality is defined for those objects.Β
data class User(
val id: Int,
val name: String
)
class Session(
val id: Int
)
fun main() {
val users = listOf(
User(1, "A"),
User(1, "A")
)
val sessions = listOf(
Session(1),
Session(1)
)
}
The two User objects shown above have the same data because User is a data class with generated equality based on its properties. The two Session objects do not automatically gain the same value-based equality behavior merely because their id fields match. When your requirement is specifically about an identifier or another field, allEqualBy() or allDistinctBy() is often clearer because it states exactly which value defines the comparison.
Keep the experimental status visible in production code
The new collection functions are useful, but Kotlin 2.4.20 marks them as experimental. That means a project should not treat them as permanently fixed APIs yet. The explicit @OptIn(ExperimentalStdlibApi::class) annotation makes that decision visible to maintainers and prevents an experimental dependency from being adopted accidentally.Β
For a private application, the convenience may easily justify the opt-in. For a reusable library, think more carefully because your API choices can affect consumers who are using different Kotlin versions or who prefer to avoid experimental standard-library features. Kotlin's release documentation also recommends checking library compatibility when moving projects between Kotlin versions, particularly when dependencies are involved.Β
Test the edge cases before replacing old checks
Do not replace an existing validation expression simply because the new function is shorter. Test an empty input, a single-element input, repeated values, and objects whose selected properties differ even when the objects themselves look similar. Those cases reveal whether the mathematical meaning of the check matches the business rule you actually need. The function can be perfectly correct while the requirement around an empty collection is still wrong.
import kotlin.ExperimentalStdlibApi
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@OptIn(ExperimentalStdlibApi::class)
class CollectionChecksTest {
@Test
fun detectsDuplicateIds() {
val ids = listOf("A", "B", "A")
assertFalse(ids.allDistinct())
}
@Test
fun acceptsUniqueIds() {
val ids = listOf("A", "B", "C")
assertTrue(ids.allDistinct())
}
}
These tests make the intended behavior executable rather than leaving it in a comment. If the experimental functions change before becoming stable, the tests also give you a small, focused place to detect a compatibility problem. That is a better upgrade strategy than discovering a behavior change through a production validation failure.
Where these functions fit in a Kotlin project
Kotlin 2.4.20 contains much more than these collection checks, including coroutine stack trace recovery, Kotlin/Native improvements, Kotlin/JS browser-testing support, Kotlin/Wasm changes, Gradle 9.7 support, and new compiler tooling. The four equality and uniqueness functions are comparatively small additions, but they solve a specific problem cleanly: expressing collection-wide validation without making readers reconstruct the intent from implementation details.
If your project has repeated checks for duplicate values or shared properties, these APIs are worth testing after upgrading to Kotlin 2.4.20. Keep the experimental opt-in deliberate, write tests around empty and duplicate inputs, and prefer the By versions when the rule concerns a field inside an object. That gives you the readability benefit now while keeping the upgrade decision easy to revisit when the APIs eventually move beyond their experimental stage.
Written by

