Kotlin programming language
Kotlin programming language
Collections

Kotlin programming language

Collections: What are they?

A collection usually contains a number of objects (may also be zero), typically of the same type, defined by the type parameter

Objects in a collection are called elements or items

  • Lists are ordered collections with access to elements by index. Elements can occur more than once in a list
  • Sets are collections of unique elements. They reflect the mathematical abstraction of “set”: a group of objects without duplicates
  • Maps (or dictionaries) are collections of key-value pairs. The keys are unique, and each of them maps to exactly one value, while the values can be duplicated
Kotlin programming language

Collections: How to use them?

The Kotlin Standard Library provides generic interfaces and functions for all collection types — the API is the same regardless of what the collection holds

val numbers = listOf(1, 2, 3)
val words   = listOf("one", "two", "three")
val users   = listOf(User("Alice"), User("Bob"))

val unique  = setOf(1, 2, 2, 3)        // {1, 2, 3}

val scores  = mapOf("Alice" to 42, "Bob" to 99)
Kotlin programming language

Collections: Taxonomy

interface Iterable <out T> + abstract operator fun iterator(): Iterator<T> interface Collection <out E> : Iterable<E> + abstract val size: Int + abstract operator fun contains( element: E): Boolean + ... interface List <out E>: Collection<E> interface Set <out E>: Collection<E> abstract class AbstractCollection <out E>: Collection<E> interface MutableIterable <out T> : Iterable<T> + abstract fun iterator(): MutableIterator<T> interface MutableCollection <E> : Collection<E>, MutableIterable<E> + abstract fun add(element: E): Boolean + abstract fun remove(element: E): Boolean + ... interface MutableList <E>: List<E>, MutableCollection<E> interface MutableSet <E>: Set<E>, MutableCollection<E> abstract class AbstractMutableCollection <E>: AbstractCollection<E>, MutableCollection<E> interface Map <K, out V> + abstract val entries: Set<Entry<K, V>> + abstract val keys: Set<K> + abstract val size: Int + abstract val values: Collection<V> + abstract fun containsKey(key: K): Boolean + ... interface Entry <out K, out V> + abstract val key: K + abstract val value: V interface MutableMap <K, V>

Interfaces — Kotlin actually uses implementations from java.util

Kotlin programming language

Collections: Taxonomy #2

MutableIterable<T> Iterable<T> MutableCollection<E> Collection<E> List<E> Set<E> Map<K, V> MutableList<E> MutableSet<E> MutableMap<K, V>
Kotlin programming language

Iterable

Collection and its subtypes implement IterableMap does not:

/**
* Classes that inherit from this interface can be represented as a sequence of elements that can be iterated over.
* @param T is the type of element being iterated over. The iterator is covariant in its element type.
*/
public interface Iterable<out T> {
    public operator fun iterator(): Iterator<T>
}

This enables the for loop syntax:

for (element in myCollection) {
    println(element)
}
Kotlin programming language

Iterable vs MutableIterable

Collection subtypes are Iterable; MutableCollection subtypes are also MutableIterable. Map is neither.

val iterator = myIterableCollection.iterator()
while (iterator.hasNext()) {
   iterator.next()
}

MutableIterable returns a MutableIterator, which adds remove()

val iterator = myMutableIterableCollection.iterator()
while (iterator.hasNext()) {
   iterator.next()
   iterator.remove() // removes the element last returned by next()
}
Kotlin programming language

Different kinds of collections

Kotlin separates read-only and mutable views of collections via two interfaces.

Collection<E> provides read-only access — it has no add or remove.

MutableCollection<E> extends both Collection and MutableIterable, adding mutation operations.

Note: read-only does not mean immutable — a Collection variable may point to an underlying MutableList that is mutated elsewhere.

val readonlyCollection = listOf(1, 2, 3)
readonlyCollection.add(4) // ERROR: Unresolved reference: add

val mutableCollection = mutableListOf(1, 2, 3)
mutableCollection.add(4) // OK
Kotlin programming language

Mutable Collection != Mutable Variable

If you create a mutable collection, you cannot reassign the val variable

val mutableCollection = mutableListOf(1, 2, 3)
mutableCollection.add(4) // OK
mutableCollection = mutableListOf(4, 5, 6) // ERROR: Val cannot be reassigned

But you can reassign var

var mutableCollection = mutableListOf(1, 2, 3)
mutableCollection.add(4) // OK
mutableCollection = mutableListOf(4, 5, 6) // OK
Kotlin programming language

The anatomy of a collection

interface List<out E>: Collection<E> Abstraction Covariance annotation. List<C> is a List<B> when C : B Parent generic Type name Generic type name Parent type name
Kotlin programming language

The anatomy of a collection #2

Each collection has several core interface methods:

public interface Collection<out E> : Iterable<E> {
    public val size: Int

    public fun isEmpty(): Boolean       // Use this instead of size == 0

    public operator fun contains(element: @UnsafeVariance E): Boolean

    public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
    ...
}
Kotlin programming language

The anatomy of a collection #3

Collection also has useful extension functions:

// Convenient to use in loops:
// for (i in collection.indices) { ... }
public val Collection<*>.indices: IntRange
    get() = 0..size - 1

public val <T> List<T>.lastIndex: Int
    get() = this.size - 1

// Use this instead of size != 0
public inline fun <T> Collection<T>.isNotEmpty(): Boolean = !isEmpty()
Kotlin programming language

Collections under the hood: List

public interface List<out E> : Collection<E> {
    public operator fun get(index: Int): E  // Convenient to use with []: collection[2]
    public fun indexOf(element: @UnsafeVariance E): Int
    public fun lastIndexOf(element: @UnsafeVariance E): Int
    public fun subList(fromIndex: Int, toIndex: Int): List<E>

    ...
}

subList returns a live view backed by the original list — mutations are reflected in both:

val list1 = mutableListOf(1, 2, 3)
val list2 = list1.subList(0, 1)
list1[0] += 1
println(list1) // [2, 2, 3]
println(list2) // [2]
Kotlin programming language

Collections under the hood: List #2

To create a new list you can use special builders (by default ArrayList):

val list1 = emptyList<Int>() // Builds the internal object EmptyList
val list2 = listOf<Int>() // Calls emptyList()
val list3 = listOf(1, 2, 3) // The type can be inferred

val list4 = mutableListOf<Int>() // backed by ArrayList<Int>
val list5 = mutableListOf(1, 2, 3) // The type can be inferred
val list6 = buildList {
    // constructs MutableList<Int>
    add(5)
    addAll(0, listOf(1, 2, 3))
}
Kotlin programming language

Collections under the hood: Set

public interface Set<out E> : Collection<E>
// No new members — all interface methods are inherited from Collection

A generic unordered collection that does not support duplicate elements.
Order of elements is not guaranteed.
Equality is determined by equals() and hashCode(), not reference identity.

Kotlin programming language

Collections under the hood: Set #2

class A(val primary: Int, val secondary: Int)
class B(val primary: Int, val secondary: Int) {
    override fun hashCode(): Int = primary
    override fun equals(other: Any?) = primary == (other as? B)?.primary
}
fun main() {
    val a = A(1,1)
    val b = A(1,2)
    val set = setOf(a, b)
    println(set) // two elements
}
fun main() {
    val a = B(1,1)
    val b = B(1,2)
    val set = setOf(a, b)
    println(set) // only one element
}
Kotlin programming language

Collections under the hood: Set #3

To create a new set you can use special builders (by default LinkedHashSet):

val set1 = emptySet<Int>() // Builds the internal object EmptySet
val set2 = setOf<Int>() // Calls emptySet()
val set3 = setOf(1, 2, 3) // The type can be inferred

val set4 = mutableSetOf<Int>() // backed by LinkedHashSet<Int>()
val set5 = mutableSetOf(1, 2, 3) // The type can be inferred
val set6 = buildSet {
    // constructs MutableSet<Int>
    add(5)
    addAll(listOf(1, 2, 3))
}
Kotlin programming language

Collections under the hood: Map

public interface Map<K, out V> {
    public fun containsKey(key: K): Boolean
    public fun containsValue(value: @UnsafeVariance V): Boolean
    public operator fun get(key: K): V?
    public fun getOrDefault(key: K, defaultValue: @UnsafeVariance V): V
    public val entries: Set<Map.Entry<K, V>>
    // Convenient to use in loops:
    // for ((key, value) in map) { ... }
    // Note: map is more idiomatic compared to map.entries
    ...
}
Kotlin programming language

Collections under the hood: Map #2

To create a new map you can use special builders (by default LinkedHashMap):

val map1 = emptyMap<Int, String>() // Builds the internal object EmptyMap
val map2 = mapOf<Int, String>() // Calls emptyMap()
val map3 = mapOf(1 to "one", 2 to "two") // The type can be inferred

val map4 = mutableMapOf<Int, String>() // backed by LinkedHashMap<...>()
val map5 = mutableMapOf(1 to "one", 2 to "two") // The type can be inferred
val map6 = buildMap {
    // constructs MutableMap<Int, String>
    put(1, "one")
    putAll(mapOf(2 to "two"))
}
Kotlin programming language

Array

Does not implement Collection or Iterable, but supports for loops via an extension iterator()
Has a fixed size, but its elements are mutable

/**
 * Represents an array (specifically a Java array when targeting the JVM platform).
 * Array instances can be created using arrayOf, arrayOfNulls, and emptyArray
 */
public class Array<T> {
    public val size: Int
    public operator fun get(index: Int): T
    public operator fun set(index: Int, value: T): Unit
    ...
}
Kotlin programming language

Array #2

Kotlin also has classes that represent arrays of primitive types without boxing overhead: ByteArray, ShortArray, IntArray, and so on.

/**
 * An array of ints. When targeting the JVM, instances of this class are represented as `int[]`.
 */
public class IntArray(size: Int) {
    public val size: Int
    public operator fun get(index: Int): Int
    public operator fun set(index: Int, value: Int): Unit
    ...
}
Kotlin programming language

Ranges

Not collections, but support iteration — standard types have IntRange, CharRange, LongRange (each extends the corresponding IntProgression, CharProgression, LongProgression):

for (c in 'a'..'c') { ... }  // CharRange  — 'a', 'b', 'c'
for (i in 1..5) { ... }      // IntRange   — 1, 2, 3, 4, 5
for (i in 1 until 5) { ... } // IntRange   — 1, 2, 3, 4  (exclusive end)
for (i in 1L..5L) { ... }    // LongRange  — 1L, 2L, 3L, 4L, 5L

downTo and step are infix extension functions for customizing direction and stride:

for (i in 10 downTo 0 step 3) { ... } // 10, 7, 4, 1
Kotlin programming language

Sequence

Not a collection, but has an iterator:

/**
 * A sequence that returns values through its iterator. 
 * The values are evaluated lazily, and the sequence is potentially infinite.
 */
public interface Sequence<out T> {
    public operator fun iterator(): Iterator<T>
}
Kotlin programming language

Sequence #2

To create a new sequence you can use special builders

val sequence1 = emptySequence<Int>() // Builds the internal object EmptySequence
val sequence2 = sequenceOf<Int>()    // Calls emptySequence()
val sequence3 = sequenceOf(1, 2, 3)  // The type can be inferred
val sequence4 = sequence {
    // constructs Sequence<Int>
    yield(1)
    yieldAll(listOf(2, 3))
}
val sequence5 = generateSequence(1) { it + 2 } // `it` is the previous element
println(sequence5.take(5).toList()) // [1, 3, 5, 7, 9]
Kotlin programming language

Sequence vs List

val words = "The quick brown fox jumps over the lazy dog".split(" ")

List — eager: each step materializes a full intermediate collection before the next step begins

val lengthsList = words
    .filter { it.length > 3 }  // all 9 words evaluated → 5-element intermediate List
    .map { it.length }          // all 5 mapped → another intermediate List
    .take(4)                    // [5, 5, 5, 4]

Sequence — lazy: elements flow one at a time; stops as soon as take(4) is satisfied

val lengthsSeq = words.asSequence()
    .filter { it.length > 3 }
    .map { it.length }
    .take(4)
    .toList()                   // only 6 of 9 words examined — "the", "lazy", "dog" never touched
                                // [5, 5, 5, 4]
Kotlin programming language

Collection operations

There are many different functions for working with collections. If you need to do something with a collection, Google it first. Most likely, the standard library already has the function you need, for example:

public fun <T : Comparable<T>> List<T?>.binarySearch(element: T?, fromIndex: Int = 0, toIndex: Int = size): Int
// list must be sorted first (e.g. sort() above)

public fun <T : Comparable<T>> MutableList<T>.sort(): Unit

public inline fun <T, K, V> Iterable<T>.groupBy(keySelector: (T) -> K, valueTransform: (T) -> V): Map<K, List<V>>

public inline fun <T> Iterable<T>.partition(predicate: (T) -> Boolean): Pair<List<T>, List<T>>
Kotlin programming language

Collection operations #2

val exampleList = listOf(1, 2, 3, 4, 5, 6) 1 2 3 4 5 6 exampleList.chunked(2) 1 2 3 4 5 6 exampleList.chunked(2) { it.sum() } 3 7 11 exampleList.windowed(2) 1 2 2 3 3 4 4 5 5 6 exampleList.drop(1).intersect(List(6) { it - 1 }) 2 3 4
Kotlin programming language

Thanx!

Iterable

Collection

List

Set

AbstractCollection

MutableIterable

MutableCollection

MutableList

MutableSet

AbstractMutableCollection

Map (extended to contain Entry)

MutableMap

ARROWS

Collection -> Iterable

Row 1: MutableIterable, Iterable

MutableIterable → Iterable

Row 2: MutableCollection, Collection

MutableCollection → MutableIterable

Row 3: List, Set, Map

List → Collection

Row 4: MutableList, MutableSet, MutableMap

MutableList → List

MutableList → MutableCollection (long diagonal)