Scala Collections: List, Vector, Map, and Set Complete Guide

TT

Scala Collections: List, Vector, Map, and Set Complete Guide

Scala's collections library is rich, consistent, and designed around immutability. Understanding when to use each collection type is essential for writing idiomatic, performant Scala. This module covers the most important collection types and their trade-offs.

Immutable vs Mutable Collections

Scala separates its collections into two packages:

  • scala.collection.immutable — the default, imported automatically
  • scala.collection.mutable — must be imported explicitly

Prefer immutable collections unless you have a specific performance reason for mutation. Immutable collections are thread-safe by default and easier to reason about.

List

List[A] is a singly-linked list. Prepending is O(1), appending is O(n), and random access is O(n).

scala
val nums = List(1, 2, 3, 4, 5)

// Creating lists
val empty = List.empty[Int]
val range = (1 to 5).toList

// Basic operations
println(nums.head)    // 1
println(nums.tail)    // List(2, 3, 4, 5)
println(nums.last)    // 5
println(nums.length)  // 5
println(nums.isEmpty) // false

// Prepending (fast — O(1))
val more = 0 :: nums        // List(0, 1, 2, 3, 4, 5)
val concat = nums ::: List(6, 7)  // List(1, 2, 3, 4, 5, 6, 7)

Use List when you primarily prepend elements or traverse the list sequentially.

Vector

Vector[A] is an immutable indexed sequence with effectively O(1) random access and O(1) append/prepend (amortized). It is backed by a wide tree structure.

scala
val vec = Vector(1, 2, 3, 4, 5)

println(vec(2))       // 3 — O(1) random access
val updated = vec.updated(2, 99)  // Vector(1, 2, 99, 4, 5)
val appended = vec :+ 6           // Vector(1, 2, 3, 4, 5, 6)
val prepended = 0 +: vec          // Vector(0, 1, 2, 3, 4, 5)

Use Vector when you need fast random access or frequent appends. It is the default IndexedSeq in Scala.

Array

Array[A] wraps Java's native array — it is mutable and has true O(1) random access. Use it for interoperability with Java or when raw performance is critical.

scala
val arr = Array(1, 2, 3, 4, 5)
arr(2) = 99  // mutable!
println(arr.mkString(", "))  // 1, 2, 99, 4, 5

Map

Map[K, V] is an immutable key-value store:

scala
val scores = Map("Alice" -> 95, "Bob" -> 87, "Carol" -> 92)

// Access
println(scores("Alice"))               // 95
println(scores.get("Dave"))            // None
println(scores.getOrElse("Dave", 0))   // 0

// Adding and updating (returns new Map)
val updated = scores + ("Dave" -> 78)
val removed = scores - "Bob"

// Iteration
scores.foreach { case (name, score) => println(s"$name: $score") }

// Transformation
val grades = scores.mapValues {
  case s if s >= 90 => "A"
  case s if s >= 80 => "B"
  case _            => "C"
}

Set

Set[A] is an immutable collection of unique elements:

scala
val fruits = Set("apple", "banana", "cherry")

println(fruits.contains("apple"))   // true
println(fruits.contains("mango"))   // false

val moreFruits = fruits + "mango"   // Set(apple, banana, cherry, mango)
val lessFruits = fruits - "banana"  // Set(apple, cherry)

val otherFruits = Set("cherry", "date", "elderberry")
println(fruits intersect otherFruits)  // Set(cherry)
println(fruits union otherFruits)      // Set(apple, banana, cherry, date, elderberry)
println(fruits diff otherFruits)       // Set(apple, banana)

Range

Range represents a sequence of integers without storing them all in memory:

scala
val r = 1 to 10          // Range(1, 2, ..., 10) — inclusive
val r2 = 1 until 10      // Range(1, 2, ..., 9) — exclusive
val r3 = 1 to 20 by 2    // Range(1, 3, 5, ..., 19) — step

println(r.sum)            // 55
println(r.toList.take(3)) // List(1, 2, 3)

Mutable Collections

When you need mutation, import from scala.collection.mutable:

scala
import scala.collection.mutable

val buf = mutable.ArrayBuffer(1, 2, 3)
buf += 4
buf ++= List(5, 6)
buf.remove(0)
println(buf)  // ArrayBuffer(2, 3, 4, 5, 6)

val mMap = mutable.HashMap("a" -> 1)
mMap("b") = 2
mMap.remove("a")
println(mMap)  // HashMap(b -> 2)

Performance Summary

CollectionPrependAppendLookupUpdate
ListO(1)O(n)O(n)O(n)
VectorO(log n)O(log n)O(log n)O(log n)
ArrayO(n)O(n)O(1)O(1)
HashMapO(1)O(1)O(1)
TreeMapO(log n)O(log n)O(log n)

Frequently Asked Questions

Q: Should I use List or Vector as my default Scala collection? For most use cases, List and Vector both work well. Use List when building up a collection by prepending elements (e.g., recursion-heavy functional code). Use Vector when you need random access by index or frequently append to the end. In practice, Vector is more versatile — it's the right default for most cases except head/tail pattern matching recursion.

Q: How do I convert between Scala and Java collections? Use import scala.jdk.CollectionConverters._ (Scala 2.13+) and call .asJava on a Scala collection or .asScala on a Java collection. For example: myScalaList.asJava gives a java.util.List.

Q: What is the difference between Map("a" -> 1) and Map(("a", 1))? They are identical. "a" -> 1 is syntactic sugar for the tuple ("a", 1). The -> method is defined in scala.Predef and returns a Tuple2. Both forms create the same Map entry.


Part of Scala Mastery Course — Module 10 of 22.