1. Introduction
If you have just learned if, your brain probably still sees it as an “intersection”: if the condition is true, we go left and do something; otherwise, we go right and do something too. And that is normal. But in real programs we often need not only to “do an action”, but to get the result of the choice: a string, a number, a label that we then use later.
Imagine you are choosing which sign to hang on the door: "Open" or "Closed". This is not a separate action for the sake of action—it is a value that needs to be stored and then printed. Kotlin says here: “OK, let if return the sign right away.” And that is very convenient.
if as a statement vs. if as an expression
When we write if “the old way”, it works as a statement: it executes or does not execute a piece of code, and that is it.
But Kotlin allows using if as an expression, meaning something that has a value, like 3 + 4 or "Hi".length. This is an official part of the language: you can write if as an expression and assign its result to a variable, but then the else branch becomes mandatory.
To grasp the idea, let us compare two styles: “first create a box (var), then put the result into it” and “get the result right away”.
Here is the style you already know (statement-style if):
fun main() {
val temperature = 28
var label = "OK"
if (temperature > 25) {
label = "Hot"
}
println(label) // Hot
}
Here, if does not “return” anything. We just change label.
And here is the style we want to learn today (if as an expression):
fun main() {
val temperature = 28
val label = if (temperature > 25) "Hot" else "OK"
println(label) // Hot
}
The meaning difference is this: in the second variant we directly tell Kotlin: “label is the result of the choice”.
Basic syntax: val x = if (cond) A else B
The basic form is: val result = if (condition) value1 else value2. It reads almost like a human phrase: “result equals (if condition then value1 else value2)”.
val result = if (condition) value1 else value2
if as an expression
An example that always works well is the modulus of a number (absolute value). In math this is |x|, and in code we simply choose either x or -x.
fun main() {
val x = -7
val abs = if (x >= 0) x else -x
println("abs = $abs") // abs = 7
}
Notice: here we do not use var at all. This is a small but important win: fewer mutable variables means fewer places where you can “accidentally break everything”.
Why else is mandatory
From a beginner’s point of view, the requirement “why can I not write val r = if (cond) 1?” can feel nitpicky. But the compiler is not being mean—it is protecting logic: if you assign a result, that result must exist always, for any outcome of the condition.
Kotlin states explicitly: if if is used as an expression, the else branch is required.
Bad example (not allowed):
fun main() {
val isVip = true
val discount = if (isVip) 10 // Compilation error: missing else
}
Good example (correct):
fun main() {
val isVip = true
val discount = if (isVip) 10 else 0
println("discount = $discount") // discount = 10
}
A handy rule to remember here: if if is on the right side of = and you want to store the result, then else is always required.
Block branches { ... }: the value is the last expression
Often you need to do more than one thing in a branch: for example, print an explanation and only then return a value. Kotlin allows each branch to be a block { ... }. Then the branch result becomes the last expression in the block.
Let us make a mini-example: choose who is considered “taller”—Alice or Bob—and at the same time print who we chose (as if we are logging).
fun main() {
val heightAlice = 160
val heightBob = 175
val taller = if (heightAlice > heightBob) {
println("Chose Alice") // Chose Alice (if the condition were true)
heightAlice
} else {
println("Chose Bob") // Chose Bob
heightBob
}
println("taller = $taller") // taller = 175
}
Notice the “trick”: println(...) is not the value for taller. The value is the last line of the branch (heightAlice or heightBob). This is a very important habit: at the end of a branch of an if-expression, you should place what you want to return.
A small diagram of how it works
flowchart TD
A["if (condition)"] -->|true| B["then branch { ... }"]
A -->|false| C["else branch { ... }"]
B --> D["value = last expression of then branch"]
C --> E["value = last expression of else branch"]
D --> F["final if-expression value"]
E --> F
2. else if and a linear coding style
Sometimes we do not have “two options”, but “three or four”. And the good news is: an if / else if / else chain can also be an expression. That is, we can assign the result immediately, without intermediate vars.
Example: we have a number x and we want to get a string "positive", "negative", or "zero".
fun main() {
val x = 0
val sign = if (x > 0) {
"positive"
} else if (x < 0) {
"negative"
} else {
"zero"
}
println(sign) // zero
}
This style is especially good because the variable sign has a clear meaning immediately: “this is the label of the number’s sign”.
How an if-expression helps keep code “linear”
Before if-expressions, people often write it like this: “declare a var, then assign in different branches”. This works, but it gradually leads to a variable living too long and changing too many times.
Let us compare two approaches on a real-life task: if a string is empty, replace it with "EMPTY"; otherwise, keep it as is (after trim()).
Approach with var:
fun main() {
val raw = " Kotlin "
val trimmed = raw.trim()
var label = "EMPTY"
if (trimmed.length != 0) {
label = trimmed
}
println(label) // Kotlin
}
Approach with an if-expression:
fun main() {
val raw = " Kotlin "
val trimmed = raw.trim()
val label = if (trimmed.length == 0) "EMPTY" else trimmed
println(label) // Kotlin
}
The second variant reads faster: “label is either "EMPTY" or trimmed”. And most importantly: label is now a val, meaning you cannot accidentally change the “door sign” in the middle of the program.
3. Practical example: “Cash register with a discount”
Now we will build a small console program that looks like a real scenario: the user enters the order amount, and we calculate the discount and print the total. This is an honest training exercise that clearly shows where if as an expression makes the code simpler.
The idea is:
1) read the amount (as a string), trim spaces, try to parse it to a number via toIntOrNull();
2) if it is not a number, print an error message;
3) if it is a number, determine the discount percent: if the amount is >= 1000, then the discount is 10%, otherwise 0%;
4) build a message and print it.
Step 1. Choose the discount percent as a value
Let us start with the most important part for this lecture—assigning the result of branching:
fun main() {
val sum = 1200
val discountPercent = if (sum >= 1000) 10 else 0
println("discountPercent = $discountPercent") // discountPercent = 10
}
The key point is visible here: discountPercent is the result of a choice, so an if-expression is a perfect fit.
Step 2. The message can also be “computed”
Beginners often do this: in each if branch they print almost the same text. It works, but quickly leads to duplication. Instead, we can compute a message string and keep printing to a single line.
fun main() {
val finalSum = 950
val discountPercent = 0
val message = if (discountPercent > 0) {
"Discount $discountPercent% applied. Total: $finalSum"
} else {
"No discount. Total: $finalSum"
}
println(message) // No discount. Total: 950
}
Notice: we use blocks { ... }, but the last line is a string, not a println.
Step 3. Full version of main
Below is a complete example. It is a bit longer than the separate snippets (because a real program still does input/validation), but all the techniques here are already familiar.
fun main() {
print("Enter the order amount: ")
val sumText = readln().trim()
val sum = sumText.toIntOrNull()
if (sum == null) {
println("Error: you must enter an integer.") // Error: you must enter an integer.
} else {
val discountPercent = if (sum >= 1000) 10 else 0
val discount = sum * discountPercent / 100
val finalSum = sum - discount
val message = if (discountPercent > 0) {
"Discount $discountPercent% (-$discount). Total: $finalSum"
} else {
"No discount. Total: $finalSum"
}
println(message) // example: Discount 10% (-120). Total: 1080
}
}
There is an important mindset here: we try to make variables “single-use facts”. discountPercent, discount, finalSum, message are not “boxes that change”, but “results of computations”. Once you get used to this, the code becomes more predictable: you read it top to bottom, like a recipe.