Algorithms and Data Structures in Kotlin: In Practice
Kotlin is a modern multi-paradigm language actively used for Android development, backend, and even desktop applications. However, to write efficient and scalable code, knowing the syntax is not enough — you need to understand how algorithms and data structures work. In this article, we will break down key concepts in practice with examples in Kotlin. You will learn how to implement basic structures and algorithms, as well as how to use the language's built-in capabilities to optimize code.
1. Basic Data Structures: Arrays and Lists
In Kotlin, arrays are represented by the Array<T> type, and dynamic lists by List<T> (immutable) and MutableList<T> (mutable). However, in real-world tasks, it is often necessary to implement your own linked list to understand the principles of operation.
Singly Linked List in Kotlin
A linked list consists of nodes, each containing data and a reference to the next node. Let's look at a basic implementation:
class Node<T>(var value: T, var next: Node<T>? = null)
class LinkedList<T> { private var head: Node<T>? = null
fun add(value: T) { val newNode = Node(value) if (head == null) { head = newNode } else { var current = head while (current?.next != null) { current = current.next } current?.next = newNode } }
fun printAll() { var current = head while (current != null) { print("${current.value} ") current = current.next } println() }}
fun main() { val list = LinkedList<Int>() list.add(1) list.add(2) list.add(3) list.printAll() // Output: 1 2 3}This implementation demonstrates basic operations: adding to the end and traversal. In real projects, use Kotlin's built-in collections, but understanding the internal workings helps avoid errors when dealing with memory and performance.
2. Sorting Algorithms in Kotlin
Sorting is one of the most common operations. Kotlin has a built-in sorted() function, but for a deep understanding, it is useful to implement classic algorithms.
QuickSort
QuickSort is one of the most efficient algorithms with an average complexity of O(n log n). Implementation in Kotlin using recursion:
fun quickSort(arr: IntArray, low: Int, high: Int) { if (low < high) { val pivotIndex = partition(arr, low, high) quickSort(arr, low, pivotIndex - 1) quickSort(arr, pivotIndex + 1, high) }}
fun partition(arr: IntArray, low: Int, high: Int): Int { val pivot = arr[high] var i = low - 1 for (j in low until high) { if (arr[j] <= pivot) { i++ arr[i] = arr[j].also { arr[j] = arr[i] } } } arr[i + 1] = arr[high].also { arr[high] = arr[i + 1] } return i + 1}
fun main() { val array = intArrayOf(10, 7, 8, 9, 1, 5) quickSort(array, 0, array.size - 1) println(array.joinToString()) // Output: 1, 5, 7, 8, 9, 10}Note the use of also for swapping values — this is an idiomatic way in Kotlin. The algorithm is stable and works in-place, saving memory.
3. Searching: Binary and Linear
Searching for elements in data structures is the second most important task. Let's look at two approaches.
Linear Search
A simple but slow algorithm (O(n)). Suitable for unsorted data:
fun linearSearch(arr: IntArray, target: Int): Int { for (i in arr.indices) { if (arr[i] == target) return i } retu