Collections and functional style
The collection hierarchy, the transformations you use daily, and for-comprehensions for readable pipelines.
The collection hierarchy
Scala separates an immutable hierarchy from a mutable one, and the immutable types are imported by default. The names are the same, so the difference you notice in code is whether a method returns a new collection or updates in place.
| Collection | Ordered? | Notes |
|---|---|---|
List | Yes | Immutable linked list: O(1) prepend, O(n) index access |
Vector | Yes | Effectively O(1) index and update; the default for large sequences |
ArraySeq | Yes | An immutable wrapper over an array |
Set | No | HashSet for membership, SortedSet to keep order |
Map | No | Key and value lookup; SortedMap or ListMap keep order |
Range | Yes | A sequence with no storage, such as 1 to 1000 |
LazyList | Yes | Computed on demand, so it can be infinite |
val xs = List(1, 2, 3, 4)
val ys = Vector(1, 2, 3)
val prepended = 0 +: xs // O(1) for a List
val appended = xs :+ 5 // O(n) for a List
val joined = xs ++ ys
xs.head // 1
xs.tail // List(2, 3, 4)
xs.take(2) // List(1, 2)
xs.drop(2) // List(3, 4)
xs.sliding(2).toList // List(List(1,2), List(2,3), List(3,4))
xs.grouped(3).toList // List(List(1,2,3), List(4))
xs.lift(10) // None, instead of an exceptionTransformations
val numbers = (1 to 10).toList
numbers.map(_ * 2) // transform every element
numbers.filter(_ % 2 == 0) // keep some
numbers.collect { case n if n > 5 => n * n } // transform and filter in one pass
numbers.foldLeft(0)(_ + _) // combine, starting from a value
numbers.reduce(_ + _) // combine, and fail on an empty collection
numbers.partition(_ % 2 == 0) // a pair: evens and odds
numbers.groupBy(_ % 3) // a Map from key to bucket
numbers.sortBy(-_) // descending
numbers.zipWithIndex.map { case (n, i) => s"$i:$n" }
val words = List("delta", "alpha", "beta")
words.min // "alpha", using Ordering[String]
words.maxBy(_.length)
words.mkString("[", ", ", "]") // "[delta, alpha, beta]"
// a pipeline, read top to bottom: each step produces a new collection
val result = (1 to 100).toList
.filter(_ % 3 == 0)
.map(n => n * n)
.take(5)
.summap,filter,flatMapandfoldare the core four; most other methods are conveniences built from them.- Use
collectwhen you want to transform some elements and discard the rest in a single pass. viewmakes a chain lazy, so a long sequence is not materialised at every step — better for performance and often clearer.foldLefttakes an initial value, so it works on an empty collection;reducedoes not and will throw.
for-comprehensions
A for comprehension is sugar over map, flatMap and withFilter. That is why it works for Option, Either and Future as well as for collections.
val people = List(("Ada", 36), ("Grace", 45), ("Alan", 41))
val adults = for {
(name, age) <- people
if age >= 40
} yield name // List(Grace, Alan)
// the same thing written with explicit calls
val adults2 = people.withFilter { case (_, age) => age >= 40 }.map(_._1)
// Option composes without nested matches
def lookup(id: Long): Option[String] = ???
def lookupAge(name: String): Option[Int] = ???
val summary: Option[String] = for {
name <- lookup(1)
age <- lookupAge(name)
} yield s"$name is $age"
// Future composes exactly the same way
val report = for {
user <- fetchUser(1)
items <- fetchItems(user.id)
} yield Report(user, items)💡
One syntax, one set of rules, several behaviours: a comprehension over a
List produces every combination, over an Option it short-circuits at the first None, and over a Future it sequences asynchronous work. Learn the desugaring once and you can read all of them.FAQ
List or Vector?
List when you build by prepending and walk front to back; Vector when you index or update large sequences. Below a few thousand elements the difference is rarely worth measuring.Why is my chain of operations slow?
Each step on a
List allocates an intermediate collection. Insert .view before the chain to fuse the steps, switch to Vector, or use iterator when a single pass is enough.Related
Case classes and pattern matching Syntax and immutability
Last refreshed 2026-09-18.