Immutable collections

Scala · Essentials · 14 min

Collections and their higher-order methods are what most everyday Scala code is made of.

Scala's default collections are immutable: operations return new collections and the old ones stay valid, safe to share across threads. The main types:

  • List is a singly linked list: O(1) prepend with ::, linear indexing. Great for recursion and head/tail processing.
  • Vector is an indexed sequence with fast (effectively constant) access and update anywhere: the default general-purpose Seq.
  • Set holds unique elements; Map holds key-value pairs, built with "a" -> 1 tuples.
  • Range (1 to 10) and Array (mutable, JVM array) complete the picture.

All share a uniform API:

  • map, filter, flatMap, collect transform;
  • foldLeft(z)(op), sum, max, count, exists, forall, find reduce or search;
  • groupBy, partition, zip, sortBy, distinct, take, drop reshape.

Maps return Option from get, use getOrElse for defaults, and updated or + to add entries. map(key) throws if the key is missing.

Chaining these methods replaces most loops: describe what you want instead of how to iterate. toList, toVector, toSet and toMap convert between types.

Example

val nums = List(5, 3, 8, 1)
println(0 :: nums)                          // List(0, 5, 3, 8, 1)
println(nums.map(_ * 2).filter(_ > 5))      // List(10, 6, 16)
println(nums.foldLeft(0)(_ + _))            // 17
println(nums.sorted.take(2))                // List(1, 3)
println(nums.exists(_ > 7))                 // true

val v = Vector("a", "b", "c")
println(v.updated(1, "B"))                  // Vector(a, B, c)
println(v)                                  // Vector(a, b, c) (unchanged)

val ages = Map("ana" -> 31, "bo" -> 25)
println(ages.get("cy"))                     // None
println(ages.getOrElse("cy", 0))            // 0
println(ages + ("cy" -> 40))                // Map(ana -> 31, bo -> 25, cy -> 40)

val words = List("apple", "avocado", "banana", "cherry")
println(words.groupBy(_.head).view.mapValues(_.size).toMap)
// Map(a -> 2, b -> 1, c -> 1) (order may vary)
println(words.partition(_.length > 5))      // (List(avocado, banana, cherry),List(apple))

Remember

  • Default collections are immutable: operations return new ones
  • List for head/tail and prepend, Vector for indexed access
  • map, filter, foldLeft, groupBy and friends replace loops
  • Map.get returns an Option; map(key) throws if missing

Watch out for

  • Using list(i) in a loop over a List, which is O(n) per access; use a Vector or iterate.

Check yourself

  1. Which collection gives fast random access by index?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scala-lang.org/scala3/book/collections-intro.html