If you have a few years of experience with the Kotlin language and server-side development, and you’re interested in sharing that experience with the community, have a look at our **Contribution Guidelines**.

# Multiple Variables “let” in Kotlin

Last modified: August 6, 2022

## 1. Overview

In Kotlin, *let()* is a pretty convenient scope function. It allows us to transform the given variable to a value in another type.

In this tutorial, we’ll explore how to apply *let*-like operations on multiple variables.

## 2. Introduction to the Problem

First of all, let’s look at a simple *let* example:

```
val str:String? = "hello"
val lengthReport = str?.let{"The length of the string [$it] is: ${it.length}"}
println(lengthReport)
//will print: The length of the string [hello] is: 5
```

In the example above, the *let()* function produces the length report of the given string. The code is pretty straightforward. However, it’s worth mentioning that the *str* variable is a nullable String type (*String?*). Further,** we want to call the let() function only if the str variable is not null. Therefore, we’ve used a null-safe let() call**:

*str?.let{ … }*. This is also a common technique when we handle nullable types.

Sometimes, we’d like to apply the null-safe *let-*like operation to more than one variable. But **the standard let() function can only handle a single variable.**

In this tutorial, we’ll first look at the “two variables’ *let*” case. Then we’ll see if we can build a function to execute the *let*-like operation on even more variables.

For simplicity, we’ll use unit-test assertions to verify if our functions work as expected.

Next, let’s see them in action.

## 3. Null-Safe *let* on Two Variables

Now, we’ll address several approaches to achieving null-safe *let* calls on two variables.

### 3.1. Nesting Two *let* Calls

The most straightforward way to make null-safe *let()* handle two nullable variables would be to write two *let()* calls. Next, let’s understand it with an example:

```
val theName: String? = "Kai"
val theNumber: Int? = 7
val result = theName?.let { name ->
theNumber?.let { num -> "Hi $name, $num squared is ${num * num}" }
}
assertThat(result).isEqualTo("Hi Kai, 7 squared is 49")
```

As the code above shows, we have two nested null-safe *let()* calls. If we run the test, it passes. So, it works as expected.

However, the nested structure isn’t easy to read. So, next, let’s see if we can find a better way to achieve it.

### 3.2. Creating Our Own *let2()* Function

A usual way to save some typing and make the code easier to understand is wrapping the logic in a function or method.

Now, let’s create a function to simulate *let()* so that we can execute lambda expressions on two variables:

```
inline fun <T1 : Any, T2 : Any, R : Any> let2(p1: T1?, p2: T2?, block: (T1, T2) -> R?): R? {
return if (p1 != null && p2 != null) block(p1, p2) else null
}
```

Next, let’s walk through the function implementation quickly and understand what it does.

The function *let2()* has three parameters: *p1, p2,* and *block. **p1* and *p2* are the two nullable parameters. *block* is a function that accepts two not-null arguments and returns a nullable *R* instance.

As we can see in the code above, *let2()* is a generic function, so the two parameters’ (*p1* and *p2*) types can be different.** The function block decides the return type of the let2() function**.

We execute the *block(p1, p2)* function only if both *p1* and *p2* are not null. Otherwise, *let2()* will return *null*.

In Kotlin, if the last parameter of a function is a function, we can pass a lambda expression to it and put it outside of * (…)*. For example, we can call our *let2()* in this way:

`let2(v1, v2) { a, b -> ... (lambda) }`

Moreover**, we declare the let2() function as an inline function to gain better performance.**

Next, let’s create a test to see if *let2()* works as expected. First, let’s declare a nullable *Int* type (*Int?*) to hold the *null* value so that Kotlin knows the concrete type of the *null* value:

`val nullNum: Int? = null`

Then, let’s pass a string and an integer to the message to test the *let2()* function:

```
assertThat(let2("Kai", 7) { name, num -> "Hi $name, $num squared is ${num * num}" }).isEqualTo("Hi Kai, 7 squared is 49")
assertThat(let2(nullNum, 7) { name, num -> "Hi $name, $num squared is ${num * num}" }).isNull()
assertThat(let2(7, nullNum) { name, num -> "Hi $name, $num squared is ${num * num}" }).isNull()
assertThat(let2(nullNum, nullNum) { name, num -> "Hi $name, $num squared is ${num * num}" }).isNull()
```

If we execute the test, it passes. So our *let2()* function can perform the null-safe *let*-like operation on two nullable variables.

Next, let’s see if we can perform *let()* on more than two variables.

## 4. Null-Safe *let* on Multiple Variables

For simplicity, we’ll address how to execute the *let-*like operation on three variables in this section.

### 4.1. Extending the *let2() *Function

As the *let2()* function works as expected, we can easily extend it to accept three variables. So it won’t be a challenge for us:

```
inline fun <T1 : Any, T2 : Any, T3 : Any, R : Any> let3(p1: T1?, p2: T2?, p3: T3?, block: (T1, T2, T3) -> R?): R? {
return if (p1 != null && p2 != null && p3 != null) block(p1, p2, p3) else null
}
```

Basically, we just extend *let2()* to *let3()* by adding a new parameter *p3* to the function.

Next, let’s test our *let3()* function. This time, we’ll use three nullable integers (*Int?*) variables for simplicity:

```
assertThat(let3(5, 6, 7) { n1, n2, n3 -> "$n1 + $n2 + $n3 is ${n1 + n2 + n3}" }).isEqualTo("5 + 6 + 7 is 18")
assertThat(let3(nullNum, 7, 6) { n1, n2, n3 -> "$n1 + $n2 + $n3 is ${n1 + n2 + n3}" }).isNull()
assertThat(let3(nullNum, nullNum, 6) { n1, n2, n3 -> "$n1 + $n2 + $n3 is ${n1 + n2 + n3}" }).isNull()
assertThat(let3(nullNum, nullNum, nullNum) { n1, n2, n3 -> "$n1 + $n2 + $n3 is ${n1 + n2 + n3}" }).isNull()
```

Unsurprisingly, the test passes if we execute it. So the *let3()* function works. However, if we review the functions we’ve created for both *let2()* and *let3(),* the number of the parameters is always fixed. Of course, if it’s required, we could still create *let4(), let5()*, and so on to support even more variables. But it would be good to have one single function to handle a variable number of parameters.

So next, let’s see how to achieve that.

### 4.2. Handling a Dynamic Number of Variables

**In Kotlin, the vararg parameter allows us to pass a dynamic number of arguments to a function**. Therefore, we can create a new

*inline*function with a

*vararg*parameter:

```
inline fun <T : Any, R : Any> letIfAllNotNull(vararg arguments: T?, block: (List<T>) -> R): R? {
return if (arguments.all { it != null }) {
block(arguments.filterNotNull())
} else null
}
```

As we can see in the *letIfAllNotNull()* function above, the *vararg* parameter *arguments* can have various number of parameters. The *block* function now accepts a list of elements with the not-null *T* type.

Similar to the *let2()* and the *let3()* functions, we first check if all parameters are not-null values using *arguments.all { it != null }*. Then, *arguments.filterNotNull()* converts the nullable *vararg* parameter (array) into a **List ****of not-null values.**

Next, let’s create a test to see how to call this function and verify if it works as expected:

```
assertThat(letIfAllNotNull(5, 6, 7, 8) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isEqualTo("5 + 6 + 7 + 8 is 26")
assertThat(letIfAllNotNull(5, 6, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isEqualTo("5 + 6 + 7 is 18")
assertThat(letIfAllNotNull(5, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isEqualTo("5 + 7 is 12")
assertThat(letIfAllNotNull(nullNum, 7, 6) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isNull()
assertThat(letIfAllNotNull(nullNum, null, 6) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isNull()
assertThat(letIfAllNotNull(nullNum, nullNum, nullNum) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isNull()
```

As we’ve seen in the test code, since the *block()* function accepts a list object as the parameter **when we write the lambda expression, the it variable is a list containing all variables we’ve passed to **

**letIfAllNotNull()**.If we run the test, it’ll pass. So the *letIfAllNotNull()* function allows us to perform null-safe *let*-like operations on a dynamic number of nullable variables.

### 4.3. *let* on Not-Null Variables

So far, we’ve created functions to apply the null-safe *let*-like operation on multiple variables. A common requirement among the solutions is that the *block* function gets called only if all variables are not null.

However, in practice, **we may want first to filter out the null values and pass all not-null variables to our block function or the lambda expression**.

Finally, let’s make some changes based on the *letIfAllNotNull()* function to create the *letIfAnyNotNull()* function:

```
inline fun <T : Any, R : Any> letIfAnyNotNull(vararg arguments: T?, block: (List<T>) -> R?): R? {
return if (arguments.any { it != null }) {
block(arguments.filterNotNull())
} else null
}
```

As the code above shows, **we check arguments.any { it != null } instead of arguments.all { it != null } so that the block() function gets called if arguments contain any not-null variables.**

Next, let’s create a test and some input data to see if *letIfAnyNotNull()* can produce the expected result:

```
assertThat(letIfAnyNotNull(5, 6, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isEqualTo("5 + 6 + 7 is 18")
assertThat(letIfAnyNotNull(nullNum, 6, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isEqualTo("6 + 7 is 13")
assertThat(letIfAnyNotNull(nullNum, nullNum, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isEqualTo("7 is 7")
assertThat(letIfAnyNotNull(nullNum, nullNum, nullNum) { "${it.joinToString(separator = " + ") { num -> "$num" }} is ${it.sum()}" }).isNull()
```

As the assertions show, the *letIfAnyNotNull()* function will pack all not-null values in a list and pass the list to our lambda expression.

The test passes if we give it a run.

## 5. Conclusion

In this article, we’ve learned different approaches to performing null-safe *let-*like operations on multiple variables in Kotlin.

As always, the complete source code used in the article can be found over on GitHub.