📖 Learn Scala — From Toolchain to Production
A code-first engineering reference for Scala 3. Every chapter is structured around annotated production code — anti-patterns, edge cases, compiler internals, and JVM-level reality. No tutorial fluff.
Scala is a statically-typed, JVM-compiled language blending object-oriented and functional programming. Used in data engineering (Spark, Kafka), distributed systems (Akka, ZIO), and backend services (Twitter, LinkedIn, Databricks). This curriculum targets Scala 3.x with notes on Scala 2 interop.
How to Use This Reference
- Read sequentially (01 → 11) for a structured progression from toolchain to production interop.
- Jump to a chapter when you hit a concept in the wild — each is self-contained.
- Run examples with
scala-cli(single files) orsbt(multi-module projects). - Debug type errors with
-Xprint:typerand-explain— see 01 for compiler introspection. - Every code block is annotated — inline comments explain under-the-hood behavior, allocation, and bytecode mapping.
Prerequisites
- Java 17+ installed (
java -version). - Scala 3 via Coursier:
cs install scala3(preferred) orbrew install scala3. - sbt:
brew install sbtorcs install sbt. - IntelliJ IDEA with Scala plugin or VS Code with Metals.
- Comfort with JVM concepts (classloaders, bytecode, garbage collection) and functional programming basics.
Curriculum
Part I — Foundations & Execution Model
| # | Topic | Why It Matters |
|---|---|---|
| 01 | Toolchain & JVM Execution Model | Compiler phases, bytecode mapping, sbt/scala-cli, project layout. Without this, you can't debug type errors or perf. |
| 02 | Value Semantics & Type Hierarchy | val/var/lazy val init semantics, primitive boxing, opaque types, AnyVal/AnyRef, IEEE 754 gotchas. |
| 03 | Functions & Closures | Function1 JVM representation, closure capture, @tailrec, by-name vs by-need, currying, inline def. |
| 04 | Pattern Matching & Exhaustiveness | Sealed exhaustiveness, type erasure traps, custom extractors, match types, Either/Try/Option as error values. |
Part II — Collections & Object Model
| # | Topic | Why It Matters |
|---|---|---|
| 05 | Collections: Persistent Data Structures | Vector RRB-trees, structural sharing, lazy views, fusion, performance characteristics, streaming patterns. |
| 06 | Classes, Traits & Object Model | Trait linearization, self types, open/sealed, universal equality, companion objects, path-dependent types. |
| 07 | Case Classes & ADTs | Generated code, ADT design, recursive types, regex patterns, custom extractors, copy and lens patterns. |
Part III — Type System & Concurrency
| # | Topic | Why It Matters |
|---|---|---|
| 08 | Type System: Variance & Phantom Types | Variance constraints, type classes (given/using), higher-kinded types, phantom types for state machines, match types. |
| 09 | Concurrency: Futures & Backpressure | ExecutionContext tuning, parallel vs sequential, retry with backoff, race conditions, Promise, Try, atomic operations. |
Part IV — Production
| # | Topic | Why It Matters |
|---|---|---|
| 10 | Testing: ScalaTest & ScalaCheck | Fixtures, property-based testing with shrinking, mock-free design with fakes, tagged integration tests, async testing. |
| 11 | Java Interoperability | Collection views vs copies, null safety bridges, SAM conversion, @targetName, Java-friendly API design, boxing overhead. |
Learning Path Suggestions
If you're coming from Java
- Read 01 closely — the compiler pipeline and bytecode mapping differ from
javac. - Skim 02–03 —
val/varand lambdas map to Java concepts but with different init semantics. - Read 04 & 07 — sealed exhaustiveness and pattern matching are Scala's biggest wins over Java.
- Read 08 — variance, type classes, and phantom types are concepts Java doesn't have.
- Read 11 — interop has subtle traps (views vs copies, boxing, checked exceptions).
If you're coming from Python/Ruby
- Read 02 — static types, type inference, and the
Anyhierarchy are fundamentally different. - Read 03–04 — closures, tail recursion, and pattern matching replace dynamic dispatch.
- Read 05 — persistent data structures and structural sharing replace mutable defaults.
- Read 08 — the type system (variance, type classes) is the biggest paradigm shift.
- Read 09 —
FutureandExecutionContextreplaceasynciowith a thread-pool model.
If you're coming from Haskell/OCaml
- Skim 01–05 — most concepts are familiar; focus on JVM-specific details (boxing, erasure).
- Read 07 — ADTs and pattern matching are similar but with different ergonomics.
- Read 08 closely — variance annotations and type classes (given/using) differ from typeclass instances.
- Read 09 —
Futureis eager (not lazy likeIO); consider ZIO or Cats Effect for laziness. - Read 11 — Java interop is unique to Scala and has real production consequences.
If you're a senior engineer using Scala in production
- Read 01 — compiler phases and bytecode mapping for debugging production issues.
- Read 05 & 08 — collection performance and type system design for API/library design.
- Read 09 —
ExecutionContexttuning and backpressure for high-throughput services. - Read 10 — property-based testing and mock-free design for maintainable test suites.
- Use 11 as a reference for cross-language team boundaries.
Key Differences from Java (Quick Reference)
| Java | Scala 3 |
|---|---|
public static void main(String[] args) | @main def run(): Unit = ... |
int x = 5; | val x = 5 (immutable) or var x = 5 (mutable) |
Optional<T> | Option[T] — Some(v) / None |
switch (fallthrough, no exhaustiveness) | match — expression, exhaustiveness-checked on sealed |
| Checked exceptions | No checked exceptions — use Try/Either |
instanceof + cast | Pattern match: case s: String => ... |
ArrayList<T> (mutable, covariant arrays) | Vector[T] (immutable, persistent) or ArrayBuffer[T] |
Function<T,R> (single interface) | T => R (Function1, with 22 arities + specialized variants) |
synchronized blocks | synchronized + AtomicLong + Future + Promise |
| Getter/setter conventions | Properties (direct field access, no getX()/setX()) |