Kotlin - Collections
Kotlin Collections
Collections are fundamental to programming. Kotlin provides rich collection types (List, Set, Map) with both mutable and immutable variants, plus powerful operations for data processing.
Collection Types Overview
Immutable vs Mutable Collections
✅ Immutable (Read-Only) - Preferred
val list = listOf(1, 2, 3)
val set = setOf("a", "b", "c")
val map = mapOf("key" to "value")
// Cannot modify:
// list.add(4) // ❌ No such method
⚠️ Mutable (Changeable) - When Needed
val list = mutableListOf(1, 2, 3)
val set = mutableSetOf("a", "b", "c")
val map = mutableMapOf("key" to "value")
// Can modify:
list.add(4) // ✅ Allowed
set.remove("a") // ✅ Allowed
map["new"] = "value" // ✅ Allowed
Best Practice: Start with immutable collections and only use mutable ones when you need to modify the collection after creation.
Lists - Ordered Collections
Creating Lists
// Immutable lists
val fruits = listOf("apple", "banana", "cherry")
val numbers = listOf(1, 2, 3, 4, 5)
val mixedList = listOf("text", 42, true) // Any type
val emptyList = emptyList<String>()
// Mutable lists
val mutableFruits = mutableListOf("apple", "banana")
val growingList = mutableListOf<String>()
// ArrayList (specific implementation)
val arrayList = arrayListOf(1, 2, 3)
List Operations
val numbers = listOf(1, 2, 3, 4, 5)
// Accessing elements
println(numbers[0]) // 1 (first element)
println(numbers.first()) // 1
println(numbers.last()) // 5
println(numbers.get(2)) // 3
// Safe access
println(numbers.getOrNull(10)) // null (instead of exception)
println(numbers.getOrElse(10) { -1 }) // -1 (default value)
// Properties
println(numbers.size) // 5
println(numbers.isEmpty()) // false
println(numbers.isNotEmpty()) // true
// Checking contents
println(3 in numbers) // true
println(numbers.contains(6)) // false
Mutable List Operations
val fruits = mutableListOf("apple", "banana")
// Adding elements
fruits.add("cherry") // [apple, banana, cherry]
fruits.add(0, "orange") // [orange, apple, banana, cherry]
fruits += "grape" // [orange, apple, banana, cherry, grape]
fruits.addAll(listOf("kiwi", "mango"))
// Removing elements
fruits.remove("apple") // Remove by value
fruits.removeAt(0) // Remove by index
fruits -= "banana" // Remove using -= operator
fruits.clear() // Remove all elements
println(fruits) // []
Sets - Unique Collections
Creating Sets
// Immutable sets (no duplicates)
val colors = setOf("red", "green", "blue", "red") // Only 3 items
val uniqueNumbers = setOf(1, 2, 3, 2, 1) // Only 3 items
val emptySet = emptySet<String>()
// Mutable sets
val mutableColors = mutableSetOf("red", "green")
val hashSet = hashSetOf(1, 2, 3)
println(colors) // [red, green, blue]
println(colors.size) // 3 (duplicates removed)
Set Operations
val set1 = setOf(1, 2, 3, 4)
val set2 = setOf(3, 4, 5, 6)
// Basic operations
println(3 in set1) // true
println(set1.contains(5)) // false
// Set mathematics
println(set1 union set2) // [1, 2, 3, 4, 5, 6] (all elements)
println(set1 intersect set2) // [3, 4] (common elements)
println(set1 subtract set2) // [1, 2] (elements only in set1)
// Mutable set operations
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4) // true (added)
mutableSet.add(2) // false (already exists)
mutableSet.remove(1) // true (removed)
println(mutableSet) // [2, 3, 4]
Maps - Key-Value Pairs
Creating Maps
// Immutable maps
val ages = mapOf("Alice" to 30, "Bob" to 25, "Charlie" to 35)
val grades = mapOf(
"Alice" to 'A',
"Bob" to 'B',
"Charlie" to 'A'
)
val emptyMap = emptyMap<String, Int>()
// Mutable maps
val mutableAges = mutableMapOf("Alice" to 30, "Bob" to 25)
val hashMap = hashMapOf("key1" to "value1", "key2" to "value2")
Map Operations
val phoneBook = mapOf(
"Alice" to "555-1234",
"Bob" to "555-5678",
"Charlie" to "555-9012"
)
// Accessing values
println(phoneBook["Alice"]) // 555-1234
println(phoneBook.get("Alice")) // 555-1234
println(phoneBook["David"]) // null (key doesn't exist)
println(phoneBook.getValue("Alice")) // 555-1234 (throws exception if not found)
// Safe access
println(phoneBook.getOrDefault("David", "Unknown")) // Unknown
println(phoneBook.getOrElse("David") { "Not found" }) // Not found
// Properties and checks
println(phoneBook.size) // 3
println(phoneBook.isEmpty()) // false
println("Alice" in phoneBook) // true
println(phoneBook.containsKey("David")) // false
println(phoneBook.containsValue("555-1234")) // true
// Getting keys and values
println(phoneBook.keys) // [Alice, Bob, Charlie]
println(phoneBook.values) // [555-1234, 555-5678, 555-9012]
Mutable Map Operations
val inventory = mutableMapOf(
"apples" to 10,
"bananas" to 5,
"oranges" to 8
)
// Adding/updating entries
inventory["grapes"] = 12 // Add new entry
inventory.put("kiwis", 6) // Alternative way to add
inventory["apples"] = 15 // Update existing value
inventory += "mangoes" to 4 // Using += operator
// Removing entries
inventory.remove("bananas") // Remove by key
inventory -= "oranges" // Using -= operator
// Batch operations
inventory.putAll(mapOf("berries" to 20, "cherries" to 15))
inventory.clear() // Remove all entries
println(inventory) // {}
Collection Operations
Filtering
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Filter elements
val evenNumbers = numbers.filter { it % 2 == 0 }
val oddNumbers = numbers.filterNot { it % 2 == 0 }
val greaterThanFive = numbers.filter { it > 5 }
println(evenNumbers) // [2, 4, 6, 8, 10]
println(oddNumbers) // [1, 3, 5, 7, 9]
println(greaterThanFive) // [6, 7, 8, 9, 10]
// Filter by type
val mixedList = listOf("hello", 42, "world", 3.14, true)
val strings = mixedList.filterIsInstance<String>()
val numbers2 = mixedList.filterIsInstance<Number>()
println(strings) // [hello, world]
println(numbers2) // [42, 3.14]
Transformation (Map)
val words = listOf("hello", "world", "kotlin")
// Transform elements
val lengths = words.map { it.length }
val upperCase = words.map { it.uppercase() }
val withIndex = words.mapIndexed { index, word -> "$index: $word" }
println(lengths) // [5, 5, 6]
println(upperCase) // [HELLO, WORLD, KOTLIN]
println(withIndex) // [0: hello, 1: world, 2: kotlin]
// Transform and flatten
val sentences = listOf("hello world", "kotlin programming")
val allWords = sentences.flatMap { it.split(" ") }
println(allWords) // [hello, world, kotlin, programming]
Aggregation
val scores = listOf(85, 92, 78, 96, 88)
// Basic aggregations
println(scores.sum()) // 439
println(scores.average()) // 87.8
println(scores.min()) // 78
println(scores.max()) // 96
println(scores.count()) // 5
// Conditional aggregations
println(scores.count { it > 90 }) // 2
println(scores.sumOf { it * 2 }) // 878 (sum of doubled values)
// Reduce operations
val product = scores.reduce { acc, score -> acc * score }
val concatenated = listOf("A", "B", "C").reduce { acc, letter -> acc + letter }
println(product) // Very large number (multiplication of all scores)
println(concatenated) // ABC
Searching and Finding
val students = listOf("Alice", "Bob", "Charlie", "Diana", "Eve")
// Find operations
val firstLongName = students.find { it.length > 5 } // Charlie
val lastLongName = students.findLast { it.length > 5 } // Charlie
val anyShortName = students.any { it.length < 4 } // true (Bob, Eve)
val allLongNames = students.all { it.length > 2 } // true
val noneEmpty = students.none { it.isEmpty() } // true
// First/last with conditions
val firstA = students.first { it.startsWith("A") } // Alice
val lastE = students.last { it.endsWith("e") } // Charlie
val firstD = students.firstOrNull { it.startsWith("D") } // Diana
val firstZ = students.firstOrNull { it.startsWith("Z") } // null
println("First long name: $firstLongName")
println("Any short names: $anyShortName")
Collection Conversions
val numbers = listOf(1, 2, 3, 2, 1, 4, 3)
// Convert between collection types
val numberSet = numbers.toSet() // Remove duplicates: [1, 2, 3, 4]
val backToList = numberSet.toList() // Convert back to list
val mutableCopy = numbers.toMutableList() // Create mutable copy
// Convert to Map
val indexed = numbers.withIndex().associate { it.index to it.value }
val grouped = numbers.groupBy { it % 2 } // Group by even/odd
println(numberSet) // [1, 2, 3, 4]
println(indexed) // {0=1, 1=2, 2=3, 3=2, 4=1, 5=4, 6=3}
println(grouped) // {1=[1, 1, 3, 3], 0=[2, 2, 4]}
// Array conversions
val array = numbers.toTypedArray()
val intArray = numbers.toIntArray()
val listFromArray = array.toList()
Sequences for Large Collections
For large collections or complex operations, sequences provide lazy evaluation:
val largeNumbers = (1..1_000_000).toList()
// Eager evaluation (processes all elements immediately)
val eagerResult = largeNumbers
.filter { it % 2 == 0 }
.map { it * it }
.take(10)
// Lazy evaluation with sequences (processes only what's needed)
val lazyResult = largeNumbers.asSequence()
.filter { it % 2 == 0 }
.map { it * it }
.take(10)
.toList() // Terminal operation triggers evaluation
println(lazyResult) // [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]
// Infinite sequence
val infiniteSequence = generateSequence(1) { it + 1 }
val firstTenSquares = infiniteSequence
.map { it * it }
.take(10)
.toList()
println(firstTenSquares) // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Real-World Examples
Student Grade Management
data class Student(val name: String, val grades: List<Int>)
val students = listOf(
Student("Alice", listOf(85, 92, 78, 96)),
Student("Bob", listOf(79, 83, 88, 85)),
Student("Charlie", listOf(95, 89, 92, 94)),
Student("Diana", listOf(67, 74, 82, 79))
)
// Calculate averages
val averages = students.associate { student ->
student.name to student.grades.average()
}
// Find top performers
val topStudents = students.filter { student ->
student.grades.average() >= 85.0
}
// Grade distribution
val allGrades = students.flatMap { it.grades }
val gradeDistribution = allGrades.groupBy { grade ->
when (grade) {
in 90..100 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
in 60..69 -> "D"
else -> "F"
}
}
println("Averages: $averages")
println("Top students: ${topStudents.map { it.name }}")
println("Grade distribution: $gradeDistribution")
Inventory Management
data class Product(val id: String, val name: String, val price: Double, val stock: Int)
val inventory = listOf(
Product("P001", "Laptop", 999.99, 5),
Product("P002", "Mouse", 29.99, 50),
Product("P003", "Keyboard", 79.99, 25),
Product("P004", "Monitor", 199.99, 10),
Product("P005", "Headphones", 149.99, 0)
)
// Products in stock
val inStock = inventory.filter { it.stock > 0 }
// Low stock alerts (less than 10 items)
val lowStock = inventory.filter { it.stock in 1..9 }
// Out of stock
val outOfStock = inventory.filter { it.stock == 0 }
// Total inventory value
val totalValue = inventory.sumOf { it.price * it.stock }
// Price ranges
val priceRanges = inventory.groupBy { product ->
when {
product.price < 50 -> "Budget"
product.price < 200 -> "Mid-range"
else -> "Premium"
}
}
println("Low stock items: ${lowStock.map { it.name }}")
println("Total inventory value: $${"%.2f".format(totalValue)}")
println("Price ranges: ${priceRanges.mapValues { it.value.size }}")
Best Practices
Performance Tips
- Use sequences for large collections or complex operations
- Prefer immutable collections when possible
- Use appropriate collection types (Set for uniqueness, Map for lookups)
- Consider using
any()
instead offilter().isNotEmpty()
- Chain operations efficiently to avoid intermediate collections
Architecture Note: Kotlin's collection library is based on Java collections but adds many functional programming features. The distinction between mutable and immutable collections helps prevent bugs and makes code more predictable.
Practice Exercises
- Create a program that manages a library's book collection using different collection types
- Build a simple shopping cart system with products, quantities, and total calculations
- Implement a word frequency counter that processes a list of sentences
- Create a student roster system that can filter and sort students by various criteria
- Design a inventory system that tracks products, categories, and stock levels
Quick Quiz
- What's the difference between
listOf()
andmutableListOf()
? - How does a Set differ from a List?
- When should you use sequences instead of regular collections?
- What's the difference between
map()
andflatMap()
?
Show answers
listOf()
creates an immutable list that cannot be modified,mutableListOf()
creates a mutable list that can be modified- A Set automatically removes duplicates and doesn't maintain order, while a List allows duplicates and maintains insertion order
- Use sequences for large collections or when you need lazy evaluation to avoid processing all elements unnecessarily
map()
transforms each element into one result element,flatMap()
transforms each element into a collection and flattens the results