Scala Collections: List, Vector, Map, and Set Complete Guide
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 automaticallyscala.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).
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.
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.
val arr = Array(1, 2, 3, 4, 5)
arr(2) = 99 // mutable!
println(arr.mkString(", ")) // 1, 2, 99, 4, 5Map
Map[K, V] is an immutable key-value store:
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:
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:
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:
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
| Collection | Prepend | Append | Lookup | Update |
|---|---|---|---|---|
List | O(1) | O(n) | O(n) | O(n) |
Vector | O(log n) | O(log n) | O(log n) | O(log n) |
Array | O(n) | O(n) | O(1) | O(1) |
HashMap | — | O(1) | O(1) | O(1) |
TreeMap | — | O(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.
