Kotlin Functions — Complete Guide with Examples
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
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.
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.
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.
Not using default parameters to reduce overloads: In Java, you write multiple overloaded methods. In Kotlin, one function with default parameters replaces them all.
Using varargs incorrectly: The vararg parameter must be the last parameter. Spread the array with * when passing an existing array.
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
- 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.
- 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.
- 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().
- What is a higher-order function?
Answer: A function that takes another function as a parameter or returns a function. Examples: filter, map, forEach.
- 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'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