Blog post

Pairs and triples in Kotlin (and why you shouldn't use them)

Illustration: Pairs and triples in Kotlin (and why you shouldn't use them)
Information

This article was first published in December 2022 and was updated in December 2024.

Kotlin’s sugar coating is great, but too much sugar can seriously hurt your health. In this blog post, we’ll discuss just that: why pairs and triples in Kotlin are an anti-pattern, and why you shouldn’t use them.

Introduction to pairs and triples

In Kotlin, pairs and triples are handy tools for bundling together two or three values, respectively. These data structures are helpful when you need to return multiple values from a function or combine several values without the overhead of creating a formal data class. For instance, if you want to return both a user’s name and age from a function, a pair can do the job neatly. Similarly, a triple can handle three values, like a user’s first name, last name, and age.

Pairs and triples are part of the Kotlin standard library, making them readily available and widely used in Kotlin programming. They offer a quick way to group multiple values, but as we’ll see, this convenience comes with its own set of drawbacks.

Using pairs and triples

Let’s say you have a form to collect the first and last name of a user, and you need to pass them to another screen. A convenient way would be to create a Pair:

val user : Pair<String, String> = Pair("John", "Doe")

What if you want to collect and pass the user’s age as well? You could use a Triple instead of a Pair:

val user : Triple<String, String, Int> = Triple("John", "Doe", 20)

Simple, right? This is where the convenience ends, for several reasons. First, if you’re passing this to another screen, it’s easy to get confused about what these properties mean. Take a look at the following example:

if(user.third > 21) {
  setEligible(user.first, user.second)
}

If you were to see this chunk of code without previous knowledge of how the Triple was created, you could easily be confused with its meaning. What is user.third and why should it be over 21? What are user.first and user.second, and why are they parameters for the setEligible() function? Pairs and triples are easy to write, but they’re often confusing to read and later interpret. This is always a red flag that something should be changed in the code.

This is also where your expandability possibilities end — there’s no Quadruple or anything of that sort. If you want to add another property, you’d have to create your own data class. Or if you’re too lazy, you could go with a more radical solution, such as Triple<Pair, Int, String> (where the pair contains the first and last name), which would make things even more confusing:

if(user.second > 21) {
  setEligible(user.first.first, user.first.second)
}

So if pairs and triples are problematic, why do we use them?

Pairs and triples under the hood

We create pairs and triples because we want to avoid creating our own data classes. But let’s dig into the Tuples.kt class, which provides the implementation of Pair and Triple, to see what they really are:

public data class Pair<out A, out B>(
    public val first: A,
    public val second: B
)

(...)

public data class Triple<out A, out B, out C>(
    public val first: A,
    public val second: B,
    public val third: C
)

We avoid writing data classes and instead write… data classes?

Pair is just a data class

Limitations of pairs and triples

While pairs and triples can provide a quick way to group two or three values together, they have notable limitations, outlined below.

  1. Limited capacity

Pairs can only hold two values, and triples can hold up to three. If you need to handle more than three values, there’s no built-in support for quadruples or larger groups, which means you’ll need to design your own data structures or resort to workarounds.

  1. Lack of named components

Elements in pairs and triples are accessed through generic property names like first, second, and third. This can lead to code that is less intuitive and harder to understand. For instance, user.third gives no immediate indication of what third represents without additional context.

  1. Not suitable for complex data

Pairs and triples are simple containers without any built-in support for adding methods or additional functionality. This limits their flexibility, making them less ideal for more complex or extensible data structures.

Overall, while pairs and triples are useful for quick, ad hoc grouping of values, their lack of scalability, readability, and extensibility can lead to maintenance challenges in larger codebases.

Data classes as a superior alternative

To address the limitations of pairs and triples, Kotlin offers data classes, a robust and flexible way to define simple or complex data structures. Some of the features of data classes are outlined below.

  1. Named components

Data classes allow you to name properties, making your code more readable. For example:

data class Person(val firstName: String, val lastName: String, val age: Int)

Accessing properties with meaningful names is much clearer:

val user = Person("John", "Doe", 20)
if (user.age > 21) {
   setEligible(user.firstName, user.lastName)
}
  1. Extensibility

Data classes enable you to add methods and additional functionality, making them suitable for representing complex data structures and business logic.

  1. Maintainability

With their clear structure and named properties, data classes improve code maintainability, reducing ambiguity and potential errors.

While creating data classes may require a bit more upfront effort, the benefits in terms of readability, scalability, and maintainability make them a superior choice over pairs and triples for most use cases.

Conclusion

Pair and Triple are just data classes that are (arguably) more convenient than making our own data class, but there’s not much sense in using them. Although there’s nothing wrong with how Pair and Triple work, they violate the O in SOLID, in that they’re not open for extension. Additionally, they confuse code reviewers, and they could possibly even confuse future-you. Yes, making a completely new data class does take more time, but it contributes to improved code readability.

FAQ

Here are a few frequently asked questions about pairs and triples.

What are pairs and triples in Kotlin? Pairs and triples are Kotlin data structures used to group two or three values together, respectively.
Why are pairs and triples considered an anti-pattern? They lack named components and scalability, making code less readable and more difficult to maintain.
What are the main limitations of pairs and triples? Their limited capacity (no more than two or three values) and lack of extensibility make them unsuitable for complex data structures.
How do data classes improve code maintainability? Data classes provide named properties, making code more readable and enabling additional functionality for better structure.
When should I use data classes instead of pairs or triples? Use data classes when representing complex or extensible data structures or whenever readability and maintainability are priorities.

Explore related topics

Free trial Ready to get started?
Free trial