JavaRush /Courses /Kotlin SELF /Tuples: Pair and Triple

Tuples: Pair and Triple

Kotlin SELF
Level 10, Lesson 1
Available

1. How do you return multiple values from a function?

If you are just starting to program, it is very easy to think: “Well, if I need to return two values, I will just... return two values.” Alas, most languages (including Kotlin) usually require a function to return exactly one value.

For this case, programmers have a hack: return not “two values,” but one container that holds two values inside. And this is exactly where Pair and Triple show up in Kotlin.

Imagine a box with a label: “Two items inside.” That is Pair. And a box “Three items inside” is Triple. Kotlin provides such boxes out of the box (sorry for the pun), so you do not reinvent the wheel and do not glue data into a string like "10,20".

Pair: a container of two values

Pair is a standard Kotlin type that stores exactly two related values. Related is key: values that logically go together. Not “cutlets and space rockets,” but something like “name and age,” “X coordinate and Y coordinate,” “status code and message text.”

It is important that Pair does not impose meaning on the components. It has no fields x and y, no name and age. There are only the honest and slightly boring first and second. Meaning comes from the context and from the variable names around it.

Create a Pair via Pair(a, b) and read it via first/second

The most straightforward way is to call the constructor Pair(...).

fun main() {
    val point = Pair(10, 20)

    println(point.first)   // 10
    println(point.second)  // 20
}

Here point is a “point,” so we have agreed that first is x, and second is y. Kotlin does not know that. Kotlin just stores “the first” and “the second.” The agreement is on your conscience.

A more realistic example: “value + explanation”

Sometimes it is useful to keep a number and a short text next to it—for example, “calculation result + a comment about what it even was.”

fun main() {
    val result = Pair(200, "OK")

    println("code=${result.first}")     // code=200
    println("message=${result.second}") // message=OK
}

Note: reading result.first does not say much about what is stored there. That is why we usually either give the variable a more specific name, or (in the next lecture) unpack it via destructuring. But today we honestly practice reading values via first/second.

3. Triple: a container of three values

Triple is the twin brother of Pair, only it contains three values inside. This is handy when there are truly three pieces of data and they form one meaningful set. A classic example is RGB: red, green, blue.

As with Pair, the component names are boring but obvious: first, second, third. Kotlin is as if saying: “I only store. Meaning is your job.”

Create a Triple and read it via first/second/third

fun main() {
    val rgb = Triple(255, 128, 0)

    println("r=${rgb.first}")   // r=255
    println("g=${rgb.second}")  // g=128
    println("b=${rgb.third}")   // b=0
}

Cheat sheet: how to read components

When your eyes get tired of first/second/third, it helps to keep a simple cheat sheet.

Type How many values it stores How to read components
Pair
2
first, second
Triple
3
first, second, third

4. Convenient creation: to and type inference

Now there will be a small piece of “Kotlin magic,” but do not be afraid: the magic here is benign.

Besides Pair(a, b) there is a super-popular notation:

val range = 1 to 10

It creates Pair(1, 10).

This form works because to is a function that Kotlin allows you to call in infix form: when the function name is between two values. Kotlin describes the rules for infix calls (the function must have one parameter and a few other requirements) in separate documentation about infix notation.

a to b is a pair, not “special syntax”

It is important to understand: to is not “a language feature for pairs,” but a regular function (it is just convenient to use it in infix form). Therefore, a to b is exactly creating Pair(a, b).

Important: 1 to 10 is not the range 1..10

Very important: 1 to 10 is not the range 1..10. It is just a pair of values where first = 1, second = 10.

fun main() {
    val fromTo = 1 to 10

    println("from=${fromTo.first}") // from=1
    println("to=${fromTo.second}")  // to=10
}

Make variable names “speaking”

The readability of Pair almost always rests on variable names. If you name a variable well, the code becomes noticeably friendlier.

fun main() {
    val minMax = 3 to 12

    println("min=${minMax.first}") // min=3
    println("max=${minMax.second}") // max=12
}

The notation a to b is so widespread that Kotlin directly uses it in documentation examples when you need to “combine pairs.”

What types are stored inside Pair/Triple

Now there will be an important point: Pair and Triple can store anything. Want Int and String? Sure. Want String and Boolean? That works too. Kotlin often infers the types by itself, as we are already used to with variables.

That is, if you write val p = 10 to 20, Kotlin understands that it is Pair<Int, Int>. But today we are not going into the details—that will be much later. For now, it is enough to understand the practice: the types inside the pair match what you put there.

Pair of different types

fun main() {
    val user = Pair("Ada", 36)

    println(user.first)   // Ada
    println(user.second)  // 36
}

Here first is a string, second is a number. Kotlin handles this perfectly.

Triple only if it is one meaning, not “a bag of everything”

Just remember: Triple is not a bag of “what else can we cram in.” If the three values do not form a single idea, later you will look at it yourself as a riddle.

fun main() {
    val movie = Triple("Matrix", 1999, true)

    println(movie.first)  // Matrix
    println(movie.second) // 1999
    println(movie.third)  // true
}

Does it work? Yes. Is it readable? With a stretch. Because true is “what,” exactly? “Watched”? “Liked”? “The movie exists”? That is why Pair/Triple should be used carefully.

5. Application: a “rough expenses draft” in the console

To avoid leaving Pair and Triple in the world of abstract points and colors, we will start developing a small educational console app. Let it be a super-minimal “rough expenses tracker”: the user enters a category and an amount, and we print a clear confirmation.

Today our task is simple: learn to store one expense record as a bundle “category + amount.” For this, Pair fits perfectly: two values, one meaning.

Create an “expense” as a Pair

fun main() {
    val expense = "food" to 250

    println("Category: ${expense.first}") // Category: food
    println("Amount: ${expense.second}")  // Amount: 250
}

Yes, food and 250 could have been stored in two variables. But the idea is this: sometimes it is convenient for us to pass this further as one object: “here is the entire expense.”

Read data and assemble a Pair

We already know readln() and toIntOrNull(). Let us not complicate it: assume the user enters the category on the first line and the amount on the second.

fun main() {
    print("Enter category: ")
    val category = readln().trim()

    print("Enter amount: ")
    val amountText = readln().trim()
    val amount = amountText.toInt()

    val expense = category to amount
    println("Saved: ${expense.first} -> ${expense.second}") // example: Saved: food -> 250
}

This is not yet “real” expense tracking, but as a learning step it is excellent: we saw how Pair becomes a small “package” for related information.

When Pair/Triple are appropriate, and when it is better to stop

Pair and Triple are like duct tape. Useful around the house, but if you built a spaceship out of duct tape alone, you probably missed something in the design.

Usually Pair/Triple are appropriate when the values are truly small, there are few of them, and you quickly understand from the context what first and second mean. For example, x to y for a coordinate, min to max for bounds, code to message for a result.

But if you catch yourself constantly thinking “so what is first?” and start writing comments like // first = category, second = amount, that is a signal: you might need a more “named” result. But I will tell you how to do that properly about 10 levels later.

7. Typical mistakes when working with Pair and Triple

Mistake #1: mixing up the component order (first/second).
The most common issue is confusing what is in first and what is in second. Especially when in different parts of the code you create similar pairs, but “agreed differently”: somewhere (min, max), and somewhere (max, min). This is not a compiler error—it is a meaning error. Discipline saves you: fix the order and keep it consistent everywhere, and give variables names that hint at that order (minMax, fromTo, xToY).

Mistake #2: using Pair for unrelated data “because you can.”
Technically, you can put anything into a Pair. But if two values do not form a single meaning, reading turns into a puzzle. A week later you will open the code and guess why val x = pair.first, and what x even is. Pair is not a “to-go box” for random leftovers.

Mistake #3: hoping that first means “the main one.”
Sometimes beginners think that first is the “main field,” and second is “additional.” Kotlin does not think so. There is no main and secondary there, only order. If the meaning requires “mainness,” you need to express it with variable names and code logic, not hope for the magic of the word first.

Mistake #4: Triple as “a bag of everything.”
Triple easily turns into mini-chaos: “and put the name there, and the age, and a flag, and... oops, there is no fourth slot.” If you already feel that Triple does not fit the real meaning, it is not a “bad Triple,” it is a good signal that the data model is asking for a more normal form. For now, just remember this signal—we will return to it in the future when we have our own types.

Mistake #5: long chains of .first.first....
When you start seeing constructions like something.first.second, it means that somewhere you glued a Pair inside a Pair and already built yourself a maze. In such a situation, it is better to stop and spread intermediate values into variables with normal names. Even if it is two lines longer, it will be cheaper for your brain than decoding “a nesting doll of first’s.”

1
Task
Kotlin SELF, level 10, lesson 1
Locked
Coordinates on the map
Coordinates on the map
1
Task
Kotlin SELF, level 10, lesson 1
Locked
Route bounds
Route bounds
1
Task
Kotlin SELF, level 10, lesson 1
Locked
Dividing the loot
Dividing the loot
1
Task
Kotlin SELF, level 10, lesson 1
Locked
Rectangle Passport
Rectangle Passport
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION