Sets & Maps

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Scala 3.10 (3.10.0, released 2026-10-12) and every example was compiled and run with Scala 3.10.0 through Scala CLI on JDK 26 (checked on 2026-10-15). Sets and maps belong to the collections library, whose API is the Scala 2.13 one (since 3.8 it is compiled by Scala 3 and versioned with the compiler), so the 3.9 LTS (3.9.0, 2026-09-03) and 3.3 LTS (3.3.8) lines behave the same way; see Scala 3.9 LTS versus 3.10.

A Set holds distinct elements and answers "is this in the set?". A Map associates distinct keys with values and answers "what is the value for this key?". Both come in an immutable default (Set, Map with no import) and a mutable version in scala.collection.mutable, with several implementations that differ in iteration order and in cost. The overall picture is on Collections Overview; sequences are on Sequences; the transformations (map, groupBy, foldLeft, …​) are on Collection Operations; costs are tabulated on Collection Performance & Equality. The examples use the trailhead scenario: peaks as sets and trail distances as maps. For the Java side see the Java Collections Framework.

Which set or map

You need Set Map

Fast membership or lookup, order does not matter

HashSet (default Set)

HashMap (default Map)

Sorted iteration, range queries

TreeSet (a SortedSet)

TreeMap (a SortedMap)

Iteration in insertion order

mutable.LinkedHashSet, ListSet

VectorMap (immutable), ListMap (immutable, small), mutable.LinkedHashMap

Small sets of integers

BitSet

Not applicable

Updates in place

mutable.Set, mutable.HashSet

mutable.Map, mutable.HashMap

Shared across threads

java.util.concurrent sets via asScala

concurrent.TrieMap, ConcurrentHashMap via asScala

Immutable Set

Set(...) builds an immutable set. + and - return a new set, contains tests membership, and the operators & (intersection), | (union) and &~ (difference) mirror the named methods intersect, union and diff.

//> using scala 3.10.0
package trailhead

@main def sets(): Unit =
  val peaks = Set("Alpina", "Rocca", "Alpina")
  println(peaks)
  println(peaks.size)
  println(peaks + "Vetta")
  println(peaks - "Rocca")
  println(peaks.contains("Rocca"))
  println(peaks("Rocca"))
  println(peaks ++ Set("X", "Y"))
  println(peaks.map(_.length))
  println(Set(1, 2, 3) & Set(2, 3, 4))
  println(Set(1, 2, 3) | Set(2, 3, 4))
  println(Set(1, 2, 3) &~ Set(2, 3, 4))
  println(Set(1, 2).subsetOf(Set(1, 2, 3)))
  println(Set(1, 2) == Set(2, 1))
  println(List(1, 2, 3).filter(Set(1, 2)))
  println(Set(1, 2, 3, 4).getClass.getSimpleName)
  println(Set(1, 2, 3, 4, 5).getClass.getSimpleName)
Set(Alpina, Rocca)
2
Set(Alpina, Rocca, Vetta)
Set(Alpina)
true
true
Set(Alpina, Rocca, X, Y)
Set(6, 5)
Set(2, 3)
Set(1, 2, 3, 4)
Set(1)
true
true
List(1, 2)
Set4
HashSet

A few details from the output. Duplicates in the literal are discarded: the first line has two elements, not three. map on a set returns a set, so peaks.map(_.length) collapses two equal lengths if there were any. peaks("Rocca") is contains, because a Set[A] is also a function A => Boolean; this is why filter(Set(1, 2)) works with a set as the predicate. Sets of up to four elements use specialised classes (Set1 to Set4, here Set4) that keep insertion order; from five elements the class is HashSet, a hash trie whose iteration order is unspecified.

Mutable Set

A mutable.Set changes in place: += adds, -= removes, ++= adds many, and add and remove return a Boolean that tells whether the set changed, which is convenient for "seen before" checks.

//> using scala 3.10.0
package trailhead

import scala.collection.mutable

@main def mutableSets(): Unit =
  val s = mutable.Set(1, 2)
  s += 3
  s ++= List(4, 5)
  println(s)
  println(s.add(3))
  println(s.add(6))
  println(s.remove(1))
  s -= 2
  println(s)
  s.filterInPlace(_ > 3)
  println(s)
  val lhs = mutable.LinkedHashSet("c", "a", "b")
  println(lhs)
  var imm = Set(1, 2)
  imm += 3
  println(imm)
HashSet(1, 2, 3, 4, 5)
false
true
true
HashSet(3, 4, 5, 6)
HashSet(4, 5, 6)
LinkedHashSet(c, a, b)
Set(1, 2, 3)

The default mutable.Set is a mutable.HashSet. mutable.LinkedHashSet keeps insertion order. The last two lines compare with a var holding an immutable Set: imm += 3 is rewritten by the compiler to imm = imm + 3, a new set each time, which is fine for small, local state. If a val held the set that rewrite would not compile.

Sorted sets: TreeSet, and BitSet

TreeSet is an immutable red-black tree that keeps its elements ordered by an Ordering; SortedSet(...) builds one. Besides the ordinary set operations it offers range, rangeFrom, rangeUntil, rangeTo, firstKey, lastKey and iteratorFrom. A custom ordering is passed with using. BitSet is a set of small non-negative integers stored as bits; ListSet is a set backed by a list that keeps insertion order but has linear lookup and is only suitable for very small sets.

//> using scala 3.10.0
package trailhead

import scala.collection.immutable.{BitSet, ListSet, SortedSet, TreeSet}

@main def sortedSets(): Unit =
  val ts = TreeSet(5, 1, 3)
  println(ts)
  println(ts.range(2, 5))
  println(ts.rangeFrom(3))
  println(ts.rangeUntil(3))
  println(ts.rangeTo(3))
  println(ts.firstKey)
  println(ts.lastKey)
  println(ts + 2)
  println(ts.iteratorFrom(2).toList)
  println(TreeSet(5, 1, 3)(using Ordering[Int].reverse))
  println(SortedSet("pass", "ridge", "falls"))
  val bs = BitSet(1, 5, 3, 64, 200)
  println(bs)
  println(bs.contains(5))
  println(bs + 7)
  println(ListSet(3, 1, 2))
TreeSet(1, 3, 5)
TreeSet(3)
TreeSet(3, 5)
TreeSet(1)
TreeSet(1, 3)
1
5
TreeSet(1, 2, 3, 5)
List(3, 5)
TreeSet(5, 3, 1)
TreeSet(falls, pass, ridge)
BitSet(1, 3, 5, 64, 200)
true
BitSet(1, 3, 5, 7, 64, 200)
ListSet(3, 1, 2)

All sorted operations take logarithmic time. The key type needs an implicit Ordering; for custom classes either define a given Ordering or make the class Ordered, see Type Classes. A BitSet stores one bit per possible element, so its size is proportional to the largest element (the set 1, 5, 3, 64, 200 needs four 64-bit words); it is excellent for dense small integers and wasteful for a few large ones.

Immutable Map

A Map[K, V] is built from (key, value) pairs, most readably written with ->. Lookup with m(k) throws when the key is missing; get returns an Option and getOrElse returns a default (evaluated only when the key is missing, because the parameter is by-name). + and updated add or replace one entry, - and removedAll remove, and ++ merges, with the right operand winning on conflicts.

//> using scala 3.10.0
package trailhead

@main def maps(): Unit =
  val km = Map("Ridge" -> 12.5, "Falls" -> 4.0, "Pass" -> 8.25)
  println(km("Ridge"))
  println(km.get("Falls"))
  println(km.get("Lake"))
  println(km.getOrElse("Lake", 0.0))
  println(km.contains("Pass"))
  println(km + ("Lake" -> 3.0))
  println(km - "Pass")
  println(km ++ Map("Pass" -> 9.0, "Hut" -> 1.0))
  println(km.updated("Ridge", 13.0))
  println(km.keys)
  println(km.values)
  println(km.keySet)
  println(km.map((k, v) => k -> v * 2))
  println(km.view.mapValues(_ * 2).toMap)
  println(km.filter((_, v) => v > 5))
  println(km.toList)
  println(km.removedAll(List("Pass", "Falls")))
  println(km.updatedWith("Ridge")(_.map(_ + 1)))
  println(km.updatedWith("Lake")(_ => Some(2.0)))
  println(km.updatedWith("Pass")(_ => None))
12.5
Some(4.0)
None
0.0
true
Map(Ridge -> 12.5, Falls -> 4.0, Pass -> 8.25, Lake -> 3.0)
Map(Ridge -> 12.5, Falls -> 4.0)
Map(Ridge -> 12.5, Falls -> 4.0, Pass -> 9.0, Hut -> 1.0)
Map(Ridge -> 13.0, Falls -> 4.0, Pass -> 8.25)
Set(Ridge, Falls, Pass)
Iterable(12.5, 4.0, 8.25)
Set(Ridge, Falls, Pass)
Map(Ridge -> 25.0, Falls -> 8.0, Pass -> 16.5)
Map(Ridge -> 25.0, Falls -> 8.0, Pass -> 16.5)
Map(Ridge -> 12.5, Pass -> 8.25)
List((Ridge,12.5), (Falls,4.0), (Pass,8.25))
Map(Ridge -> 12.5)
Map(Ridge -> 13.5, Falls -> 4.0, Pass -> 8.25)
Map(Ridge -> 12.5, Falls -> 4.0, Pass -> 8.25, Lake -> 2.0)
Map(Ridge -> 12.5, Falls -> 4.0)

km.keys is an Iterable, km.keySet a Set, km.values an Iterable. Mapping a Map with a function that returns a pair (k -> v * 2) gives a Map; with a function that returns anything else the result is an Iterable. Scala 3 accepts the parameter-untupling form (k, v) => ... for functions over pairs, which Scala 2 did not, so no case (k, v) is needed.

updatedWith(key)(f) is the one-step update: f receives Some(oldValue) or None and returns Some(newValue) to set or None to remove. It replaces the get-then-updated pattern and it is the Scala counterpart of Java’s Map.compute. The same method exists on mutable.Map as updateWith.

Asking for a missing key throws, with a message that names the key:

//> using scala 3.10.0
@main def missing(): Unit =
  val km = Map("Ridge" -> 12.5)
  println(km("Lake"))
Exception in thread "main" java.util.NoSuchElementException: key not found: Lake
	at scala.collection.immutable.Map$Map1.apply(Map.scala:291)
	at missing$package$.missing(missing.scala:4)
	at missing.main(missing.scala:2)

Maps and sets as functions

A Map[K, V] is a PartialFunction[K, V] and a Set[A] is a Function1[A, Boolean], so either can be passed where a function is expected. collect(map) keeps only the keys that are present and replaces them with their values; map(m.lift) produces Option values; filterNot(set) drops the members; applyOrElse is the one-call default.

//> using scala 3.10.0
package trailhead

@main def asFunctions(): Unit =
  val km = Map("Ridge" -> 12.5, "Falls" -> 4.0)
  val closed = Set("Pass", "Lake")
  println(List("Ridge", "Lake", "Falls").collect(km))
  println(List("Ridge", "Lake", "Falls").map(km.lift))
  println(List("Ridge", "Pass", "Falls").filterNot(closed))
  println(List("Ridge", "Lake").map(km.getOrElse(_, 0.0)))
  println(km.applyOrElse("Lake", _ => -1.0))
  val nested = Map("a" -> Map("x" -> 1))
  println(nested.get("a").flatMap(_.get("x")))
  println(nested.getOrElse("b", Map.empty).get("x"))
  println(km.view.filterKeys(_.startsWith("R")).toMap)
  println(km.maxBy(_._2))
  println(km.toSeq.sortBy(-_._2).map(_._1))
  println(km.foldLeft(0.0)((acc, kv) => acc + kv._2))
  println(km.keySet.intersect(Set("Ridge", "Lake")))
List(12.5, 4.0)
List(Some(12.5), None, Some(4.0))
List(Ridge, Falls)
List(12.5, 0.0)
-1.0
Some(1)
None
Map(Ridge -> 12.5)
(Ridge,12.5)
List(Ridge, Falls)
16.5
Set(Ridge)

Nested maps are read with get and flatMap, so that a missing key at any level gives None instead of an exception. view.filterKeys(...) is the lazy, view-based form of key filtering, described on Views, Iterators & LazyList.

Mutable Map

A mutable.Map adds update (m(k) = v), += for a pair, put and remove (which return the previous value as an Option), getOrElseUpdate, and in-place bulk operations.

//> using scala 3.10.0
package trailhead

import scala.collection.mutable

@main def mutableMaps(): Unit =
  val m = mutable.Map("a" -> 1)
  m("b") = 2
  m += ("c" -> 3)
  m.update("d", 4)
  println(m)
  println(m.put("a", 10))
  println(m.put("z", 0))
  println(m.remove("z"))
  println(m.remove("zz"))
  println(m.getOrElseUpdate("e", 5))
  println(m.getOrElseUpdate("e", 6))
  m.mapValuesInPlace((_, v) => v * 2)
  println(m.toSeq.sortBy(_._1))
  m.filterInPlace((k, _) => k != "a")
  println(m.keys.toList.sorted)
  m.updateWith("b")(_.map(_ + 1))
  println(m("b"))
HashMap(a -> 1, b -> 2, c -> 3, d -> 4)
Some(1)
None
Some(0)
None
5
5
List((a,20), (b,4), (c,6), (d,8), (e,10))
List(b, c, d, e)
5

getOrElseUpdate(key, default) computes the default only when the key is missing and stores it; the second call with the same key returns the stored value without running the computation, which is why both printed 5. It is the Scala counterpart of Java’s computeIfAbsent (and of Kotlin’s getOrPut). m(k) = v desugars to m.update(k, v). Modifying a map held in a val is fine because the reference is constant, not the contents.

Default values

A map can be wrapped so that a missing key returns a default instead of throwing. withDefaultValue(v) returns v for every missing key and withDefault(f) computes the default from the key. The default does not affect get and contains, which still report the truth, and it is not stored.

//> using scala 3.10.0
package trailhead

import scala.collection.mutable

@main def defaults(): Unit =
  val counts = mutable.Map.empty[Char, Int].withDefaultValue(0)
  for c <- "trailhead" do counts(c) += 1
  println(counts.toSeq.sortBy(_._1))
  println(counts('z'))
  println(counts.contains('z'))
  val byLength = Map("a" -> 1).withDefault(k => k.length)
  println(byLength("zzz"))
  println(byLength.get("zzz"))
  val zero = Map("a" -> 1).withDefaultValue(0)
  println(zero("q"))
  println(zero.getClass.getSimpleName)
List((a,2), (d,1), (e,1), (h,1), (i,1), (l,1), (r,1), (t,1))
0
false
3
None
0
WithDefault

The counting loop, counts(c) += 1, expands to counts(c) = counts(c) + 1, and relies on the default 0 for a new character; this is the compact way to count in Scala and it works with a mutable.Map only. For an immutable map use updatedWith or fold: xs.foldLeft(Map.empty[Char, Int])((m, c) => m.updated(c, m.getOrElse(c, 0) + 1)).

Ordered maps: TreeMap, ListMap, VectorMap and LinkedHashMap

By default an immutable map with more than four entries is a hash trie and iterates in an unspecified order. When order matters choose a class that defines it:

  • TreeMap (immutable or mutable): sorted by key, with range, rangeFrom, firstKey, lastKey, minAfter and maxBefore.

  • ListMap and VectorMap (immutable): insertion order. ListMap has linear lookup and is for tiny maps; VectorMap has effectively constant lookup and is the one to use when you need an immutable insertion-ordered map.

  • mutable.LinkedHashMap: mutable insertion order with constant time lookup.

//> using scala 3.10.0
package trailhead

import scala.collection.immutable.{ListMap, SortedMap, TreeMap, VectorMap}
import scala.collection.mutable

@main def orderedMaps(): Unit =
  val tm = TreeMap("Pass" -> 8.25, "Falls" -> 4.0, "Ridge" -> 12.5)
  println(tm)
  println(tm.firstKey)
  println(tm.lastKey)
  println(tm.range("G", "Q"))
  println(tm.rangeFrom("Pass"))
  println(tm.minAfter("G"))
  println(tm.maxBefore("G"))
  println(tm.keysIteratorFrom("Pass").toList)
  println(SortedMap("b" -> 1, "a" -> 2).getClass.getSimpleName)
  val six = (1 to 6).map(i => s"k$i" -> i)
  println(Map.from(six))
  println(ListMap.from(six))
  println(VectorMap.from(six))
  println(mutable.LinkedHashMap.from(six))
  println(Map.from(six).getClass.getSimpleName)
  println(Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4).getClass.getSimpleName)
  println(VectorMap("x" -> 1, "y" -> 2, "z" -> 3) - "y" + ("y" -> 9))
  println(ListMap("x" -> 1, "y" -> 2) + ("x" -> 5))
  val mm = mutable.TreeMap(3 -> "c", 1 -> "a")
  mm(2) = "b"
  println(mm)
  val lhm = mutable.LinkedHashMap("z" -> 1, "a" -> 2)
  lhm("m") = 3
  println(lhm)
TreeMap(Falls -> 4.0, Pass -> 8.25, Ridge -> 12.5)
Falls
Ridge
TreeMap(Pass -> 8.25)
TreeMap(Pass -> 8.25, Ridge -> 12.5)
Some((Pass,8.25))
Some((Falls,4.0))
List(Pass, Ridge)
TreeMap
HashMap(k2 -> 2, k5 -> 5, k6 -> 6, k1 -> 1, k4 -> 4, k3 -> 3)
ListMap(k1 -> 1, k2 -> 2, k3 -> 3, k4 -> 4, k5 -> 5, k6 -> 6)
VectorMap(k1 -> 1, k2 -> 2, k3 -> 3, k4 -> 4, k5 -> 5, k6 -> 6)
LinkedHashMap(k1 -> 1, k2 -> 2, k3 -> 3, k4 -> 4, k5 -> 5, k6 -> 6)
HashMap
Map4
VectorMap(x -> 1, z -> 3, y -> 9)
ListMap(x -> 5, y -> 2)
TreeMap(1 -> a, 2 -> b, 3 -> c)
LinkedHashMap(z -> 1, a -> 2, m -> 3)

Note in the output that Map.from(six) (six entries) is a HashMap with scrambled order, while ListMap, VectorMap and LinkedHashMap keep the order k1 to k6; and that Map with four entries is the specialised class Map4. Updating a key that already exists in a ListMap keeps its original position (x -> 5 still first), and re-adding a removed key to a VectorMap puts it at the end.

Concurrent maps

A scala.collection.concurrent.Map is a mutable map whose operations are atomic: putIfAbsent, remove(key, value), replace(key, value) and getOrElseUpdate. The standard implementation is TrieMap, a lock-free hash trie with constant time snapshots; Java’s ConcurrentHashMap is available as the same interface through asScala.

//> using scala 3.10.0
package trailhead

import java.util.concurrent.ConcurrentHashMap
import scala.collection.concurrent.TrieMap
import scala.jdk.CollectionConverters.*

@main def concurrentMaps(): Unit =
  val tr = TrieMap("a" -> 1)
  tr.put("b", 2)
  println(tr.putIfAbsent("a", 99))
  println(tr.putIfAbsent("c", 3))
  println(tr.replace("a", 10))
  println(tr.remove("b", 2))
  println(tr.getOrElseUpdate("d", 4))
  println(tr.toSeq.sortBy(_._1))
  println(tr.readOnlySnapshot().size)
  println(tr.getClass.getName)
  val chm = ConcurrentHashMap[String, Int]()
  val m = chm.asScala
  m.put("a", 1)
  println(m.putIfAbsent("a", 2))
  println(m.getOrElseUpdate("b", 5))
  println(chm.get("b"))
  println(m.getClass.getName)
  println(m.updateWith("a")(_.map(_ + 1)))
  println(m.toSeq.sortBy(_._1))
Some(1)
None
Some(1)
true
4
List((a,10), (c,3), (d,4))
3
scala.collection.concurrent.TrieMap
Some(1)
5
5
scala.collection.convert.JavaCollectionWrappers$JConcurrentMapWrapper
Some(2)
List((a,2), (b,5))

putIfAbsent returns the existing value (Some(1)) or None when it inserted; remove(key, value) and replace(key, value) are conditional and return a Boolean or the previous value. readOnlySnapshot() is an immutable view taken in constant time. Plain mutable.HashMap and mutable.HashSet are not thread safe; use a concurrent map, an immutable map in an atomic reference, or confine the map to one thread. See also Parallel Collections & Concurrency Libraries, and for a Java thread-safety comparison the Java page on Virtual Threads.

Mutable versus immutable at the boundary

A mutable map is an scala.collection.Map but not an immutable.Map, and the compiler distinguishes the two Map traits in its error message by adding a superscript to the name; val m = Map(...) followed by m("b") = 2 fails because only the mutable map has update:

//> using scala 3.10.0
import scala.collection.mutable

@main def bad(): Unit =
  val m = Map("a" -> 1)
  m("b") = 2
  val mm = mutable.Map(1 -> "a")
  val i: Map[Int, String] = mm
-- [E008] Not Found Error: mapbad.scala:6:2
6 |  m("b") = 2
  |  ^
  |value update is not a member of Map[String, Int] - did you mean m.updated?
-- [E007] Type Mismatch Error: mapbad.scala:8:28
8 |  val i: Map[Int, String] = mm
  |                            ^^
  |           Found:    (mm : scala.collection.mutable.Map[Int, String])
  |           Required: Map²[Int, String]
  |
  |           where:    Map  is a trait in package scala.collection.mutable
  |                     Map² is a trait in package scala.collection.immutable
  |
  | longer explanation available when compiling with `-explain`
2 errors found
Compilation failed

Expose a mutable map through the read-only type scala.collection.Map when callers should not change it, and know that this is a view, not a copy: later changes through the owner are visible. For a frozen copy call toMap:

//> using scala 3.10.0
package trailhead

import scala.collection.mutable

@main def sharing(): Unit =
  val shared = mutable.Map("a" -> 1)
  val readOnly: scala.collection.Map[String, Int] = shared
  shared("b") = 2
  println(readOnly)
  val snapshot = shared.toMap
  shared("c") = 3
  println(snapshot)
HashMap(a -> 1, b -> 2)
Map(a -> 1, b -> 2)

Comparison with Java and Kotlin

Aspect Scala Java Kotlin

Default map

Immutable Map; hash trie for more than four entries.

Map interface; HashMap is the usual implementation; Map.of is unmodifiable.

mapOf returns a read-only LinkedHashMap; mutableMapOf a mutable LinkedHashMap.

Iteration order

Unspecified for HashMap; insertion order for VectorMap and LinkedHashMap.

Unspecified for HashMap; insertion order for LinkedHashMap; Map.of is randomised per JVM run.

Insertion order by default.

Sorted

TreeMap and SortedMap, with range and minAfter.

TreeMap and NavigableMap.

sortedMapOf returns a TreeMap.

Missing key

m(k) throws NoSuchElementException; get returns an Option.

get returns null.

get returns V?, getValue throws.

Get or create

getOrElseUpdate on mutable maps.

computeIfAbsent.

getOrPut.

Atomic update

updatedWith and updateWith, and the concurrent Map methods.

compute, merge, computeIfPresent.

compute and merge through the Java Map.

Sets

Set is also a function; &, | and &~ operators.

Set.retainAll, addAll and removeAll mutate the set.

intersect, union, subtract and the + and - operators return a new set.

Null keys and values

Allowed in HashMap but discouraged; use Option for absence.

HashMap allows one null key; ConcurrentHashMap allows none.

Nullable keys need a nullable type; see Kotlin Null Safety.

For the Option-based style, see Optional and Option & Null Safety. Passing maps and sets across the Scala-Java boundary uses the converters in Collections Overview.

Common pitfalls

  • Relying on Set or Map iteration order when there are more than four entries. Use VectorMap or a TreeMap.

  • m(key) on a missing key throws. Use get, getOrElse or a default map.

  • Deprecated mapValues. m.mapValues(f) returns a lazy view that recomputes f on every access and is deprecated since 2.13.0 ("Use .view.mapValues(f). A future version will include a strict version of this method"); write m.view.mapValues(f).toMap for a strict map, or m.map((k, v) => k -> f(v)). The same applies to filterKeys, with .view.filterKeys(p).toMap.

  • A literal with duplicate keys keeps the last one, silently: Map("a" -> 1, "a" -> 2) is Map(a -> 2).

  • Mapping a Set merges results: Set(1, 2, 3).map(_ % 2) is Set(1, 0), not three elements. Convert to a List first when you want to keep duplicates.

  • Mutating the default of withDefault. The default is not stored; counts(c) for a missing key returns 0 but does not add the key.

  • Forgetting thread safety. Non-concurrent mutable maps and sets corrupt under concurrent writes.

  • Using a ListMap for large data: lookup is linear. Use VectorMap.

  • Comparing a Set with a List is rejected by the compiler (E172), see Collections Overview.

Here is the deprecation in action, with the strict replacement and a map with a duplicate literal key:

//> using scala 3.10.0
package trailhead

@main def mapDeprecated(): Unit =
  val m = Map("a" -> 1)
  println(m.mapValues(_ + 1).toMap)
  println(m.view.mapValues(_ + 1).getClass.getSimpleName)
  val dup: Map[String, Int] = Map("a" -> 1, "a" -> 2)
  println(dup)
  for (k, v) <- m do println(s"$k=$v")
  println(m.map((k, v) => (k.length, v)))
  println(m.map((k, v) => k))
  println(m.groupBy(_._2 % 2))
Map(a -> 2)
MapValues
Map(a -> 2)
a=1
Map(1 -> 1)
List(a)
HashMap(1 -> Map(a -> 1))
-- Deprecation Warning: mapdep.scala:6:12
6 |  println(m.mapValues(_ + 1).toMap)
  |          ^^^^^^^^^^^
  |method mapValues in trait MapOps is deprecated since 2.13.0: Use .view.mapValues(f). A future version will include a strict version of this method (for now, .view.mapValues(f).toMap).
1 warning found

Where the books are outdated

  • mapValues and filterKeys on Map returned lazy views and were fine in 2.12 material; they are deprecated since 2.13.0.

  • Scala 2 code must write case (k, v) => in lambdas over pairs; Scala 3 lets you write (k, v) => directly.

  • scala.collection.JavaConverters (and JavaConversions) appear in material about ConcurrentHashMap and Java maps; use scala.jdk.CollectionConverters (asScala) as shown above.

  • The + method on a map that takes several pairs, m + (a -> 1, b -> 2), is deprecated since 2.13.0: the compiler asks for ++ with an explicit collection argument instead; write m ++ Map(a -> 1, b -> 2).

Scala 3.9 LTS versus 3.10

The set and map classes, their operations and the compiler messages shown on this page are identical on 3.3.8, 3.9.0 and 3.10.0: every program was compiled and run on all three and produced the same output, including the E007 and E008 errors and the mapValues deprecation. Nothing in the sets and maps part of the library is gated behind a newer Scala 3 release.