A Kotlin expression is any piece of code that evaluates to a value. This fundamental concept sets Kotlin apart from many other programming languages. In Kotlin, expressions are the building blocks that combine variables, operators, method calls, and literals to produce a single result value.
Every Kotlin expression has a type and returns a value, even if that value is Unit
(equivalent to void
in Java). This expression-oriented approach makes Kotlin more functional and allows for more concise code patterns.
Key characteristics of Kotlin expressions:
Here’s a simple example of a Kotlin expression:
val result = 10 + 5 // 10 + 5 is an expression that evaluates to 15
The distinction between Kotlin expressions and statements is crucial for understanding how Kotlin code works. While expressions always return values, statements perform actions without returning meaningful values.
Kotlin expressions:
5 + 3
, if (a > b) a else b
, when(x) { 1 -> "one" else -> "other" }
Kotlin statements:
val x = 5
), function declarations// Expression - returns a value
val maximum = if (a > b) a else b
// Statement - declares a variable
val number = 42
// Expression used as statement
println("Hello") // Function call expression used as statement
Arithmetic Kotlin expressions combine numbers using mathematical operators. These expressions follow standard mathematical precedence rules.
val addition = 10 + 5 // Addition expression
val subtraction = 20 - 8 // Subtraction expression
val multiplication = 4 * 7 // Multiplication expression
val division = 15 / 3 // Division expression
val modulus = 17 % 5 // Modulus expression
val power = 2.0.pow(3.0) // Power expression (requires import)
Compound arithmetic expressions combine multiple operations:
val complexCalculation = (10 + 5) * 3 - 8 / 2 // Evaluates to 41
val averageScore = (math + science + english) / 3
Boolean Kotlin expressions evaluate to either true
or false
. These are essential for conditional logic and control flow.
val isAdult = age >= 18 // Comparison expression
val isValidUser = isLoggedIn && isActive // Logical AND expression
val shouldShowAd = isPremium || hasTrialExpired // Logical OR expression
val isNotEmpty = !text.isEmpty() // Logical NOT expression
Complex boolean expressions can combine multiple conditions:
val canAccessFeature = (user.isPremium || user.isAdmin) &&
user.isEmailVerified &&
!user.isSuspended
One of the most powerful features of Kotlin is that if
constructs are expressions, not just statements. Kotlin if expressions can return values, making them incredibly useful for conditional assignments.
// Simple if expression
val status = if (score >= 90) "Excellent" else "Good"
// Multi-line if expression
val grade = if (percentage >= 90) {
println("Outstanding performance!")
"A+"
} else if (percentage >= 80) {
println("Great job!")
"A"
} else {
println("Keep trying!")
"B"
}
If expressions must have an else
branch when used as expressions because every expression must return a value.
// This won't compile - missing else branch
// val result = if (condition) "yes" // Error!
// Correct version
val result = if (condition) "yes" else "no"
Kotlin when expressions are the modern replacement for Java’s switch statements, but far more powerful. They can work with any type and support complex pattern matching.
// Basic when expression
val dayType = when (dayOfWeek) {
1, 2, 3, 4, 5 -> "Weekday"
6, 7 -> "Weekend"
else -> "Invalid day"
}
// When expression with ranges
val ageGroup = when (age) {
in 0..12 -> "Child"
in 13..19 -> "Teenager"
in 20..64 -> "Adult"
in 65..120 -> "Senior"
else -> "Invalid age"
}
// When expression with type checking
val description = when (value) {
is String -> "Text: ${value.length} characters"
is Int -> "Number: $value"
is Boolean -> "Boolean: $value"
else -> "Unknown type"
}
When expressions without arguments act like if-else chains:
val recommendation = when {
temperature > 30 -> "It's hot, stay hydrated"
temperature > 20 -> "Perfect weather for outdoor activities"
temperature > 10 -> "A bit cool, wear a light jacket"
else -> "It's cold, dress warmly"
}
Kotlin lambda expressions are anonymous functions that can be treated as values. They’re essential for functional programming and working with collections.
// Basic lambda expression
val square = { x: Int -> x * x }
val result = square(5) // Returns 25
// Lambda with multiple parameters
val multiply = { a: Int, b: Int -> a * b }
val product = multiply(4, 7) // Returns 28
// Lambda with collections
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10]
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
Higher-order function expressions with lambdas:
// Function that takes a lambda as parameter
fun processNumbers(numbers: List<Int>, operation: (Int) -> Int): List<Int> {
return numbers.map(operation)
}
// Using the function with lambda expressions
val squares = processNumbers(listOf(1, 2, 3, 4)) { it * it }
val cubes = processNumbers(listOf(1, 2, 3, 4)) { it * it * it }
Kotlin try-catch expressions can return values, making error handling more functional and expressive.
// Try expression returning a value
val number = try {
inputString.toInt()
} catch (e: NumberFormatException) {
0 // Default value if parsing fails
}
// Try expression with complex logic
val result = try {
val data = fetchDataFromAPI()
processData(data)
"Success"
} catch (e: NetworkException) {
"Network error: ${e.message}"
} catch (e: Exception) {
"Unknown error occurred"
} finally {
cleanup() // Finally block doesn't affect the returned value
}
Every function call in Kotlin is an expression that returns a value, even if that value is Unit
.
// Function that returns a value
fun calculateArea(radius: Double): Double = Math.PI * radius * radius
val area = calculateArea(5.0) // Function call expression
// Function with Unit return type
fun printMessage(msg: String): Unit = println(msg)
printMessage("Hello World") // Expression returning Unit
// Expression function (single expression function)
fun getGreeting(name: String) = "Hello, $name!"
val greeting = getGreeting("Alice")
Kotlin string template expressions allow embedding expressions directly within strings using the $
symbol.
val name = "John"
val age = 25
// Simple variable interpolation
val introduction = "My name is $name and I am $age years old"
// Complex expression interpolation
val message = "Next year, $name will be ${age + 1} years old"
// Function call in string template
val formatted = "Current time: ${getCurrentTime()}"
// Expression with string operations
val details = "Name length: ${name.length}, uppercase: ${name.uppercase()}"
Accessing properties and array elements are also expressions in Kotlin.
// Property access expressions
val person = Person("Alice", 30)
val personName = person.name // Property access expression
val personAge = person.age // Property access expression
// Array access expressions
val numbers = arrayOf(10, 20, 30, 40, 50)
val firstNumber = numbers[0] // Array access expression
val lastNumber = numbers[numbers.size - 1] // Complex access expression
// List access expressions
val fruits = listOf("apple", "banana", "orange")
val favoriteFruit = fruits[1] // List access expression
val fruitCount = fruits.size // Property access expression
Kotlin allows functions to have expression bodies instead of block bodies, making code more concise for simple functions.
// Traditional function with block body
fun addTraditional(a: Int, b: Int): Int {
return a + b
}
// Function with expression body
fun add(a: Int, b: Int): Int = a + b
// Expression body with type inference
fun multiply(a: Int, b: Int) = a * b
// More complex expression body
fun getDiscountedPrice(price: Double, discount: Double) =
if (discount > 0) price * (1 - discount) else price
Kotlin expressions can be nested and combined to create sophisticated logic in a readable way.
// Nested expressions
val complexResult = when {
temperature > 30 -> if (humidity > 80) "Hot and humid" else "Hot and dry"
temperature > 20 -> if (isRaining) "Warm but rainy" else "Pleasant weather"
else -> if (isSnowing) "Cold and snowy" else "Cold"
}
// Expression chains
val processedData = inputList
.filter { it.isValid }
.map { it.process() }
.sortedBy { it.priority }
.take(10)
// Combined expressions in function calls
fun analyzeData(data: List<DataPoint>) = data
.groupBy { it.category }
.mapValues { (_, values) ->
values.map { it.value }.average()
}
.filterValues { it > threshold }
Here’s a practical example using various Kotlin expressions in a configuration management scenario:
class ConfigurationManager(private val environment: String) {
// When expression for environment-specific settings
private val databaseUrl = when (environment.lowercase()) {
"development" -> "jdbc:h2:mem:devdb"
"testing" -> "jdbc:h2:mem:testdb"
"staging" -> "jdbc:postgresql://staging-db:5432/app"
"production" -> "jdbc:postgresql://prod-db:5432/app"
else -> throw IllegalArgumentException("Unknown environment: $environment")
}
// If expression for feature flags
private val enableCaching = if (environment == "production") true else false
// Try expression for safe property access
private val maxConnections = try {
System.getProperty("db.maxConnections")?.toInt()
} catch (e: NumberFormatException) {
null
} ?: getDefaultMaxConnections()
// Lambda expression for default calculation
private fun getDefaultMaxConnections() = when (environment) {
"production" -> 50
"staging" -> 20
else -> 10
}
// Expression function for configuration summary
fun getConfigSummary() = """
Environment: $environment
Database URL: $databaseUrl
Caching Enabled: $enableCaching
Max Connections: $maxConnections
""".trimIndent()
}
This example demonstrates Kotlin expressions in a data processing context:
data class SalesRecord(val product: String, val amount: Double, val region: String, val date: String)
class SalesAnalyzer {
fun analyzeSales(records: List<SalesRecord>): Map<String, Double> {
// Complex expression chain for data analysis
return records
.filter { it.amount > 0 } // Lambda expression
.groupBy { it.region } // Lambda expression
.mapValues { (_, regionRecords) -> // Lambda with destructuring
regionRecords
.map { it.amount } // Lambda expression
.sum() // Method call expression
}
.filterValues { it > 1000 } // Lambda expression
}
fun getTopPerformingRegion(records: List<SalesRecord>): String {
// Nested expressions with elvis operator
return analyzeSales(records)
.maxByOrNull { it.value } // Lambda expression
?.key // Safe call expression
?: "No data available" // Elvis expression
}
fun generateReport(records: List<SalesRecord>): String {
val totalSales = records.sumOf { it.amount } // Lambda expression
val avgSale = if (records.isNotEmpty()) totalSales / records.size else 0.0
val topRegion = getTopPerformingRegion(records)
// String template expressions
return """
Sales Report
============
Total Records: ${records.size}
Total Sales: $${String.format("%.2f", totalSales)}
Average Sale: $${String.format("%.2f", avgSale)}
Top Region: $topRegion
Report Generated: ${getCurrentTimestamp()}
""".trimIndent()
}
private fun getCurrentTimestamp() = System.currentTimeMillis().toString()
}
Here’s a comprehensive example that demonstrates multiple types of Kotlin expressions working together:
import kotlin.math.pow
import kotlin.random.Random
// Data classes for the example
data class User(val name: String, val age: Int, val isPremium: Boolean, val score: Double)
data class GameResult(val user: User, val level: Int, val points: Int, val timeInSeconds: Int)
class GameAnalytics {
// Expression function for user classification
fun classifyUser(user: User) = when {
user.age < 13 -> "Junior Player"
user.age < 18 -> "Teen Player"
user.age < 65 -> if (user.isPremium) "Premium Adult" else "Regular Adult"
else -> "Senior Player"
}
// Complex expression for score calculation
fun calculateFinalScore(result: GameResult): Double {
val baseScore = result.points.toDouble()
// Time bonus using if expression
val timeBonus = if (result.timeInSeconds < 60) {
baseScore * 0.5 // 50% bonus for quick completion
} else if (result.timeInSeconds < 120) {
baseScore * 0.25 // 25% bonus for moderate speed
} else {
0.0 // No time bonus
}
// Level multiplier using when expression
val levelMultiplier = when (result.level) {
in 1..5 -> 1.0
in 6..10 -> 1.5
in 11..15 -> 2.0
else -> 2.5
}
// Premium bonus using if expression
val premiumBonus = if (result.user.isPremium) baseScore * 0.1 else 0.0
// Final calculation expression
return (baseScore + timeBonus + premiumBonus) * levelMultiplier
}
// Expression for achievement determination
fun getAchievements(results: List<GameResult>): List<String> {
val achievements = mutableListOf<String>()
// Lambda expressions for data analysis
val totalGames = results.size
val avgScore = results.map { calculateFinalScore(it) }.average()
val maxLevel = results.maxOfOrNull { it.level } ?: 0
val fastestTime = results.minOfOrNull { it.timeInSeconds } ?: Int.MAX_VALUE
// When expressions for achievement logic
when {
totalGames >= 100 -> achievements.add("Century Player")
totalGames >= 50 -> achievements.add("Dedicated Gamer")
totalGames >= 10 -> achievements.add("Regular Player")
}
when {
avgScore >= 10000 -> achievements.add("Score Master")
avgScore >= 5000 -> achievements.add("High Scorer")
avgScore >= 1000 -> achievements.add("Good Player")
}
when {
maxLevel >= 20 -> achievements.add("Level Champion")
maxLevel >= 15 -> achievements.add("Advanced Player")
maxLevel >= 10 -> achievements.add("Intermediate Player")
}
when {
fastestTime < 30 -> achievements.add("Speed Demon")
fastestTime < 60 -> achievements.add("Quick Player")
}
return achievements
}
// String template expressions for reporting
fun generatePlayerReport(user: User, results: List<GameResult>): String {
val classification = classifyUser(user)
val totalScore = results.sumOf { calculateFinalScore(it) }
val achievements = getAchievements(results)
val bestResult = results.maxByOrNull { calculateFinalScore(it) }
return """
Player Report for ${user.name}
==============================
Classification: $classification
Total Games Played: ${results.size}
Total Score: ${String.format("%.2f", totalScore)}
Average Score: ${String.format("%.2f", if (results.isNotEmpty()) totalScore / results.size else 0.0)}
Best Game Score: ${bestResult?.let { String.format("%.2f", calculateFinalScore(it)) } ?: "N/A"}
Highest Level Reached: ${results.maxOfOrNull { it.level } ?: 0}
Achievements: ${if (achievements.isNotEmpty()) achievements.joinToString(", ") else "None yet"}
Premium Status: ${if (user.isPremium) "Active" else "Not Active"}
""".trimIndent()
}
}
// Main function demonstrating the usage
fun main() {
// Sample data creation using expressions
val users = listOf(
User("Alice", 25, true, 85.5),
User("Bob", 16, false, 72.0),
User("Charlie", 67, true, 90.0)
)
val gameResults = listOf(
GameResult(users[0], 12, 1500, 45),
GameResult(users[0], 15, 2200, 75),
GameResult(users[1], 8, 900, 120),
GameResult(users[2], 20, 3000, 30)
)
val analytics = GameAnalytics()
// Using expressions to generate reports
users.forEach { user ->
val userResults = gameResults.filter { it.user == user }
if (userResults.isNotEmpty()) {
println(analytics.generatePlayerReport(user, userResults))
println("\n" + "=".repeat(50) + "\n")
}
}
// Additional analysis using expressions
val topScorer = gameResults
.map { it.user to analytics.calculateFinalScore(it) }
.maxByOrNull { it.second }
?.first
println("Top scorer: ${topScorer?.name ?: "Unknown"}")
// Complex expression for statistics
val statisticsSummary = gameResults
.groupBy { it.user.name }
.mapValues { (_, results) ->
results.map { analytics.calculateFinalScore(it) }.average()
}
.entries
.sortedByDescending { it.value }
.joinToString("\n") { "${it.key}: ${String.format("%.2f", it.value)}" }
println("\nPlayer Rankings by Average Score:")
println(statisticsSummary)
}
When you run the complete example above, you’ll see output similar to this:
Player Report for Alice
==============================
Classification: Premium Adult
Total Games Played: 2
Total Score: 8775.00
Average Score: 4387.50
Best Game Score: 4950.00
Highest Level Reached: 15
Achievements: Regular Player, High Scorer, Advanced Player
Premium Status: Active
==================================================
Player Report for Bob
==============================
Classification: Teen Player
Total Games Played: 1
Total Score: 1350.00
Average Score: 1350.00
Best Game Score: 1350.00
Highest Level Reached: 8
Achievements: Good Player
Premium Status: Not Active
==================================================
Player Report for Charlie
==============================
Classification: Senior Player
Total Games Played: 1
Total Score: 9075.00
Average Score: 9075.00
Best Game Score: 9075.00
Highest Level Reached: 20
Achievements: High Scorer, Level Champion, Speed Demon
Premium Status: Active
==================================================
Top scorer: Charlie
Player Rankings by Average Score:
Charlie: 9075.00
Alice: 4387.50
Bob: 1350.00