Question
In Kotlin, is there a clean way to use let with multiple nullable variables at the same time?
For example:
fun example(first: String?, second: String?) {
first?.let {
second?.let {
// Do something only if both are not null
}
}
}
I am looking for a shorter or clearer approach, something conceptually like this:
fun example(first: String?, second: String?) {
first?.let && second?.let {
// Do something only if both are not null
}
}
Is there an idiomatic Kotlin way to run code only when multiple nullable values are all non-null?
Short Answer
By the end of this page, you will understand how Kotlin's null-safety works with multiple nullable values, how let behaves, and the common idiomatic ways to execute code only when several variables are not null. You will also see practical alternatives such as if checks, nested let, early returns, and helper patterns.
Concept
Kotlin has built-in null-safety features to help prevent NullPointerException. A nullable type is written with ?, such as String?, which means the variable may contain either a String or null.
The let function is often used with the safe-call operator ?.:
value?.let {
// runs only if value is not null
}
This is useful for a single nullable value. But when you have multiple nullable variables, Kotlin does not provide special syntax like:
first?.let && second?.let { ... }
That syntax does not exist because let is just a normal function call on a value, not a language-level boolean operator.
When you want to run code only if several values are non-null, the main idea is simple:
- check that all required values are not null
- then use them in a non-null context
In Kotlin, the most common ways to do that are:
- nested
let - a normal
ifnull check - early return with guard clauses
- helper functions for repeated patterns
This matters in real programming because nullable values are everywhere:
- API responses
- form input
- database fields
- optional configuration
- parsed JSON values
Knowing how to combine null checks cleanly makes your code safer and easier to read.
Mental Model
Think of nullable values as keys that may or may not exist.
first?.let { ... }means: "If keyfirstexists, unlock this block."second?.let { ... }means the same forsecond.
If you need both keys, then both must be present before you can open the door.
You can do that in different ways:
- check one key, then the other
- check both first with an
if - return early if one key is missing
So the real question is not "How do I chain lets with &&?" but rather:
What is the clearest way to guarantee all required values are non-null before running the code?
Syntax and Examples
The simplest valid approaches in Kotlin are below.
1. Nested let
fun example(first: String?, second: String?) {
first?.let { f ->
second?.let { s ->
println("Both values exist: $f and $s")
}
}
}
This works, but nesting can become harder to read if you have many nullable values.
2. Use if to check both values
fun example(first: String?, second: String?) {
if (first != null && second != null) {
println("Both values exist: $first and $second")
}
}
This is often the clearest option. Inside the if block, Kotlin smart-casts both values to non-null.
3. Early return with guard clauses
fun example(first: String?, second: String?) {
if (first == null || second == null) return
println("Both values exist: $first and $second")
}
This style is very common in real code because it avoids nesting.
4. Use one let inside another but name values clearly
fun example(first: String?, second: String?) {
first?.let { firstValue ->
second?.let { secondValue ->
println(firstValue.length + secondValue.length)
}
}
}
Using named lambda parameters like firstValue and secondValue is clearer than relying on the default it.
5. Create a helper function for reuse
inline fun <A, B> ifNotNull(a: A?, b: B?, block: (A, B) -> Unit) {
if (a != null && b != null) {
block(a, b)
}
}
fun example(first: String?, second: String?) {
ifNotNull(first, second) { f, s ->
println("Both values exist: $f and $s")
}
}
This can be useful if your project frequently needs this pattern.
Step by Step Execution
Consider this example:
fun example(first: String?, second: String?) {
if (first != null && second != null) {
println(first.uppercase())
println(second.uppercase())
}
}
Now imagine this call:
example("hello", "world")
Step by step
firstreceives"hello".secondreceives"world".- Kotlin checks
first != null.- This is
true.
- This is
- Kotlin checks
second != null.- This is also
true.
- This is also
- Since both are true, the
ifblock runs. - Inside the block, Kotlin smart-casts both variables to non-null
String. first.uppercase()printsHELLO.second.uppercase()printsWORLD.
Now imagine this call instead:
example("hello", null)
What changes?
first != nullistrue.second != nullisfalse.- Because the whole condition uses
&&, the block does not run. - No uppercase call happens, so there is no risk of using a null value.
This is the core behavior you want when working with multiple nullable variables.
Real World Use Cases
This pattern appears often in everyday Kotlin code.
Form validation
if (email != null && password != null) {
login(email, password)
}
Only proceed when the required fields exist.
API response handling
if (user.id != null && user.name != null) {
saveUser(user.id, user.name)
}
Some API fields may be optional, so you check before using them.
Database values
if (row.title != null && row.createdAt != null) {
println("${row.title} created at ${row.createdAt}")
}
Useful when reading partially populated records.
Configuration loading
if (host != null && port != null) {
connect(host, port)
}
A program may need multiple configuration values before it can continue.
UI logic
if (selectedUser != null && selectedAccount != null) {
showDetails(selectedUser, selectedAccount)
}
Only render a screen when all required state is available.
Real Codebase Usage
In real projects, developers usually choose the style that makes the intent easiest to read.
Common pattern: guard clauses
fun process(first: String?, second: String?) {
if (first == null || second == null) return
println("Processing $first and $second")
}
Why it is popular:
- avoids deep nesting
- keeps the main logic at the left margin
- makes missing data handling explicit
Common pattern: validation before work
fun createUser(name: String?, email: String?) {
require(name != null && email != null) { "Name and email are required" }
println("Creating user: $name, $email")
}
This is useful when null values are considered invalid input.
Common pattern: helper for repeated null checks
inline fun <A, B, R> withNotNull(a: A?, b: B?, block: (A, B) -> R): R? {
return if (a != null && b != null) block(a, b) else null
}
Teams may create helpers like this when the same null-check pattern appears many times.
Common pattern: avoid overusing nested scope functions
Kotlin has several scope functions:
letrunapplyalsowith
In real codebases, developers try not to stack too many of them together because readability drops quickly.
Practical rule
If a normal if is clearer than nested let, use if. Idiomatic Kotlin values readability, not cleverness.
Common Mistakes
1. Expecting special syntax for multiple let calls
Broken idea:
fun example(first: String?, second: String?) {
first?.let && second?.let {
println("Won't compile")
}
}
Why it fails:
letis not a boolean expression by itself&&works with boolean values, not function calls in this form
2. Using nested it and losing track of values
Confusing code:
fun example(first: String?, second: String?) {
first?.let {
second?.let {
println(it)
}
}
}
Problem:
- the inner
ithides the outerit - it becomes unclear which value you are using
Better:
fun example(first: String?, second: String?) {
first?.let { f ->
second?.let { s ->
println("$f $s")
}
}
}
3. Using !! when it is not necessary
Risky code:
fun example(first: String?, second: String?) {
if (first != null && second != null) {
println(first!!)
println(second!!)
}
}
Why avoid it:
!!can throw an exception- inside the
if, Kotlin already knows they are non-null
Better:
fun example(first: String?, second: String?) {
if (first != null && second != null) {
println(first)
println(second)
}
}
4. Overcomplicating simple null checks
Sometimes beginners try to use let everywhere because it looks more "Kotlin-like". But this is often easier:
if (first != null && second != null) {
doSomething(first, second)
}
Simple code is often the best code.
5. Forgetting smart casts only work in suitable conditions
Kotlin smart-casts when it can safely prove a variable is non-null. In simple local checks, this usually works well. But with mutable properties or complex logic, you may need to store values in local variables first.
Comparisons
| Approach | Example | Best for | Pros | Cons |
|---|---|---|---|---|
Nested let | first?.let { f -> second?.let { s -> ... } } | Short nullable chains | Safe and expressive | Can become deeply nested |
if null check | if (first != null && second != null) { ... } | Most common cases | Very clear, smart-cast works | Slightly more verbose |
| Guard clause | `if (first == null | second == null) return` | Functions that should stop early | |
| Helper function | ifNotNull(first, second) { f, s -> ... } | Repeated patterns | Reusable and clean at call site | Adds abstraction |
!! operator | doSomething(first!!, second!!) | Rare cases only | Short syntax | Unsafe, can throw exceptions |
let vs if
- Use
letwhen you want to create a temporary non-null scope for one value. - Use
ifwhen checking multiple conditions is clearer.
Guard clause vs nested block
- Guard clauses reduce indentation.
- Nested blocks are okay for very small logic, but can become harder to follow.
Cheat Sheet
Quick syntax
One nullable value
value?.let { nonNullValue ->
println(nonNullValue)
}
Two nullable values with if
if (first != null && second != null) {
doSomething(first, second)
}
Two nullable values with early return
if (first == null || second == null) return
doSomething(first, second)
Two nullable values with nested let
first?.let { f ->
second?.let { s ->
doSomething(f, s)
}
}
Rules to remember
?.calls something only if the value is not null.letis a scope function, not special null-check syntax.- There is no Kotlin syntax like
first?.let && second?.let. - For multiple nullable values,
ifis often the clearest choice. - Prefer named lambda parameters over nested
it. - Avoid
!!unless you truly want to throw on null.
Handy reusable helper
inline fun <A, B> ifNotNull(a: A?, b: B?, block: (A, B) -> Unit) {
if (a != null && b != null) block(a, b)
}
Edge case
Smart casts may not work as expected with mutable properties. In that case, copy the values into local variables first.
FAQ
Can Kotlin use one let for two nullable variables?
Not directly with special syntax. You usually use nested let, an if check, or a helper function.
What is the most idiomatic way to check two nullable values in Kotlin?
In many cases, a simple if (a != null && b != null) is the clearest and most idiomatic choice.
Is nested let bad in Kotlin?
No, but too much nesting can hurt readability. For two or more variables, if or guard clauses are often easier to read.
Why does first?.let && second?.let not work?
Because let is a function call, not a boolean operator. The && operator only combines boolean expressions.
Should I use !! after checking for null?
Usually no. If Kotlin smart-casts the variables inside the checked block, !! is unnecessary.
How do I avoid deeply nested nullable code?
Use guard clauses, local variables, or helper functions to keep the code flat and readable.
Can I write a helper for multiple non-null values?
Yes. Many developers create small utility functions such as ifNotNull(a, b) { ... } when the pattern appears often.
Mini Project
Description
Build a small Kotlin function that formats a contact card only when all required nullable inputs are available. This demonstrates how to safely combine multiple nullable values without using unsafe operators.
Goal
Create a function that prints a contact summary only when name and email are both non-null.
Requirements
- Write a function that accepts
name: String?andemail: String?. - Return early if either value is null.
- Print a formatted contact summary when both values exist.
- Call the function with both valid and null inputs to verify the behavior.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.