Skip to content

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

  1. Create a calculator class that uses higher-order functions to implement various mathematical operations
  2. Implement a text processing library containing various string extension functions
  3. Write a functional-style data validation system
  4. Create a tree traversal algorithm using tail recursion
  5. Design a function composition system that allows chaining multiple processing functions

Content is for learning and research only.