cv.cm

Coding · Programming languages

Scala tutorial for beginners

7 min read

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
TypeExampleNotes
Int4232-bit signed integer
Long42L64-bit signed integer
Double3.1464-bit floating point
Booleantruetrue 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 x

1 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)
CollectionUse it for
Listimmutable linked list; fast prepend and head/tail access
Vectorimmutable indexed sequence; good general default
Mapkey-value lookup
Setunique elements and membership tests
Arraymutable, 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: NumberFormatException

A 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.0

Where 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.

All coding tutorials