Functions
Overview
Functions are the fundamental building blocks of Kotlin programs. Kotlin supports functional programming paradigms and provides powerful function features, including higher-order functions, lambda expressions, extension functions, and more. This chapter provides comprehensive coverage of function definition, usage, and advanced features in Kotlin.
Function Basics
Basic Function Definition
kotlin
// Basic function syntax
fun functionName(parameter1: Type1, parameter2: Type2): ReturnType {
// Function body
return result
}
// Simple function example
fun greet(name: String): String {
return "Hello, $name!"
}
// Function with no return value (returns Unit)
fun printMessage(message: String) {
println(message)
}
// Explicitly declaring Unit return type
fun printMessageExplicit(message: String): Unit {
println(message)
}
fun main() {
val greeting = greet("Kotlin")
println(greeting)
printMessage("This is a message")
printMessageExplicit("This is another message")
}Single-Expression Functions
kotlin
// Single-expression functions
fun add(a: Int, b: Int): Int = a + b
// Type inference for single-expression functions
fun multiply(a: Int, b: Int) = a * b
// More complex single-expression functions
fun max(a: Int, b: Int) = if (a > b) a else b
// String processing
fun formatName(firstName: String, lastName: String) = "$firstName $lastName"
// Mathematical calculation
fun circleArea(radius: Double) = Math.PI * radius * radius
fun main() {
println("Addition: ${add(5, 3)}")
println("Multiplication: ${multiply(4, 6)}")
println("Maximum: ${max(10, 15)}")
println("Formatted name: ${formatName("John", "Doe")}")
println("Circle area: ${"%.2f".format(circleArea(5.0))}")
}Function Parameters
Default Parameters
kotlin
// Function with default parameters
fun createUser(
name: String,
age: Int = 18,
email: String = "unknown@example.com",
isActive: Boolean = true
): String {
return "User(name=$name, age=$age, email=$email, active=$isActive)"
}
// Multiple default parameters
fun connectToDatabase(
host: String = "localhost",
port: Int = 5432,
database: String = "mydb",
username: String = "user",
password: String = "password"
): String {
return "Connecting to $host:$port/$database as user: $username"
}
fun main() {
// Using default parameters
println(createUser("Alice"))
println(createUser("Bob", 25))
println(createUser("Charlie", 30, "charlie@example.com"))
// Database connection examples
println(connectToDatabase())
println(connectToDatabase("remote-server"))
println(connectToDatabase("remote-server", 3306, "production"))
}Named Parameters
kotlin
fun createProduct(
name: String,
price: Double,
category: String,
inStock: Boolean = true,
description: String = ""
): String {
return "Product: $name, Price: $$price, Category: $category, InStock: $inStock"
}
fun main() {
// Using named parameters
val product1 = createProduct(
name = "Laptop",
price = 999.99,
category = "Electronics"
)
// Changing parameter order
val product2 = createProduct(
category = "Books",
name = "Kotlin Programming Guide",
price = 49.99,
description = "Best resource for learning Kotlin"
)
// Mixing positional and named parameters
val product3 = createProduct("Phone", 699.99, category = "Electronics", inStock = false)
println(product1)
println(product2)
println(product3)
}Vararg Parameters
kotlin
// Vararg function
fun sum(vararg numbers: Int): Int {
var total = 0
for (number in numbers) {
total += number
}
return total
}
// Vararg combined with other parameters
fun formatMessage(prefix: String, vararg messages: String, suffix: String = ""): String {
val combined = messages.joinToString(" ")
return "$prefix $combined $suffix".trim()
}
// Generic vararg
fun <T> printAll(vararg items: T) {
for (item in items) {
println(item)
}
}
fun main() {
// Using vararg
println("Sum: ${sum(1, 2, 3, 4, 5)}")
println("Sum: ${sum(10, 20)}")
println("Sum: ${sum()}") // Empty parameters
// Passing array to vararg
val numbers = intArrayOf(1, 2, 3, 4, 5)
println("Array sum: ${sum(*numbers)}") // Using spread operator
// Formatting messages
println(formatMessage("Error:", "File", "not found", suffix = "!"))
// Generic vararg
printAll("Hello", "World", "Kotlin")
printAll(1, 2, 3, 4, 5)
}Higher-Order Functions
Functions as Parameters
kotlin
// Higher-order function that accepts a function as parameter
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
// Higher-order function for list operations
fun processNumbers(numbers: List<Int>, processor: (Int) -> Int): List<Int> {
return numbers.map(processor)
}
// Conditional filter function
fun <T> filterItems(items: List<T>, predicate: (T) -> Boolean): List<T> {
return items.filter(predicate)
}
fun main() {
// Passing functions as parameters
val result1 = calculate(10, 5) { a, b -> a + b }
val result2 = calculate(10, 5) { a, b -> a * b }
val result3 = calculate(10, 5) { a, b -> a - b }
println("Addition: $result1")
println("Multiplication: $result2")
println("Subtraction: $result3")
// Processing number list
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = processNumbers(numbers) { it * 2 }
val squared = processNumbers(numbers) { it * it }
println("Original numbers: $numbers")
println("Doubled: $doubled")
println("Squared: $squared")
// Filter examples
val words = listOf("apple", "banana", "cherry", "date")
val longWords = filterItems(words) { it.length > 5 }
val wordsWithA = filterItems(words) { it.contains('a') }
println("Long words: $longWords")
println("Words containing 'a': $wordsWithA")
}Functions as Return Values
kotlin
// Function that returns a function
fun createMultiplier(factor: Int): (Int) -> Int {
return { number -> number * factor }
}
// Create validator function
fun createValidator(minLength: Int): (String) -> Boolean {
return { input -> input.length >= minLength }
}
// Create formatter
fun createFormatter(prefix: String, suffix: String): (String) -> String {
return { content -> "$prefix$content$suffix" }
}
// Function composition
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}
fun main() {
// Using returned functions
val double = createMultiplier(2)
val triple = createMultiplier(3)
println("2 * 5 = ${double(5)}")
println("3 * 7 = ${triple(7)}")
// Validator examples
val passwordValidator = createValidator(8)
val usernameValidator = createValidator(3)
println("Password '12345678' valid: ${passwordValidator("12345678")}")
println("Username 'ab' valid: ${usernameValidator("ab")}")
// Formatter examples
val htmlFormatter = createFormatter("<p>", "</p>")
val markdownFormatter = createFormatter("**", "**")
println(htmlFormatter("Hello World"))
println(markdownFormatter("Bold Text"))
// Function composition example
val addOne = { x: Int -> x + 1 }
val multiplyByTwo = { x: Int -> x * 2 }
val addOneThenDouble = compose(multiplyByTwo, addOne)
println("(5 + 1) * 2 = ${addOneThenDouble(5)}")
}Lambda Expressions
Lambda Syntax
kotlin
fun main() {
// Basic lambda syntax
val sum = { a: Int, b: Int -> a + b }
println("Lambda sum: ${sum(3, 4)}")
// Single parameter lambda (using it)
val square = { x: Int -> x * x }
val squareIt = { it: Int -> it * it } // Explicit it
val squareImplicit: (Int) -> Int = { it * it } // Implicit it
println("Square: ${square(5)}")
println("Square (it): ${squareIt(6)}")
println("Square (implicit): ${squareImplicit(7)}")
// No parameter lambda
val greeting = { "Hello, World!" }
println(greeting())
// Multi-line lambda
val complexOperation = { x: Int, y: Int ->
val temp = x * 2
val result = temp + y
println("Intermediate result: $temp")
result // Last line is the return value
}
println("Complex operation result: ${complexOperation(5, 3)}")
}Lambda with Collection Operations
kotlin
data class Person(val name: String, val age: Int, val city: String)
fun main() {
val people = listOf(
Person("Alice", 25, "New York"),
Person("Bob", 30, "London"),
Person("Charlie", 35, "Tokyo"),
Person("Diana", 28, "Paris"),
Person("Eve", 32, "Berlin")
)
// map - transformation
val names = people.map { it.name }
val ages = people.map { person -> person.age }
val nameAgeMap = people.map { "${it.name} (${it.age})" }
println("Names: $names")
println("Ages: $ages")
println("Name-Age: $nameAgeMap")
// filter - filtering
val adults = people.filter { it.age >= 30 }
val europeans = people.filter { it.city in listOf("London", "Paris", "Berlin") }
println("Age 30+: ${adults.map { it.name }}")
println("Europeans: ${europeans.map { it.name }}")
// find - finding
val firstAdult = people.find { it.age >= 30 }
val personInTokyo = people.find { it.city == "Tokyo" }
println("First adult: ${firstAdult?.name}")
println("Person in Tokyo: ${personInTokyo?.name}")
// groupBy - grouping
val peopleByCity = people.groupBy { it.city }
val peopleByAgeGroup = people.groupBy {
when {
it.age < 30 -> "Young"
it.age < 35 -> "Middle-aged"
else -> "Mature"
}
}
println("By city: $peopleByCity")
println("By age group: $peopleByAgeGroup")
// sortedBy - sorting
val sortedByAge = people.sortedBy { it.age }
val sortedByName = people.sortedBy { it.name }
println("Sorted by age: ${sortedByAge.map { "${it.name}(${it.age})" }}")
println("Sorted by name: ${sortedByName.map { it.name }}")
}Extension Functions
Basic Extension Functions
kotlin
// Add extension function to String class
fun String.isPalindrome(): Boolean {
val cleaned = this.lowercase().replace(" ", "")
return cleaned == cleaned.reversed()
}
// Add extension function to Int class
fun Int.isEven(): Boolean = this % 2 == 0
fun Int.isOdd(): Boolean = this % 2 != 0
fun Int.factorial(): Long {
return if (this <= 1) 1 else this * (this - 1).factorial()
}
// Add extension function to List
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null
fun <T> List<T>.penultimate(): T? = if (size >= 2) this[size - 2] else null
// Extension property
val String.wordCount: Int
get() = this.split("\\s+".toRegex()).size
val List<Int>.average: Double
get() = if (isEmpty()) 0.0 else sum().toDouble() / size
fun main() {
// Using string extension functions
val text1 = "A man a plan a canal Panama"
val text2 = "Hello World"
println("'$text1' is palindrome: ${text1.isPalindrome()}")
println("'$text2' is palindrome: ${text2.isPalindrome()}")
println("'$text1' word count: ${text1.wordCount}")
// Using integer extension functions
val number = 8
println("$number is even: ${number.isEven()}")
println("$number is odd: ${number.isOdd()}")
println("$number factorial: ${number.factorial()}")
// Using list extension functions
val numbers = listOf(1, 2, 3, 4, 5)
println("List: $numbers")
println("Second element: ${numbers.secondOrNull()}")
println("Penultimate element: ${numbers.penultimate()}")
println("Average: ${numbers.average}")
}Extension Function Scope
kotlin
class MathUtils {
// Extension function inside class
fun Int.square(): Int = this * this
fun Double.round(decimals: Int): Double {
val factor = kotlin.math.pow(10.0, decimals.toDouble())
return kotlin.math.round(this * factor) / factor
}
fun demonstrateExtensions() {
println("5 squared: ${5.square()}")
println("3.14159 rounded to 2 decimals: ${3.14159.round(2)}")
}
}
// Top-level extension function
fun String.toTitleCase(): String {
return this.split(" ").joinToString(" ") { word ->
word.lowercase().replaceFirstChar { it.uppercase() }
}
}
fun main() {
val mathUtils = MathUtils()
mathUtils.demonstrateExtensions()
// Top-level extension functions can be used anywhere
val text = "hello world kotlin"
println("Title case: ${text.toTitleCase()}")
}Inline Functions
Inline Function Basics
kotlin
// Inline function
inline fun measureTime(action: () -> Unit): Long {
val startTime = System.currentTimeMillis()
action()
val endTime = System.currentTimeMillis()
return endTime - startTime
}
// Inline function with parameters
inline fun <T> withLogging(name: String, action: () -> T): T {
println("Starting: $name")
val result = action()
println("Completed: $name")
return result
}
// noinline parameter
inline fun processData(
data: List<Int>,
transform: (Int) -> Int,
noinline logger: (String) -> Unit
) {
logger("Starting data processing")
val result = data.map(transform)
logger("Processing complete, result: $result")
}
fun main() {
// Using inline function
val time = measureTime {
Thread.sleep(100) // Simulate time-consuming operation
println("Performed some operation")
}
println("Execution time: ${time}ms")
// Inline function with return value
val result = withLogging("Data calculation") {
val numbers = listOf(1, 2, 3, 4, 5)
numbers.sum()
}
println("Calculation result: $result")
// Using noinline parameter
processData(
data = listOf(1, 2, 3, 4, 5),
transform = { it * 2 },
logger = { message -> println("[LOG] $message") }
)
}Local Functions
Nested Functions
kotlin
fun processUser(name: String, email: String): String {
// Local functions
fun validateName(name: String): Boolean {
return name.isNotBlank() && name.length >= 2
}
fun validateEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
fun formatResult(name: String, email: String): String {
return "User: $name, Email: $email"
}
// Using local functions
return when {
!validateName(name) -> "Invalid name"
!validateEmail(email) -> "Invalid email"
else -> formatResult(name, email)
}
}
// More complex local function example
fun calculateStatistics(numbers: List<Double>): Map<String, Double> {
fun mean(): Double = numbers.sum() / numbers.size
fun variance(): Double {
val avg = mean()
return numbers.map { (it - avg) * (it - avg) }.sum() / numbers.size
}
fun standardDeviation(): Double = kotlin.math.sqrt(variance())
fun median(): Double {
val sorted = numbers.sorted()
val size = sorted.size
return if (size % 2 == 0) {
(sorted[size / 2 - 1] + sorted[size / 2]) / 2
} else {
sorted[size / 2]
}
}
return mapOf(
"mean" to mean(),
"variance" to variance(),
"standardDeviation" to standardDeviation(),
"median" to median(),
"min" to (numbers.minOrNull() ?: 0.0),
"max" to (numbers.maxOrNull() ?: 0.0)
)
}
fun main() {
// Test user processing
println(processUser("Alice", "alice@example.com"))
println(processUser("", "invalid-email"))
println(processUser("Bob", "bob@test.com"))
// Test statistics calculation
val data = listOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)
val stats = calculateStatistics(data)
println("\nData statistics:")
stats.forEach { (key, value) ->
println("$key: ${"%.2f".format(value)}")
}
}Tail Recursive Functions
tailrec Keyword
kotlin
// Tail recursive factorial
tailrec fun factorial(n: Long, accumulator: Long = 1): Long {
return if (n <= 1) {
accumulator
} else {
factorial(n - 1, n * accumulator)
}
}
// Tail recursive fibonacci
tailrec fun fibonacci(n: Int, a: Long = 0, b: Long = 1): Long {
return when (n) {
0 -> a
1 -> b
else -> fibonacci(n - 1, b, a + b)
}
}
// Tail recursive sum
tailrec fun sumList(list: List<Int>, accumulator: Int = 0): Int {
return if (list.isEmpty()) {
accumulator
} else {
sumList(list.drop(1), accumulator + list.first())
}
}
// Tail recursive find
tailrec fun <T> findElement(
list: List<T>,
element: T,
index: Int = 0
): Int {
return when {
index >= list.size -> -1
list[index] == element -> index
else -> findElement(list, element, index + 1)
}
}
fun main() {
// Test tail recursive functions
println("Factorial of 10: ${factorial(10)}")
println("Factorial of 20: ${factorial(20)}")
println("Fibonacci sequence:")
for (i in 0..10) {
print("${fibonacci(i)} ")
}
println()
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
println("List sum: ${sumList(numbers)}")
val fruits = listOf("apple", "banana", "cherry", "date")
println("Find 'cherry': ${findElement(fruits, "cherry")}")
println("Find 'grape': ${findElement(fruits, "grape")}")
}Practical Examples
Functional Programming Style Data Processing
kotlin
data class Order(
val id: String,
val customerId: String,
val items: List<OrderItem>,
val status: OrderStatus
)
data class OrderItem(
val productId: String,
val name: String,
val price: Double,
val quantity: Int
)
enum class OrderStatus { PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED }
class OrderProcessor {
// Higher-order function: Order filter
fun filterOrders(
orders: List<Order>,
predicate: (Order) -> Boolean
): List<Order> = orders.filter(predicate)
// Calculate order total
fun Order.totalAmount(): Double = items.sumOf { it.price * it.quantity }
// Order statistics
fun generateStatistics(orders: List<Order>): Map<String, Any> {
val totalOrders = orders.size
val totalRevenue = orders.sumOf { it.totalAmount() }
val averageOrderValue = if (totalOrders > 0) totalRevenue / totalOrders else 0.0
val statusCounts = orders.groupingBy { it.status }.eachCount()
val topCustomers = orders
.groupBy { it.customerId }
.mapValues { (_, customerOrders) ->
customerOrders.sumOf { it.totalAmount() }
}
.toList()
.sortedByDescending { it.second }
.take(5)
return mapOf(
"totalOrders" to totalOrders,
"totalRevenue" to totalRevenue,
"averageOrderValue" to averageOrderValue,
"statusCounts" to statusCounts,
"topCustomers" to topCustomers
)
}
// Order processing pipeline
fun processOrdersPipeline(
orders: List<Order>,
filters: List<(Order) -> Boolean> = emptyList(),
transformations: List<(Order) -> Order> = emptyList()
): List<Order> {
return orders
.let { orderList ->
filters.fold(orderList) { acc, filter -> acc.filter(filter) }
}
.let { filteredOrders ->
transformations.fold(filteredOrders) { acc, transform ->
acc.map(transform)
}
}
}
}
fun main() {
val orders = listOf(
Order("1", "customer1", listOf(
OrderItem("p1", "Laptop", 999.99, 1),
OrderItem("p2", "Mouse", 29.99, 2)
), OrderStatus.DELIVERED),
Order("2", "customer2", listOf(
OrderItem("p3", "Keyboard", 79.99, 1)
), OrderStatus.SHIPPED),
Order("3", "customer1", listOf(
OrderItem("p1", "Laptop", 999.99, 2)
), OrderStatus.PROCESSING),
Order("4", "customer3", listOf(
OrderItem("p4", "Monitor", 299.99, 1),
OrderItem("p2", "Mouse", 29.99, 1)
), OrderStatus.PENDING)
)
val processor = OrderProcessor()
// Using extension function to calculate total
with(processor) {
orders.forEach { order ->
println("Order ${order.id} total: ${"%.2f".format(order.totalAmount())}")
}
}
// Filter high-value orders
val highValueOrders = processor.filterOrders(orders) {
with(processor) { it.totalAmount() > 500 }
}
println("\nHigh-value orders: ${highValueOrders.map { it.id }}")
// Generate statistics report
val stats = processor.generateStatistics(orders)
println("\nOrder statistics:")
stats.forEach { (key, value) ->
println("$key: $value")
}
// Using processing pipeline
val processedOrders = processor.processOrdersPipeline(
orders = orders,
filters = listOf(
{ it.status != OrderStatus.CANCELLED },
{ with(processor) { it.totalAmount() > 100 } }
)
)
println("\nProcessed orders: ${processedOrders.map { it.id }}")
}Best Practices
1. Function Design Principles
kotlin
// Good practice: Single responsibility
fun calculateTax(amount: Double, rate: Double): Double = amount * rate
fun formatCurrency(amount: Double): String = "$%.2f".format(amount)
fun validateEmail(email: String): Boolean =
email.contains("@") && email.contains(".")
// Avoid: Function doing too many things
fun badProcessOrder(order: Order): String {
// Validation, calculation, formatting, sending email, etc.
// Such functions are hard to test and maintain
return ""
}2. Use Appropriate Function Types
kotlin
// For simple operations, use single-expression functions
fun isPositive(number: Int) = number > 0
// For complex logic, use full function body
fun processComplexData(data: List<String>): Map<String, Int> {
val result = mutableMapOf<String, Int>()
for (item in data) {
// Complex processing logic
val processed = item.trim().lowercase()
result[processed] = result.getOrDefault(processed, 0) + 1
}
return result
}
// For reusable logic, create extension functions
fun String.isValidPhoneNumber(): Boolean {
return this.matches(Regex("\\d{3}-\\d{3}-\\d{4}"))
}3. Use Higher-Order Functions Appropriately
kotlin
// Good practice: Use standard library higher-order functions
fun processItems(items: List<String>): List<String> {
return items
.filter { it.isNotBlank() }
.map { it.trim().lowercase() }
.distinct()
.sorted()
}
// Create reusable higher-order functions
fun <T, R> List<T>.mapNotNull(transform: (T) -> R?): List<R> {
return this.mapNotNull(transform)
}Next Steps
After mastering the various function features, let's learn about arrays and collection structures in Kotlin.
Next Chapter: Arrays and Collections
Exercises
- Create a calculator class that uses higher-order functions to implement various mathematical operations
- Implement a text processing library containing various string extension functions
- Write a functional-style data validation system
- Create a tree traversal algorithm using tail recursion
- Design a function composition system that allows chaining multiple processing functions