Kotlin Lists
Lists are one of the most commonly used collection types in programming. In Kotlin, lists are ordered collections that allow duplicate elements and provide indexed access to elements. This tutorial will introduce you to Kotlin lists, their types, operations, and practical applications.
Introduction to Kotlin Lists
A list is an ordered collection of elements. In Kotlin, lists come in two flavors:
- Immutable List (
List
) - Once created, you cannot add, remove, or replace elements - Mutable List (
MutableList
) - Allows modification after creation (add, remove, update)
Lists maintain insertion order and allow duplicate elements, which makes them perfect for many programming tasks where you need to maintain a specific sequence of items.
Creating Lists in Kotlin
Immutable Lists
// Using listOf() function
val colors = listOf("Red", "Green", "Blue")
// Empty list
val emptyList = listOf<String>()
// List with mixed types (not recommended)
val mixedList = listOf("String", 1, true, 3.14)
println(colors) // Output: [Red, Green, Blue]
Mutable Lists
// Using mutableListOf()
val fruits = mutableListOf("Apple", "Banana", "Orange")
// Empty mutable list
val emptyMutableList = mutableListOf<Int>()
// Using ArrayList constructor
val vegetables = ArrayList<String>()
vegetables.add("Carrot")
vegetables.add("Broccoli")
println(fruits) // Output: [Apple, Banana, Orange]
println(vegetables) // Output: [Carrot, Broccoli]
Accessing List Elements
Lists provide indexed access starting from 0:
val animals = listOf("Lion", "Tiger", "Elephant", "Giraffe")
// Access by index
println(animals[0]) // Output: Lion
println(animals[2]) // Output: Elephant
// Using get() method
println(animals.get(1)) // Output: Tiger
// Using first() and last()
println(animals.first()) // Output: Lion
println(animals.last()) // Output: Giraffe
// Safe access with getOrNull() or getOrElse()
println(animals.getOrNull(10) ?: "Index not found") // Output: Index not found
println(animals.getOrElse(10) { "Index not found" }) // Output: Index not found
List Operations
Basic Properties and Methods
val numbers = listOf(1, 2, 3, 4, 5)
println("Size: ${numbers.size}") // Output: Size: 5
println("Is empty: ${numbers.isEmpty()}") // Output: Is empty: false
println("Contains 3: ${numbers.contains(3)}") // Output: Contains 3: true
println("Index of 4: ${numbers.indexOf(4)}") // Output: Index of 4: 3
println("Last index of 5: ${numbers.lastIndexOf(5)}") // Output: Last index of 5: 4
Iterating Over Lists
val planets = listOf("Mercury", "Venus", "Earth", "Mars")
// Using for loop
for (planet in planets) {
println(planet)
}
// Output:
// Mercury
// Venus
// Earth
// Mars
// Using forEach
planets.forEach { planet ->
println("Planet: $planet")
}
// Output:
// Planet: Mercury
// Planet: Planet: Venus
// Planet: Earth
// Planet: Mars
// With index
planets.forEachIndexed { index, planet ->
println("Planet #${index + 1}: $planet")
}
// Output:
// Planet #1: Mercury
// Planet #2: Venus
// Planet #3: Earth
// Planet #4: Mars
Filtering and Transforming Lists
val scores = listOf(85, 92, 78, 65, 90, 88, 72)
// Filter scores above 80
val highScores = scores.filter { it > 80 }
println("High scores: $highScores") // Output: High scores: [85, 92, 90, 88]
// Map: convert to letter grades
val letterGrades = scores.map { score ->
when {
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
score >= 60 -> "D"
else -> "F"
}
}
println("Letter grades: $letterGrades")
// Output: Letter grades: [B, A, C, D, A, B, C]
// Combining operations
val highestLetterGrades = scores.filter { it > 85 }.map {
when {
it >= 90 -> "A"
else -> "B"
}
}
println("Highest letter grades: $highestLetterGrades")
// Output: Highest letter grades: [B, A, A, B]
Operations for Mutable Lists
Mutable lists provide additional operations to modify content:
val todoList = mutableListOf("Learn Kotlin", "Build an app")
// Adding elements
todoList.add("Fix bugs")
todoList.add(0, "Plan project") // Add at specific index
todoList.addAll(listOf("Deploy app", "Collect feedback"))
println(todoList)
// Output: [Plan project, Learn Kotlin, Build an app, Fix bugs, Deploy app, Collect feedback]
// Removing elements
todoList.remove("Fix bugs")
todoList.removeAt(0) // Remove element at index 0
println(todoList)
// Output: [Learn Kotlin, Build an app, Deploy app, Collect feedback]
// Updating elements
todoList[1] = "Build awesome app"
println(todoList)
// Output: [Learn Kotlin, Build awesome app, Deploy app, Collect feedback]
// Clear the list
todoList.clear()
println(todoList) // Output: []
Practical Examples
Example 1: Task Manager
class Task(val name: String, var isCompleted: Boolean = false) {
override fun toString() = "$name ${if (isCompleted) "✓" else "○"}"
}
fun main() {
val taskManager = mutableListOf<Task>()
// Add tasks
taskManager.add(Task("Buy groceries"))
taskManager.add(Task("Call dentist"))
taskManager.add(Task("Finish Kotlin tutorial"))
// Display all tasks
println("Initial tasks:")
taskManager.forEachIndexed { index, task -> println("${index + 1}. $task") }
// Complete a task
taskManager[0].isCompleted = true
// Add another task
taskManager.add(Task("Go for a run"))
// Show pending tasks
println("\nPending tasks:")
val pendingTasks = taskManager.filter { !it.isCompleted }
pendingTasks.forEachIndexed { index, task -> println("${index + 1}. $task") }
// Show completed tasks
println("\nCompleted tasks:")
val completedTasks = taskManager.filter { it.isCompleted }
completedTasks.forEachIndexed { index, task -> println("${index + 1}. $task") }
}
Output:
Initial tasks:
1. Buy groceries ○
2. Call dentist ○
3. Finish Kotlin tutorial ○
Pending tasks:
1. Call dentist ○
2. Finish Kotlin tutorial ○
3. Go for a run ○
Completed tasks:
1. Buy groceries ✓
Example 2: Shopping Cart
data class Product(val name: String, val price: Double, var quantity: Int = 1) {
fun totalPrice() = price * quantity
}
class ShoppingCart {
private val items = mutableListOf<Product>()
fun addItem(product: Product) {
val existingItem = items.find { it.name == product.name }
if (existingItem != null) {
existingItem.quantity += product.quantity
} else {
items.add(product)
}
}
fun removeItem(productName: String) {
items.removeIf { it.name == productName }
}
fun updateQuantity(productName: String, newQuantity: Int) {
val product = items.find { it.name == productName }
product?.quantity = newQuantity
}
fun calculateTotal(): Double {
return items.sumOf { it.totalPrice() }
}
fun displayCart() {
if (items.isEmpty()) {
println("Your cart is empty")
return
}
println("Shopping Cart:")
println("--------------")
items.forEachIndexed { index, product ->
println("${index + 1}. ${product.name} - $${product.price} x ${product.quantity} = $${product.totalPrice()}")
}
println("--------------")
println("Total: $${calculateTotal()}")
}
}
fun main() {
val cart = ShoppingCart()
cart.addItem(Product("Laptop", 999.99))
cart.addItem(Product("Mouse", 24.99))
cart.addItem(Product("Keyboard", 49.99))
cart.displayCart()
// Update quantity
cart.updateQuantity("Mouse", 2)
// Add one more laptop
cart.addItem(Product("Laptop", 999.99))
println("\nAfter updates:")
cart.displayCart()
}
Output:
Shopping Cart:
--------------
1. Laptop - $999.99 x 1 = $999.99
2. Mouse - $24.99 x 1 = $24.99
3. Keyboard - $49.99 x 1 = $49.99
--------------
Total: $1074.97
After updates:
Shopping Cart:
--------------
1. Laptop - $999.99 x 2 = $1999.98
2. Mouse - $24.99 x 2 = $49.98
3. Keyboard - $49.99 x 1 = $49.99
--------------
Total: $2099.95
List vs ArrayList vs LinkedList
Kotlin offers different list implementations depending on your needs:
import java.util.LinkedList
fun main() {
// Regular List (backed by ArrayList in most cases)
val arrayListBased = listOf("a", "b", "c")
// Explicitly using ArrayList
val arrayList = ArrayList<String>()
arrayList.add("x")
arrayList.add("y")
arrayList.add("z")
// Using LinkedList
val linkedList = LinkedList<String>()
linkedList.add("1")
linkedList.add("2")
linkedList.add("3")
println("ArrayList-based list: $arrayListBased")
println("ArrayList: $arrayList")
println("LinkedList: $linkedList")
}
When to use what:
- Regular List/ArrayList: Good for most cases, especially when you need fast random access by index
- LinkedList: Better for frequent insertions and deletions, especially in the middle of the list
Advanced List Operations
Sorting
val numbers = mutableListOf(5, 3, 8, 2, 1, 4)
// In-place sorting (only for mutable lists)
numbers.sort()
println("Sorted numbers: $numbers") // Output: [1, 2, 3, 4, 5, 8]
// Reversing
numbers.reverse()
println("Reversed: $numbers") // Output: [8, 5, 4, 3, 2, 1]
// For immutable lists, use sorted() and reversed()
val immutableList = listOf(5, 3, 8, 2, 1, 4)
val sortedList = immutableList.sorted()
val reversedList = immutableList.reversed()
println("Original immutable: $immutableList") // Output: [5, 3, 8, 2, 1, 4]
println("Sorted immutable: $sortedList") // Output: [1, 2, 3, 4, 5, 8]
println("Reversed immutable: $reversedList") // Output: [4, 1, 2, 8, 3, 5]
Custom Sorting
data class Student(val name: String, val age: Int, val grade: Double)
fun main() {
val students = listOf(
Student("Alice", 20, 3.8),
Student("Bob", 19, 3.5),
Student("Charlie", 21, 3.9),
Student("Diana", 20, 4.0)
)
// Sort by age
val byAge = students.sortedBy { it.age }
println("Sorted by age:")
byAge.forEach { println("${it.name}, ${it.age} years old") }
// Sort by grade (descending)
val byGrade = students.sortedByDescending { it.grade }
println("\nSorted by grade (highest first):")
byGrade.forEach { println("${it.name}, Grade: ${it.grade}") }
// Multiple criteria: first by age, then by grade
val byAgeAndGrade = students.sortedWith(
compareBy<Student> { it.age }.thenByDescending { it.grade }
)
println("\nSorted by age, then by grade (highest first):")
byAgeAndGrade.forEach { println("${it.name}, ${it.age} years old, Grade: ${it.grade}") }
}
Common Pitfalls and Best Practices
-
Immutable vs Mutable: Use
listOf()
when you don't need to modify the list after creation. UsemutableListOf()
when you need to modify the list. -
IndexOutOfBoundsException: Always check if an index is valid before accessing it, or use safe accessors like
getOrNull()
.
val list = listOf("a", "b", "c")
// Bad - might throw IndexOutOfBoundsException
// val element = list[10]
// Good
val element = if (10 < list.size) list[10] else null
// Better
val element2 = list.getOrNull(10)
val element3 = list.getOrElse(10) { "Default value" }
- Type Safety: Specify the generic type for empty lists to maintain type safety:
// Not recommended - no type information
val emptyList = listOf()
// Recommended - includes type information
val emptyStrings = listOf<String>()
- Efficient List Updates: When performing multiple operations on large lists, chain operations instead of creating intermediate lists:
// Less efficient
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val filtered = numbers.filter { it % 2 == 0 }
val doubled = filtered.map { it * 2 }
// More efficient
val result = numbers.filter { it % 2 == 0 }.map { it * 2 }
Summary
In this tutorial, we've covered:
- The differences between immutable (
List
) and mutable (MutableList
) lists - How to create and initialize lists
- Accessing elements using indexing and specialized functions
- Basic list operations (size, contains, finding elements)
- Iterating through lists
- Filtering and transforming lists
- Operations specific to mutable lists (add, remove, update)
- Practical examples with task manager and shopping cart applications
- Different list implementations (ArrayList vs LinkedList)
- Advanced operations like sorting and custom sorting
- Best practices and common pitfalls
Lists are one of the most versatile collection types in Kotlin, making them essential to master for any Kotlin developer.
Exercises
- Create a function that takes a list of integers and returns a new list with only the even numbers.
- Write a program that manages a playlist of songs using a mutable list.
- Implement a function that merges two sorted lists into a single sorted list.
- Create a function that removes duplicates from a list while preserving the original order.
- Build a simple inventory management system using lists of products.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)