Skip to content

Chapter 6 of 7

Collections, Sequences, and Strings

Kotlin's collection library distinguishes read-only interfaces from their mutable counterparts. List, Set, and Map describe immutable collections, while MutableList, MutableSet, and MutableMap extend them with mutation methods like add, remove, and clear. The standard factories — listOf(), mutableListOf(), setOf(), mutableSetOf(), mapOf(), mutableMapOf(), emptyList(), and emptyList() — return instances of these interfaces. The runtime implementations are usually ArrayList and HashMap, but your code should rely only on the interface contract. arrayListOf(...) returns a Java ArrayList directly, while mutableListOf(...) returns the most general mutable type, allowing the JVM platform to choose an optimized implementation. Use type-specific factories like intArrayOf, longArrayOf, doubleArrayOf, charArrayOf, and booleanArrayOf when working with primitives to avoid the boxing overhead of Array<Int>. The Array(size) { index -> ... } constructor calls the initializer for every index, producing an array of the given size.

Functional operations are a hallmark of the library. map transforms each element, filter keeps elements matching a predicate, and reduce aggregates elements starting with the first one — though it throws on empty collections. fold is the safer variant, accepting an explicit initial accumulator that allows empty inputs. mapNotNull applies a transform and discards any null results, while flatMap maps each element to an Iterable and concatenates them into one flat list. groupBy returns a Map keyed by the selector result, and partition splits the collection into a Pair of two lists based on a predicate. For finding elements, firstOrNull returns the first match or null on empty input, while singleOrNull asserts uniqueness by returning null unless exactly one element matches. To convert between collection shapes, list.toSet() or .toHashSet() dedupes into a Set, while keys.zip(values).toMap() builds a map from two parallel lists, and associate or associateWith covers transform-based construction with last-duplicate-key-wins semantics.

Lazy sequences are the eager collection's counterpart. Each operation on a sequence is fused with the next, so elements flow through the chain one at a time without producing intermediate lists — a major advantage for chained operations over large or infinite data. sequenceOf(...), sequence { yield(...); yieldAll(...) }, and asSequence() create a sequence; the sequence builder lets you hand-craft a generator. chunked(size) splits a list into fixed-size batches, useful for pagination or batched network calls, while windowed(size, step, partialWindows) produces overlapping or stepped slices. zip pairs elements from two collections into a List<Pair>, stopping at the shorter one; zipWithNext pairs adjacent elements within a single collection. distinct removes duplicates while preserving first-occurrence order, distinctBy deduplicates by a selected key, sorted and sortedDescending return new lists in ascending or descending natural order, while sortedWith combined with compareBy and thenBy sorts by multiple criteria. reversed() allocates a new list, whereas asReversed() returns a live view that reflects later mutations on a MutableList.

Strings come with rich manipulation tools. String interpolation uses $variable and ${expr}. The substring variants — substring, substringBefore, substringAfter — support both raw indices and delimiter-based extraction. replace supports literal, regex, and range-based replacement, and split accepts an optional limit that caps the number of parts returned. For efficient construction in loops, buildString { append(...) } creates a StringBuilder internally and returns the toString() result, which is preferable to repeated + concatenation; StringBuilder is the underlying type and only worth reaching for directly when reusing instances for performance. isLetterOrDigit, isDigit, isLetter, isUpperCase, and isWhitespace give readable character classification. Multi-line raw strings delimited by triple quotes span multiple lines and skip escape processing (escape a literal $ with \$); trimIndent() and trimMargin() handle indentation. someNullable?.toString() yields null safely for null receivers, and CharSequence is the interface implemented by String, StringBuilder, and StringBuffer — accept it in library functions for flexibility. Regex is constructed with Regex("pattern") or "pattern".toRegex(), used via containsMatchIn, find, findAll, and replace; the find function returns a MatchResult? whose groups you can access via groupValues or destructure with val (user, domain) = match.destructured. Error handling has no checked exceptions: try, catch, and even if are expressions, val n = try { s.toInt() } catch (e: Exception) { -1 } returns an Int. The runCatching { ... } helper wraps a block in try/catch and returns a Result<T>, on which you can chain onSuccess, onFailure, getOrNull, getOrElse, or getOrThrow — but note that runCatching catches Throwable including Error, so prefer typed try/catch for specific exceptions. toIntOrNull() safely converts strings to Int by returning null on parse failure.

All chapters
  1. 1Foundations — Types, Variables, and Expressions
  2. 2Null Safety
  3. 3Classes, Objects, and Inheritance
  4. 4Functions, Lambdas, and Scope Functions
  5. 5Properties and Delegation
  6. 6Collections, Sequences, and Strings
  7. 7Coroutines and Flow

Drill it

Reading is not remembering. These come from the Kotlin Programming deck:

Q

What is null safety in Kotlin?

Null safety is a Kotlin feature that eliminates NullPointerException at compile time.By default, variables cannot hold null. You must explicitly declare a type...

Q

How do you declare a nullable type in Kotlin?

Append a ? to the type declaration:var name: String? = nullWithout the ?, the compiler will reject any attempt to assign null.

Q

What is the safe call operator in Kotlin?

The safe call operator ?. allows you to access a property or call a method on a nullable object without risking a NullPointerException:val length = name?.length...

Q

What is the Elvis operator in Kotlin?

The Elvis operator ?: provides a default value when an expression is null:val len = name?.length ?: 0If the left side is null, the right side is used.