Kotlin - Functions
Kotlin Functions
Functions are the building blocks of Kotlin programs. Learn how to declare, call, and use functions effectively with parameters, return types, and Kotlin's unique features.
Basic Function Declaration
Simple Function
fun greet() {
println("Hello, World!")
}
fun main() {
greet() // Call the function
}
Function with Parameters
fun greet(name: String) {
println("Hello, $name!")
}
fun main() {
greet("Alice") // Hello, Alice!
greet("Bob") // Hello, Bob!
}
Function with Return Value
fun add(a: Int, b: Int): Int {
return a + b
}
fun main() {
val result = add(5, 3)
println("5 + 3 = $result") // 5 + 3 = 8
}
Beginner Note: The basic syntax is
fun functionName(parameters): ReturnType { body }
. If there's no return value, you can omit the return type (it defaults to Unit
).
Function Syntax Breakdown
fun calculateArea(width: Double, height: Double): Double {
return width * height
}
// Breakdown:
// fun - keyword to declare function
// calculateArea - function name (camelCase)
// (width: Double, height: Double) - parameters with types
// : Double - return type
// { } - function body
Return Types
Functions with Different Return Types
fun getName(): String {
return "Alice"
}
fun getAge(): Int {
return 25
}
fun getHeight(): Double {
return 5.8
}
fun isStudent(): Boolean {
return true
}
fun printMessage(): Unit { // Unit is optional
println("Message printed")
}
// Unit return type can be omitted
fun printMessage2() {
println("Message printed")
}
Single Expression Functions
For simple functions, you can use the =
syntax:
// Traditional way
fun add(a: Int, b: Int): Int {
return a + b
}
// Single expression function
fun add(a: Int, b: Int): Int = a + b
// With type inference (return type can be omitted)
fun add(a: Int, b: Int) = a + b
// More examples
fun square(x: Int) = x * x
fun isEven(n: Int) = n % 2 == 0
fun greeting(name: String) = "Hello, $name!"
Function Parameters
Multiple Parameters
fun calculateBMI(weight: Double, height: Double): Double {
return weight / (height * height)
}
fun displayPerson(name: String, age: Int, city: String) {
println("Name: $name, Age: $age, City: $city")
}
fun main() {
val bmi = calculateBMI(70.0, 1.75)
displayPerson("Alice", 30, "New York")
}
Default Parameters
fun greet(name: String, greeting: String = "Hello") {
println("$greeting, $name!")
}
fun createUser(name: String, age: Int = 18, isActive: Boolean = true) {
println("User: $name, Age: $age, Active: $isActive")
}
fun main() {
greet("Alice") // Hello, Alice!
greet("Bob", "Hi") // Hi, Bob!
createUser("Charlie") // Uses default age and isActive
createUser("Diana", 25) // Uses default isActive
createUser("Eve", 30, false) // All parameters specified
}
Named Arguments
fun createAccount(username: String, email: String, age: Int, isVerified: Boolean = false) {
println("Account: $username, $email, $age, verified: $isVerified")
}
fun main() {
// Positional arguments
createAccount("alice", "[email protected]", 25, true)
// Named arguments (can be in any order)
createAccount(
email = "[email protected]",
username = "bob",
age = 30,
isVerified = true
)
// Mix of positional and named
createAccount("charlie", email = "[email protected]", age = 28)
}
Teacher Note: Named arguments are especially useful when functions have many parameters or when some parameters might be unclear from their position.
Variable Number of Arguments (vararg)
fun sum(vararg numbers: Int): Int {
var total = 0
for (number in numbers) {
total += number
}
return total
}
fun printItems(vararg items: String) {
for (item in items) {
println("- $item")
}
}
fun main() {
println(sum(1, 2, 3)) // 6
println(sum(10, 20, 30, 40)) // 100
printItems("Apple", "Banana", "Cherry")
// Spread operator with arrays
val numbers = intArrayOf(1, 2, 3, 4, 5)
println(sum(*numbers)) // * spreads the array
}
Local Functions
Functions can be declared inside other functions:
fun processData(data: List<String>) {
fun validateItem(item: String): Boolean {
return item.isNotBlank() && item.length > 2
}
fun formatItem(item: String): String {
return item.trim().lowercase()
}
val validItems = data.filter { validateItem(it) }
val formattedItems = validItems.map { formatItem(it) }
println("Processed items: $formattedItems")
}
fun main() {
processData(listOf("Apple", " BANANA ", "x", "Cherry"))
}
Function Types and Higher-Order Functions
Functions as Parameters
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
fun add(x: Int, y: Int) = x + y
fun multiply(x: Int, y: Int) = x * y
fun main() {
println(calculate(5, 3, ::add)) // 8 (using function reference)
println(calculate(5, 3, ::multiply)) // 15
// Using lambda expressions
println(calculate(5, 3) { x, y -> x - y }) // 2
println(calculate(5, 3) { x, y -> x / y }) // 1
}
Functions Returning Functions
fun createMultiplier(factor: Int): (Int) -> Int {
return { number -> number * factor }
}
fun main() {
val double = createMultiplier(2)
val triple = createMultiplier(3)
println(double(5)) // 10
println(triple(4)) // 12
}
Extension Functions
Add functionality to existing classes without modifying them:
// Extend String class
fun String.isPalindrome(): Boolean {
val cleaned = this.lowercase().replace(" ", "")
return cleaned == cleaned.reversed()
}
// Extend Int class
fun Int.isEven() = this % 2 == 0
// Extend List class
fun <T> List<T>.secondOrNull(): T? {
return if (this.size >= 2) this[1] else null
}
fun main() {
println("racecar".isPalindrome()) // true
println("hello".isPalindrome()) // false
println(4.isEven()) // true
println(7.isEven()) // false
val numbers = listOf(10, 20, 30)
println(numbers.secondOrNull()) // 20
}
Architecture Note: Extension functions are a powerful way to add functionality to existing APIs without modifying the original code. They're resolved statically, not through inheritance.
Infix Functions
Functions that can be called with infix notation:
infix fun Int.pow(exponent: Int): Int {
var result = 1
repeat(exponent) {
result *= this
}
return result
}
infix fun String.concat(other: String): String {
return this + other
}
fun main() {
// Regular function call
println(2.pow(3)) // 8
// Infix notation (no dots or parentheses)
println(2 pow 3) // 8
println("Hello" concat " World") // Hello World
// Built-in infix functions
val map = mapOf(1 to "one", 2 to "two") // 'to' is infix
}
Tail Recursive Functions
Optimize recursive functions to prevent stack overflow:
// Regular recursion (can cause stack overflow)
fun factorial(n: Int): Long {
return if (n <= 1) 1 else n * factorial(n - 1)
}
// Tail recursive version
tailrec fun factorialTailRec(n: Int, accumulator: Long = 1): Long {
return if (n <= 1) accumulator else factorialTailRec(n - 1, n * accumulator)
}
tailrec fun findElement(list: List<Int>, element: Int, index: Int = 0): Int {
return when {
index >= list.size -> -1
list[index] == element -> index
else -> findElement(list, element, index + 1)
}
}
fun main() {
println(factorialTailRec(10)) // 3628800
println(findElement(listOf(1, 3, 5, 7, 9), 5)) // 2
}
Function Overloading
Multiple functions with the same name but different parameters:
fun print(message: String) {
println("String: $message")
}
fun print(number: Int) {
println("Number: $number")
}
fun print(flag: Boolean) {
println("Boolean: $flag")
}
fun print(items: List<String>) {
println("List: ${items.joinToString(", ")}")
}
fun main() {
print("Hello") // String: Hello
print(42) // Number: 42
print(true) // Boolean: true
print(listOf("A", "B")) // List: A, B
}
Common Patterns and Best Practices
Input Validation
fun divide(a: Double, b: Double): Double {
require(b != 0.0) { "Division by zero is not allowed" }
return a / b
}
fun createUser(name: String, age: Int): String {
require(name.isNotBlank()) { "Name cannot be blank" }
require(age >= 0) { "Age cannot be negative" }
return "User($name, $age)"
}
fun main() {
try {
println(divide(10.0, 2.0)) // 5.0
println(divide(10.0, 0.0)) // Exception
} catch (e: IllegalArgumentException) {
println("Error: ${e.message}")
}
}
Function Documentation
/**
* Calculates the distance between two points
* @param x1 X coordinate of first point
* @param y1 Y coordinate of first point
* @param x2 X coordinate of second point
* @param y2 Y coordinate of second point
* @return The distance as a Double
*/
fun calculateDistance(x1: Double, y1: Double, x2: Double, y2: Double): Double {
val dx = x2 - x1
val dy = y2 - y1
return kotlin.math.sqrt(dx * dx + dy * dy)
}
Early Returns
fun processUser(user: User?): String {
if (user == null) return "No user provided"
if (user.name.isBlank()) return "Invalid user name"
if (user.age < 0) return "Invalid age"
return "Processing user: ${user.name}"
}
fun findFirst(numbers: List<Int>, condition: (Int) -> Boolean): Int? {
for (number in numbers) {
if (condition(number)) return number
}
return null
}
Best Practice: Use meaningful function names, keep functions small and focused on a single task, and validate input parameters when necessary.
Practice Exercises
- Write a function that calculates the area of a circle given its radius
- Create a function with default parameters for creating a user profile
- Implement a function that takes a variable number of strings and returns the longest one
- Write an extension function for String that counts vowels
- Create a tail recursive function to calculate Fibonacci numbers
Real-World Example
// Shopping cart example
data class Item(val name: String, val price: Double, val quantity: Int)
fun calculateTotal(items: List<Item>, tax: Double = 0.08): Double {
val subtotal = items.sumOf { it.price * it.quantity }
return subtotal * (1 + tax)
}
fun applyDiscount(total: Double, discountPercent: Double = 0.0): Double {
require(discountPercent in 0.0..100.0) { "Discount must be between 0 and 100" }
return total * (1 - discountPercent / 100)
}
fun formatCurrency(amount: Double): String = "$%.2f".format(amount)
// Extension function for better readability
fun List<Item>.totalCost(tax: Double = 0.08, discount: Double = 0.0): String {
val total = calculateTotal(this, tax)
val finalAmount = applyDiscount(total, discount)
return formatCurrency(finalAmount)
}
fun main() {
val cart = listOf(
Item("Laptop", 999.99, 1),
Item("Mouse", 25.99, 2),
Item("Keyboard", 79.99, 1)
)
println("Total: ${cart.totalCost(tax = 0.10, discount = 5.0)}")
}
Quick Quiz
- What keyword is used to declare a function in Kotlin?
- How do you create a single expression function?
- What does the
vararg
keyword do? - What is the default return type if none is specified?
Show answers
fun
- Use
=
instead of curly braces:fun add(a: Int, b: Int) = a + b
vararg
allows a function to accept a variable number of argumentsUnit
(equivalent tovoid
in Java)