Coding · Programming languages
Scala tutorial for beginners
Short answer
Scala is a statically typed JVM language that combines object-oriented and functional programming, used widely for data engineering with Apache Spark and for concurrent backends. Its key features are type inference, immutable collections, case classes and pattern matching. This page uses Scala 3 to cover setup, values, control flow, functions, collections, traits, enums and error handling with Option, Either and Try.
What Scala is and what it is used for
Scala is a statically typed language that combines object-oriented and functional programming. It was created by Martin Odersky at EPFL and runs mainly on the Java Virtual Machine, with Scala.js for the browser and Scala Native for native binaries. Scala code can use any Java library.
Scala is widely used for data engineering (Apache Spark is written in Scala), for concurrent and distributed backends (Akka, Pekko, ZIO, Cats Effect), and for financial and streaming systems. This tutorial uses Scala 3 syntax, which allows indentation-based blocks; braces still work.
Install Scala and run Hello, world
The recommended installer is Coursier (cs setup), which installs the scala command and a JDK. In Scala 3, a method annotated with @main is a program entry point.
// hello.scala
@main def hello(): Unit =
println("Hello, world!")scala run hello.scala
# Hello, world!For larger projects, sbt and Mill are the common build tools. Scastie (scastie.scala-lang.org) runs Scala in the browser without installing anything.
Values, variables and basic types
Use val for an immutable value and var for a mutable variable. Types are inferred but can be written after a colon. Idiomatic Scala uses val almost everywhere.
val name = "Scala" // String
var count: Int = 1 // mutable
count += 1
val ratio = 3.0 / 4 // Double: 0.75
val big = 12345678901L // Long
val ok = count > 1 // Boolean
println(s"$name: count=$count ratio=$ratio ok=$ok")
// Scala: count=2 ratio=0.75 ok=true
println(f"pi is ${math.Pi}%.3f") // pi is 3.142| Type | Example | Notes |
|---|---|---|
Int | 42 | 32-bit signed integer |
Long | 42L | 64-bit signed integer |
Double | 3.14 | 64-bit floating point |
Boolean | true | true or false |
String | "hi" | Java string; s"..." and f"..." interpolators |
Unit | () | like void: no meaningful value |
Everything in Scala is an expression: if, blocks and match all return a value, so you rarely need temporary vars.
Control flow and pattern matching
if returns a value. for loops over ranges and collections, and with yield it builds a new collection. match is Scala's powerful pattern matching, a signature feature of the language.
val n = 7
val parity = if n % 2 == 0 then "even" else "odd"
println(parity) // odd
for i <- 1 to 3 do print(s"$i ") // 1 2 3
println()
val squares = for i <- 1 to 4 yield i * i
println(squares) // Vector(1, 4, 9, 16)
def describe(x: Any): String = x match
case 0 => "zero"
case i: Int if i < 0 => "negative int"
case i: Int => s"int $i"
case s: String => s"string of length ${s.length}"
case (a, b) => s"pair $a and $b"
case _ => "something else"
println(describe(-5)) // negative int
println(describe("hey")) // string of length 3
println(describe((1, "x"))) // pair 1 and x1 to 3 includes 3; 1 until 3 stops before 3. The final case _ is a wildcard that matches anything; without it, an unmatched value throws a MatchError.
Functions and higher-order functions
Methods are defined with def. Functions are values: you can store them, pass them as arguments and return them. Parameters can have default values, and callers can use named arguments.
def add(a: Int, b: Int): Int = a + b
def greet(name: String, greeting: String = "Hello"): String =
s"$greeting, $name!"
val double = (x: Int) => x * 2 // function value
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
println(add(2, 3)) // 5
println(greet("Ann")) // Hello, Ann!
println(greet("Ann", greeting = "Hi")) // Hi, Ann!
println(applyTwice(double, 5)) // 20
println(List(1, 2, 3, 4).filter(_ % 2 == 0).map(_ * 10)) // List(20, 40)The underscore in _ * 10 is shorthand for a one-parameter lambda, x => x * 10.
Collections
The standard collections are immutable by default: operations such as map, filter and :+ return a new collection and leave the original unchanged. Mutable versions live in scala.collection.mutable.
val nums = List(5, 3, 8)
val more = nums :+ 1 // List(5, 3, 8, 1); nums unchanged
println(more.sorted) // List(1, 3, 5, 8)
println(nums.sum) // 16
val v = Vector("a", "b", "c") // fast indexed access
println(v(1)) // b
val ages = Map("Ann" -> 31, "Bo" -> 25)
println(ages("Ann")) // 31
println(ages.get("Zed")) // None
println(ages.getOrElse("Zed", 0)) // 0
val unique = Set(1, 2, 2, 3)
println(unique.size) // 3
val words = List("apple", "avocado", "banana")
println(words.groupBy(_.head)('a')) // List(apple, avocado)| Collection | Use it for |
|---|---|
List | immutable linked list; fast prepend and head/tail access |
Vector | immutable indexed sequence; good general default |
Map | key-value lookup |
Set | unique elements and membership tests |
Array | mutable, fixed-size Java array |
Case classes, traits and enums
A case class is an immutable data type that automatically gets equality, toString, copy and pattern-matching support. A trait is like an interface that can also contain code. Scala 3 adds enum for algebraic data types.
trait Shape:
def area: Double
case class Circle(r: Double) extends Shape:
def area: Double = math.Pi * r * r
case class Rect(w: Double, h: Double) extends Shape:
def area: Double = w * h
enum Color:
case Red, Green, Blue
@main def shapes(): Unit =
val all: List[Shape] = List(Circle(1), Rect(2, 3))
all.foreach(s => println(f"$s area=${s.area}%.2f"))
// Circle(1.0) area=3.14
// Rect(2.0,3.0) area=6.00
val r = Rect(2, 3)
println(r == Rect(2, 3)) // true
println(r.copy(h = 10)) // Rect(2.0,10.0)
println(Color.values.toList) // List(Red, Green, Blue)Other signature features include given and using (contextual parameters, formerly implicits), extension methods, opaque types and union types such as Int | String.
Error handling with Option, Either and Try
Scala supports try, catch and finally, but idiomatic code models failure with types. Option is either Some(value) or None; Either holds a Left error or a Right result; Try captures an exception as Failure.
import scala.util.{Try, Success, Failure}
def parseInt(s: String): Option[Int] = s.toIntOption
def safeDivide(a: Int, b: Int): Either[String, Int] =
if b == 0 then Left("division by zero") else Right(a / b)
println(parseInt("42")) // Some(42)
println(parseInt("x")) // None
println(safeDivide(10, 2)) // Right(5)
println(safeDivide(1, 0)) // Left(division by zero)
Try("abc".toInt) match
case Success(n) => println(s"got $n")
case Failure(e) => println(s"failed: ${e.getClass.getSimpleName}")
// failed: NumberFormatExceptionA small complete program: grade report
This program parses lines of name,score, skips invalid lines and prints each student's average. It uses case classes, Option, groupBy and string formatting.
case class Score(name: String, value: Int)
def parse(line: String): Option[Score] =
line.split(",").map(_.trim) match
case Array(name, v) => v.toIntOption.map(Score(name, _))
case _ => None
@main def report(): Unit =
val lines = List("ann, 90", "bo, 70", "ann, 80", "broken line", "bo, x")
val scores = lines.flatMap(parse)
val averages = scores.groupBy(_.name).view.mapValues { list =>
list.map(_.value).sum.toDouble / list.size
}.toList.sortBy(_._1)
for (name, avg) <- averages do
println(f"$name%-4s $avg%.1f")
// ann 85.0
// bo 70.0Where to go next
- The Scala 3 Book at docs.scala-lang.org, which covers the language from the basics to contextual abstractions.
- The Scala Toolkit for everyday tasks such as files, JSON and HTTP.
- The Apache Spark documentation if you want to use Scala for data processing.
- Courses linked from scala-lang.org, including the EPFL functional programming courses.
FAQ
Should I learn Scala 2 or Scala 3?
Learn Scala 3 for new projects. Scala 2.13 is still found in many existing codebases, including parts of the Spark ecosystem, and most core concepts are the same.
Is Scala a functional language?
Scala is both object-oriented and functional. Every value is an object, but functions are first-class values, collections are immutable by default and pattern matching is central.
What is the difference between val and var in Scala?
val defines a value that cannot be reassigned, and var defines a variable that can. Idiomatic Scala prefers val together with immutable collections.
What is a case class?
A case class is a class meant for immutable data. The compiler generates structural equality, a readable toString, a copy method and support for pattern matching.
Is Scala still used?
Yes. It remains a core language for Apache Spark, for streaming and data platforms, and for functional backend libraries such as ZIO and Cats Effect.