Kotlin Collections Interview Questions
1. What are the main types of collections in Kotlin, and how do they differ?
Kotlin provides two main types of collections: **read-only collections** and **mutable collections**. The primary difference is whether the elements in the collection can be modified after creation.
- Read-only collections: Created using functions like `listOf`, `mapOf`, and `setOf`. These collections cannot be modified, but they are not immutable as the underlying structure may still change.
- Mutable collections: Created using functions like `mutableListOf`, `mutableMapOf`, and `mutableSetOf`. These allow adding, removing, or updating elements.
Use cases for read-only collections include data that should not change, like configuration settings. Mutable collections are ideal for dynamic datasets, such as user inputs or results from API calls.
Example:
fun main() {
val readOnlyList = listOf(1, 2, 3)
// readOnlyList.add(4) // Compile-time error
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4) // Allowed
println(mutableList) // Output: [1, 2, 3, 4]
}
2. How do `filter`, `map`, and `reduce` operations work in Kotlin collections?
These operations are commonly used in functional programming and provide powerful ways to process collections:
- `filter`: Returns a collection containing elements that match a given condition.
- `map`: Transforms each element of a collection and returns a new collection with the transformed elements.
- `reduce`: Accumulates values starting from the first element, applying a lambda to combine elements into a single result.
These operations make the code concise and readable, especially for tasks like filtering data, transforming datasets, or aggregating results.
Example:
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter { it % 2 == 0 }
println("Evens: $evens") // Output: Evens: [2, 4]
val squares = numbers.map { it * it }
println("Squares: $squares") // Output: Squares: [1, 4, 9, 16, 25]
val sum = numbers.reduce { acc, num -> acc + num }
println("Sum: $sum") // Output: Sum: 15
}
3. What is the difference between `forEach` and `map` in Kotlin collections?
Both `forEach` and `map` are used to iterate over collections, but they serve different purposes:
- `forEach`: Iterates over the collection to perform a side effect (e.g., logging or modifying external state). It does not return a value.
- `map`: Transforms elements in a collection and returns a new collection with the transformed elements.
Example:
fun main() {
val numbers = listOf(1, 2, 3)
// Using forEach for a side effect
numbers.forEach { println("Processing $it") }
// Output:
// Processing 1
// Processing 2
// Processing 3
// Using map for transformation
val doubled = numbers.map { it * 2 }
println(doubled) // Output: [2, 4, 6]
}
Use `forEach` for actions that don't require a result, and `map` when you need a new collection as the result of transformations.
4. How does `groupBy` work in Kotlin collections?
The `groupBy` function in Kotlin allows you to group elements of a collection based on a given key selector function. It returns a `Map
Grouping is especially useful for categorizing data, such as grouping items by a property like category, status, or date.
Example:
fun main() {
val words = listOf("apple", "banana", "apricot", "blueberry", "cherry")
val groupedByFirstLetter = words.groupBy { it.first() }
println(groupedByFirstLetter)
// Output: {a=[apple, apricot], b=[banana, blueberry], c=[cherry]}
}
5. What is the difference between `flatMap` and `map` in Kotlin?
Both `flatMap` and `map` are used for transforming collections, but they serve distinct purposes:
- `map`: Transforms each element of a collection into another element, resulting in a new collection of the same size.
- `flatMap`: Transforms each element into a collection and flattens the resulting collections into a single collection.
Example:
fun main() {
val data = listOf("one", "two", "three")
val mapped = data.map { it.toUpperCase() }
println(mapped) // Output: [ONE, TWO, THREE]
val flatMapped = data.flatMap { it.toList() }
println(flatMapped) // Output: [o, n, e, t, w, o, t, h, r, e, e]
}
Use `flatMap` when working with nested data structures or when you want to flatten results after applying transformations, such as converting a list of lists into a single list.
6. How does the `partition` function work in Kotlin collections?
The `partition` function splits a collection into two lists based on a predicate. It returns a `Pair` where:
- The first list contains elements that match the predicate.
- The second list contains elements that do not match the predicate.
Example:
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6)
val (evens, odds) = numbers.partition { it % 2 == 0 }
println("Evens: $evens") // Output: Evens: [2, 4, 6]
println("Odds: $odds") // Output: Odds: [1, 3, 5]
}
7. What is the purpose of the `associate` and `associateBy` functions in Kotlin?
The `associate` and `associateBy` functions convert a collection into a `Map`. They differ in how they determine keys and values:
- `associate`: Generates key-value pairs based on a transformation function.
- `associateBy`: Uses a key selector function to determine the map's keys, with elements as values.
Example:
fun main() {
val words = listOf("apple", "banana", "cherry")
// Using associate
val map1 = words.associate { it to it.length }
println(map1) // Output: {apple=5, banana=6, cherry=6}
// Using associateBy
val map2 = words.associateBy { it.first() }
println(map2) // Output: {a=apple, b=banana, c=cherry}
}
8. What is the difference between `toList` and `toMutableList` in Kotlin?
The `toList` and `toMutableList` functions in Kotlin are used to create new collections from existing ones, but they produce different types:
- `toList`: Creates a read-only copy of the original collection. The resulting list cannot be modified.
- `toMutableList`: Creates a mutable copy, allowing modifications such as adding or removing elements.
Use `toList` for creating immutable snapshots of data, and `toMutableList` when you need a modifiable version of the collection.
Example:
fun main() {
val original = listOf(1, 2, 3)
val readOnly = original.toList()
// readOnly.add(4) // Compile-time error
val mutable = original.toMutableList()
mutable.add(4)
println(mutable) // Output: [1, 2, 3, 4]
}
9. What is the difference between `setOf`, `mutableSetOf`, and `hashSetOf` in Kotlin?
Kotlin provides multiple ways to create sets, depending on whether you need mutability or specific implementation:
- `setOf`: Creates a read-only set. Duplicate elements are ignored.
- `mutableSetOf`: Creates a mutable set that allows adding or removing elements.
- `hashSetOf`: Creates a mutable set backed by a hash table, optimized for quick lookups.
Example:
fun main() {
val readOnlySet = setOf(1, 2, 2, 3)
println(readOnlySet) // Output: [1, 2, 3]
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
println(mutableSet) // Output: [1, 2, 3, 4]
val hashSet = hashSetOf(1, 2, 3)
hashSet.add(5)
println(hashSet) // Output: [1, 2, 3, 5]
}
Use `setOf` for immutable data, `mutableSetOf` for general-purpose mutable sets, and `hashSetOf` for performance-critical use cases where fast lookups are required.
10. How does the `zip` function work in Kotlin collections?
The `zip` function combines two collections into a list of pairs. It pairs elements from both collections based on their position, truncating the result to the smaller collection’s size if they differ.
The `zip` function is particularly useful for creating structured data from multiple collections or for combining two datasets into a unified format.
Example:
fun main() {
val list1 = listOf("a", "b", "c")
val list2 = listOf(1, 2)
val zipped = list1.zip(list2)
println(zipped) // Output: [(a, 1), (b, 2)]
val customZipped = list1.zip(list2) { first, second -> "$first$second" }
println(customZipped) // Output: [a1, b2]
}
11. What is the purpose of `sortedBy` and `sortedWith` in Kotlin?
The `sortedBy` and `sortedWith` functions are used to sort collections based on custom criteria:
- `sortedBy`: Sorts a collection using a selector function that specifies the property to sort by.
- `sortedWith`: Sorts a collection using a custom comparator for more complex sorting logic.
Example:
data class Person(val name: String, val age: Int)
fun main() {
val people = listOf(Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35))
val sortedByAge = people.sortedBy { it.age }
println(sortedByAge) // Output: [Person(name=Bob, age=25), Person(name=Alice, age=30), Person(name=Charlie, age=35)]
val sortedByNameDesc = people.sortedWith(compareByDescending { it.name })
println(sortedByNameDesc) // Output: [Person(name=Charlie, age=35), Person(name=Bob, age=25), Person(name=Alice, age=30)]
}
Use `sortedBy` for straightforward sorting by a single property and `sortedWith` for complex, multi-level, or custom sorting requirements.
12. How do `take` and `drop` functions work in Kotlin collections?
The `take` and `drop` functions in Kotlin are used to extract subsets of a collection based on size or conditions:
- `take(n)`: Returns the first `n` elements of the collection.
- `drop(n)`: Returns the collection after dropping the first `n` elements.
- `takeWhile`: Takes elements as long as a condition is true.
- `dropWhile`: Drops elements as long as a condition is true.
Example:
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.take(3)) // Output: [1, 2, 3]
println(numbers.drop(3)) // Output: [4, 5]
println(numbers.takeWhile { it < 4 }) // Output: [1, 2, 3]
println(numbers.dropWhile { it < 4 }) // Output: [4, 5]
}
These functions are ideal for slicing collections or filtering data based on specific conditions.