Single-Expression Functions
A single-expression function in Kotlin is a function whose entire body is one expression, written after an = sign instead of inside { } braces with a return statement. It’s Kotlin’s way of saying: if a function just computes and returns a value, don’t make the reader wade through boilerplate to see that. This shows up constantly in idiomatic Kotlin code — predicates, small transformations, extension functions, and getters almost always use this form — and the compiler can even infer the return type for you.
Overview / How it works
Every Kotlin function has a body, and Kotlin gives you two ways to write one. The familiar way is a block body: curly braces containing one or more statements, with an explicit return for the value you want to hand back. The second way is an expression body, informally called a single-expression function: you drop the braces and the return keyword entirely, write an = after the function signature, and follow it with exactly one expression. That expression’s value becomes the function’s return value.
Under the hood these two forms compile to identical JVM bytecode. The compiler treats fun square(x: Int): Int = x * x exactly as if you had written fun square(x: Int): Int { return x * x } — there is no runtime difference, no performance cost, and no difference in how the function is called from Java or Kotlin code. The expression-body form exists purely to remove ceremony when a function’s logic really is “compute one value and give it back”, which turns out to be a huge fraction of the small functions you write day to day.
The other important thing the compiler does for expression-body functions is return type inference. Because the entire body is a single expression, the compiler can statically determine that expression’s type and use it as the function’s return type automatically — you don’t have to write it. fun greet(name: String) = "Hello, $name!" is inferred to return String without : String appearing anywhere. This only works for expression-body functions; a block-bodied function with an omitted return type is assumed to return Unit (Kotlin’s void-like type), because the compiler does not attempt to infer a type by analyzing every return statement buried inside a block.
Inference has one hard limit worth knowing up front: a recursive single-expression function cannot infer its own return type, because the compiler would need to already know that type in order to type-check the recursive call that appears inside the very expression it’s trying to type. For recursive functions you must write the return type explicitly, even when using the expression-body form. This is covered in Common Mistakes below.
Syntax
fun functionName(parameterName: ParameterType): ReturnType = expression
// return type is optional when it can be inferred:
fun functionName(parameterName: ParameterType) = expression
fun— keyword that starts every function declaration.functionName— the function’s name, following normal Kotlin naming (lowerCamelCase).(parameterName: ParameterType, ...)— the parameter list, exactly as in any Kotlin function.: ReturnType— optional. Kotlin infers it from the expression’s type when omitted; write it explicitly for public API functions, and it is required for recursive functions.=— replaces the{ }block; everything after it is the function body.expression— any single Kotlin expression: arithmetic, a string template, anifexpression, awhenexpression, a function call, a safe-call chain, and so on. It cannot be a sequence of statements — for that you need a block body.
Examples
Example 1: A basic single-expression function
fun square(x: Int): Int = x * x
fun main() {
println(square(5))
}
Output:
25
The function signature declares an explicit Int return type, and the body after = is the single expression x * x. There’s no return keyword and no braces — the value of the expression is automatically what square returns.
Example 2: Letting the compiler infer the return type
fun greet(name: String) = "Hello, $name!"
fun main() {
println(greet("Kotlin"))
}
Output:
Hello, Kotlin!
No return type is written on greet. The string template "Hello, $name!" has static type String, so the compiler infers greet returns String without any help. This is the most common style for small expression-body functions.
Example 3: Single-expression extension function
fun Int.isEven() = this % 2 == 0
fun main() {
println(4.isEven())
println(7.isEven())
}
Output:
true
false
isEven is an extension function on Int — this refers to the receiver Int it’s called on. The body is the boolean expression this % 2 == 0, so the return type Boolean is inferred. Extension functions and single-expression syntax combine constantly in idiomatic Kotlin because most extensions are small, focused computations.
Example 4: Combining when, safe calls, and the elvis operator
fun sign(x: Int) = when {
x < 0 -> "negative"
x == 0 -> "zero"
else -> "positive"
}
fun safeSign(x: Int?) = x?.let { sign(it) } ?: "unknown"
fun main() {
println(sign(-5))
println(safeSign(null))
println(safeSign(10))
}
Output:
negative
unknown
positive
sign uses a when expression with an else branch as its single expression, so it’s exhaustive and infers String. safeSign shows that single-expression functions can call other single-expression functions and thread null-safety operators through cleanly: x?.let { sign(it) } is null when x is null, and the elvis operator ?: supplies "unknown" in that case.
How it works step by step
Take fun safeSign(x: Int?) = x?.let { sign(it) } ?: "unknown" and trace what the compiler and the runtime each do:
- Parsing: the compiler sees the
=after the signature and parses everything that follows as one expression rather than a block of statements. - Type inference: the compiler determines the static type of that expression bottom-up.
x?.let { sign(it) }has typeString?— a safe-call chain is always nullable, because the receiver might be null — andString? ?: "unknown"resolves to non-nullString. That becomessafeSign‘s inferred return type. - Compilation to bytecode: the expression is compiled as if it were
{ return x?.let { sign(it) } ?: "unknown" }— identical instructions to what a block body with a singlereturnwould produce. - At call time with
safeSign(null), the runtime evaluatesx?.let { ... }first. Sincexisnull, the safe call short-circuits and the lambda never runs, producingnullwithout aNullPointerException. - The elvis operator
?:then sees anullleft-hand side and evaluates its right-hand side, yielding"unknown". - That value becomes what
safeSignreturns to its caller, whichprintlnwrites to standard output.
Common Mistakes
Mistake 1: Adding braces after = creates a lambda, not a block body
It’s tempting to think { } after = works like a block body. It doesn’t — Kotlin sees a brace-delimited expression as a lambda literal, i.e. a function value, not a set of statements to execute.
// WRONG — this does not do what it looks like it does
fun square(x: Int) = { x * x }
fun main() {
println(square(5))
}
Here square‘s inferred return type is () -> Int — a zero-argument function that computes x * x when called — not Int. Calling square(5) returns that lambda object itself, not 25, so println(square(5)) prints something like a function-object reference, not the number you expected. The fix is simply to remove the braces so the expression body is x * x directly:
fun square(x: Int) = x * x
fun main() {
println(square(5))
}
Output:
25
Mistake 2: Forgetting the explicit return type on a recursive function
Return-type inference needs to already know a function’s type to type-check a call to that same function inside its own body. For a non-recursive function that’s never a problem, but a recursive single-expression function creates a circular dependency the compiler refuses to resolve.
// WRONG — fails to compile: the recursive call to factorial()
// needs factorial's return type before that type is known
fun factorial(n: Int) = if (n <= 1) 1 else n * factorial(n - 1)
This is rejected at compile time because factorial calls itself inside the very expression the compiler is trying to type. The fix is to declare the return type explicitly, breaking the circularity:
fun factorial(n: Int): Int = if (n <= 1) 1 else n * factorial(n - 1)
fun main() {
println(factorial(5))
}
Output:
120
Best Practices
- Use expression-body syntax for functions whose entire job is “compute and return one value” — predicates, small transformations, getters, and short extension functions.
- Switch to a block body with
{ }and an explicitreturnas soon as the logic needs more than one statement, intermediatevals for readability, or side effects before returning. - Write the return type explicitly on public or exported single-expression functions even though it’s optional — it documents the API and prevents callers from being surprised if the expression’s inferred type quietly changes later.
- Always write the return type explicitly on recursive single-expression functions — the compiler requires it, and it also makes the function’s contract obvious to readers.
- Prefer
ifandwhenas the expression inside a single-expression function over chained boolean tricks; both are genuine expressions in Kotlin and read cleanly right after=. - Don’t chain
let,also, orrunexcessively just to cram multi-step logic into one expression — if the single line becomes hard to read, a block body is the more idiomatic choice, not a compromise.
Practice Exercises
- Write a single-expression function
fun isPositive(x: Int)that returnstrueifxis greater than zero andfalseotherwise. Call it with-3and7and print both results. - Write a single-expression extension function
fun String.shout()that returns the string in uppercase with an exclamation mark appended, so"hi".shout()produces"HI!". Let Kotlin infer the return type. - Write a recursive single-expression function
fun fibonacci(n: Int): Intthat returns thenth Fibonacci number, withfibonacci(0) == 0andfibonacci(1) == 1. Remember that recursive expression-body functions require an explicit return type. Test it withn = 10; the expected output is55.
Summary
- A single-expression function replaces
{ return ... }with= expression— both forms compile to identical bytecode. - The return type can usually be omitted; the compiler infers it from the expression’s static type.
- Recursive single-expression functions are the one case where the return type must be written explicitly, or the code fails to compile.
- Putting
{ }after=creates a lambda — a function value — not a block body; this is a common source of confusing bugs. - Use expression-body syntax for short, purely computational functions, and switch to a block body once a function needs multiple statements.
