Skip to content

Kotlin Functions — Complete Guide with Examples

DodaTech Updated 2026-06-28 9 min read

In this tutorial, you will learn about Kotlin Functions. We cover key concepts, practical examples, and best practices to help you master this topic.

Kotlin functions are declared with the fun keyword, support default parameter values, named arguments, single-expression bodies, extension functions, and higher-order capabilities that make them more expressive than Java methods.

What You'll Learn

  • Declare functions with fun, parameters, and return types
  • Use default parameter values and named arguments
  • Write single-expression functions with =
  • Create extension functions to add behavior to existing classes
  • Work with higher-order functions that accept or return functions
  • Use lambda expressions and anonymous functions
  • Apply scope functions: let, apply, run, with, also

Why It Matters

Functions are the building blocks of every program. Kotlin's function features reduce boilerplate significantly compared to Java. Default parameters eliminate the need for method overloading. Named arguments improve readability. Extension functions let you add methods to classes you do not own. Higher-order functions enable Functional Programming patterns that make code more declarative and less error-prone.

Real-World Use

DodaTech uses extension functions to add logging, Serialization, and security features to built-in Android classes. Higher-order functions power the event handling system in the configuration tool, where callbacks are passed as lambda parameters. Scope functions like let and apply are used extensively for null-safe initialization chains.

Learning Path

flowchart LR
  A[Control Flow] --> B[Functions\nYou are here]
  B --> C[Null Safety]
  style B fill:#f90,color:#fff

Function Declaration

A function starts with the fun keyword, followed by the name, parameters in parentheses, and the return type.

fun greet(name: String): String {
    return "Hello, $name!"
}

fun main() {
    val message = greet("Alice")
    println(message)  // Output: Hello, Alice!
}

Output: The function takes a name parameter and returns a greeting string.

Single-Expression Functions

When a function returns a single expression, use the shorthand syntax with =.

fun double(x: Int): Int = x * 2
fun square(x: Int) = x * x  // Return type inferred
fun max(a: Int, b: Int) = if (a > b) a else b

fun main() {
    println(double(5))      // Output: 10
    println(square(4))      // Output: 16
    println(max(10, 7))     // Output: 10
}

Output: Each function body is a single expression. The return type is inferred for shorter syntax.

Default Parameter Values

Kotlin functions can specify default values for parameters, reducing the need for overloaded methods.

fun createUser(
    name: String,
    age: Int = 18,
    email: String = "no-email@example.com",
    isActive: Boolean = true
): String {
    return "User(name=$name, age=$age, email=$email, active=$isActive)"
}

fun main() {
    println(createUser("Alice"))
    println(createUser("Bob", 25))
    println(createUser("Charlie", email = "charlie@example.com"))
}

Output:

User(name=Alice, age=18, email=no-email@example.com, active=true)
User(name=Bob, age=25, email=no-email@example.com, active=true)
User(name=Charlie, age=18, email=charlie@example.com, active=true)

Default values let you call the function with any subset of parameters. Named arguments allow skipping middle parameters.

Named Arguments

Use named arguments to improve readability and skip parameters with default values.

fun formatDate(year: Int, month: Int, day: Int, separator: String = "-"): String {
    return "$year$separator${month.toString().padStart(2, '0')}$separator${day.toString().padStart(2, '0')}"
}

fun main() {
    // Named arguments make the call self-documenting
    println(formatDate(year = 2026, month = 6, day = 28))
    println(formatDate(day = 1, month = 1, year = 2025, separator = "/"))
}

Output:

2026-06-28
2025/01/01

Named arguments can appear in any order. They are especially useful when a function has many parameters of the same type.

Extension Functions

Extension functions add new methods to existing classes without modifying their source code.

fun String.isEmail(): Boolean {
    return this.contains("@") && this.contains(".")
}

fun String.abbreviate(maxLength: Int): String {
    return if (this.length <= maxLength) this
           else this.take(maxLength - 3) + "..."
}

fun Int.isPrime(): Boolean {
    if (this < 2) return false
    for (i in 2..kotlin.math.sqrt(this.toDouble()).toInt()) {
        if (this % i == 0) return false
    }
    return true
}

fun main() {
    println("test@example.com".isEmail())  // Output: true
    println("hello@".isEmail())            // Output: false
    
    println("Hello, Kotlin World!".abbreviate(10))  // Output: Hello Kot...
    
    println(7.isPrime())   // Output: true
    println(10.isPrime())  // Output: false
}

Output: Each extension function works as if it were a member of the original class.

Higher-Order Functions

Higher-order functions take functions as parameters or return functions.

fun operateOnNumbers(
    a: Int,
    b: Int,
    operation: (Int, Int) -> Int
): Int {
    return operation(a, b)
}

fun main() {
    val sum = operateOnNumbers(10, 5) { x, y -> x + y }
    val product = operateOnNumbers(10, 5) { x, y -> x * y }
    val difference = operateOnNumbers(10, 5) { x, y -> x - y }
    
    println("Sum: $sum")          // Output: Sum: 15
    println("Product: $product")  // Output: Product: 50
    println("Difference: $difference") // Output: Difference: 5
    
    // Passing a function reference
    fun multiply(x: Int, y: Int) = x * y
    val result = operateOnNumbers(6, 7, ::multiply)
    println("Multiply: $result")  // Output: Multiply: 42
}

Output: The higher-order function accepts different operations as lambdas and produces different results.

Lambda Expressions

Lambdas are anonymous functions defined with curly braces.

fun main() {
    // Lambda assigned to a variable
    val square: (Int) -> Int = { x -> x * x }
    println(square(5))  // Output: 25
    
    // Using 'it' for single parameter
    val double: (Int) -> Int = { it * 2 }
    println(double(10))  // Output: 20
    
    // Lambda with multiple parameters
    val add: (Int, Int) -> Int = { a, b -> a + b }
    println(add(3, 4))  // Output: 7
    
    // Lambda with collections
    val numbers = listOf(1, 2, 3, 4, 5)
    val evenNumbers = numbers.filter { it % 2 == 0 }
    val squared = numbers.map { it * it }
    
    println(evenNumbers)  // Output: [2, 4]
    println(squared)      // Output: [1, 4, 9, 16, 25]
}

Output: Lambdas work inline with collection operations, making the code declarative.

Scope Functions

Kotlin provides five scope functions for executing code within the context of an object.

data class Person(var name: String, var age: Int, var email: String = "")

fun main() {
    // apply: modifies object and returns it
    val person1 = Person("Alice", 25).apply {
        email = "alice@example.com"
        age = 26
    }
    println(person1)  // Output: Person(name=Alice, age=26, email=alice@example.com)
    
    // let: executes code and returns result, useful for null checks
    val name: String? = "Bob"
    val length = name?.let {
        println("Name is not null: $it")
        it.length
    }
    println(length)  // Output: 3 (and prints the message)
    
    // run: similar to let but uses 'this' context
    val info = person1.run {
        "Name: $name, Age: $age, Email: $email"
    }
    println(info)  // Output: Name: Alice, Age: 26, Email: alice@example.com
    
    // with: non-extension version of run
    val description = with(person1) {
        "$name is $age years old"
    }
    println(description)  // Output: Alice is 26 years old
    
    // also: executes side effects and returns original object
    val person2 = Person("Charlie", 30).also {
        println("Creating person: ${it.name}")
        it.email = "charlie@example.com"
    }
    println(person2)  // Output: Person(name=Charlie, age=30, email=charlie@example.com)
}

Output: Each scope function serves a different purpose. apply is for configuration, let for null checks, run for transformations, with for calling multiple methods, and also for side effects.

Common Mistakes

  1. Overusing scope functions for simple operations: Scope functions add context but can hurt readability when nested. Use let only for null checks, not as a general replacement for regular variable access.

  2. Confusing it and this: In lambdas with a single parameter, it refers to the parameter. In scope functions using this (apply, run, with), this refers to the context object. Mixing them causes subtle bugs.

  3. Forgetting the return type of Unit: A function without an explicit return type that does not return a value has return type Unit. This is similar to void in Java.

  4. Not using default parameters to reduce overloads: In Java, you write multiple overloaded methods. In Kotlin, one function with default parameters replaces them all.

  5. Using varargs incorrectly: The vararg parameter must be the last parameter. Spread the array with * when passing an existing array.

  6. Ignoring trailing lambda syntax: When the last parameter is a lambda, Kotlin allows moving it outside the parentheses. This is idiomatic for collection functions.

Practice Questions

  1. What is the advantage of default parameter values over method overloading?

Answer: One function declaration handles all variations. Callers choose which parameters to specify. This reduces code duplication and maintenance burden compared to multiple overloaded methods.

  1. What is the difference between let and apply?

Answer: let uses it for the context object and returns the lambda result. apply uses this and returns the context object itself. Use let for transformations, apply for object configuration.

  1. How do you add a new method to a class you do not own?

Answer: Write an extension function. Declare the function with the class name as the receiver: fun ClassName.newMethod().

  1. What is a higher-order function?

Answer: A function that takes another function as a parameter or returns a function. Examples: filter, map, forEach.

  1. Challenge: Write an extension function for String that capitalizes the first letter of each word, then write a higher-order function that applies any String transformation to a list of strings.

Answer:

fun String.capitalizeWords(): String {
    return this.split(" ").joinToString(" ") { word ->
        word.replaceFirstChar { it.uppercase() }
    }
}

fun transformStrings(strings: List<String>, transform: (String) -> String): List<String> {
    return strings.map(transform)
}

fun main() {
    val words = listOf("hello world", "kotlin is fun", "extension functions")
    val capitalized = transformStrings(words) { it.capitalizeWords() }
    println(capitalized)  // Output: [Hello World, Kotlin Is Fun, Extension Functions]
}

Mini Project

Create a collection of utility extension functions for String and Int types. Requirements:

  • isEmail: validates basic email format
  • isPhoneNumber: validates a 10-digit phone number
  • truncate: truncates a string with ellipsis
  • factorial: Int extension that computes factorial
  • isPalindrome: checks if a string reads the same backward
  • toWords: splits a string into words with punctuation removed

Write unit tests for each extension function. This project applies extension functions, higher-order logic, and string manipulation.

FAQ

What is the difference between a lambda and an anonymous function?

Lambdas use curly braces and cannot specify the return type explicitly. Anonymous functions use the fun keyword and can specify the return type. Both are function literals.

Can extension functions access private members?

No. Extension functions have the same access as regular functions outside the class. They cannot access private or protected members.

What is the trailing lambda syntax?

When the last parameter of a function is a lambda, you can move it outside the parentheses: 'list.filter { it > 0 }' instead of 'list.filter({ it > 0 })'.

Are Kotlin functions first-class citizens?

Yes. Functions can be assigned to variables, passed as arguments, and returned from other functions. This enables functional programming patterns.

How do I write a function with a variable number of arguments?

Use the vararg modifier: 'fun sum(vararg numbers: Int): Int'. Inside the function, vararg is treated as an Array.

What's Next

After mastering functions, learn null safety to handle nullable types safely. You can also explore collections for working with lists, sets, and maps.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro