Technology  /  Scala

🔴 Scala 21 guides · updated 2026

The JVM's hybrid OOP/functional language — immutability, pattern matching, and the type system that powers Spark and Akka, from first principles to production.

Variables and Basic Types in Scala: val vs var and Expression-Oriented Syntax

val and var look like a cosmetic naming choice until you internalize what each one actually commits you to — and once you do, a second, bigger idea falls into place: in Scala, if, for, match, and even a bare { } block all evaluate to a value, the same way 2 + 2 does. This single fact — that control structures are expressions, not statements — reshapes how you write nearly every function, and it’s the piece most newcomers coming from Java or Python have to unlearn their old habits around first.


val vs var — Immutability by Default

val name = "Alice"
name = "Bob" // Error: reassignment to val
var counter = 0
counter = counter + 1 // fine — var allows reassignment

val binds a name to a value permanently — once assigned, that binding can never point to a different value. var allows reassignment, exactly like a normal variable in most other languages. Scala’s own style guidance, and the overwhelming convention in idiomatic Scala code, is to default to val and reach for var only when mutation is genuinely the clearest way to express something — a running total inside a tight, self-contained loop, for instance.

This isn’t stylistic pickiness — it’s a practical safety property. A val can never be reassigned somewhere unexpected deep in a large function, which means reasoning about what a val’s value is at any point in the code is simpler: it’s always whatever it was assigned, full stop. A var reintroduces the class of bug immutable-by-default languages are specifically designed to eliminate — a value changing out from under code that assumed it wouldn’t.

// val doesn't mean the referenced object can't change internally — only the binding is fixed
val numbers = scala.collection.mutable.ArrayBuffer(1, 2, 3)
numbers += 4 // fine — the ArrayBuffer itself is mutable, even though `numbers` is a val
// numbers = ArrayBuffer(5, 6) // Error — reassigning the binding itself is still forbidden

This distinction — val fixes the binding, not necessarily the contents — matters the moment you use a mutable collection type, and it’s exactly analogous to final in Java or const in JavaScript, both of which have the identical caveat.


Basic Types

val age: Int = 30
val price: Double = 19.99
val initial: Char = 'A'
val isActive: Boolean = true
val bigNumber: Long = 9_000_000_000L
val precise: Float = 3.14f
val nothing: Unit = ()
TypeSize / notes
Int32-bit signed integer — the default for whole numbers
Long64-bit signed integer — needs an L suffix on literals
Double64-bit floating point — the default for decimals
Float32-bit floating point — needs an f suffix
Booleantrue / false
CharA single UTF-16 character
StringText — technically java.lang.String under the hood, since Scala runs on the JVM
UnitThe type of “no meaningful value” — Scala’s equivalent of void, but genuinely a type with one value, ()

Numeric literals can use underscores as visual separators (9_000_000_000L) — purely for readability, with zero effect on the actual value, exactly mirroring the same feature in Java and JavaScript.

Unit — Scala’s honest version of “no return value”

def printMessage(msg: String): Unit = {
println(msg)
}
val result: Unit = printMessage("hi") // result is genuinely a value: ()

Unlike Java’s void, which is a special non-type carve-out meaning “there is nothing here,” Scala’s Unit is a real type with exactly one possible value, written (). This consistency (every expression, even one with no meaningful result, still produces some value of some type) is what lets if/for/blocks all be expressions uniformly — there’s no special “these don’t return anything” exception anywhere in the language.


Type Inference — Explicit Types Are Usually Optional

val x = 42 // inferred: Int
val y = 3.14 // inferred: Double
val name = "Scala" // inferred: String
val items = List(1, 2, 3) // inferred: List[Int]
def double(n: Int) = n * 2 // parameter must be annotated; return type (Int) is inferred

Scala’s compiler infers types from context aggressively enough that explicit annotations on local variables are the exception rather than the rule in idiomatic code — the opposite convention from Java, where every variable declaration states its type. Function parameters are the one place annotations remain mandatory (there’s no value at the definition site to infer from), and function return types are technically optional but commonly written explicitly on public methods anyway, for the same “acts as a contract, catches accidental signature drift” reason covered in typed-language guides generally.

// Recursive functions are the one case where return type annotation is NOT optional — required
def factorial(n: Int): Int =
if (n <= 1) 1 else n * factorial(n - 1)

The compiler cannot infer a recursive function’s return type from its own body (it would need to already know the type to type-check the recursive call, a chicken-and-egg problem) — this is a real, non-stylistic case where the annotation is mandatory, not just good practice.


Control Structures Are Expressions, Not Statements

// This is the idea that reshapes everything else in the language
val status = if (age >= 18) "adult" else "minor"

In Java, if is a statement — it controls which block of code runs, but the if construct itself has no value; you have to assign inside each branch separately. In Scala, if/else is an expression: it evaluates to whichever branch’s value actually ran, and that value can be assigned directly, exactly as shown above.

yes

no

if (condition) valueA else valueB

condition true?

expression evaluates to valueA

expression evaluates to valueB

assigned directly to a val

// A block { } is also an expression — it evaluates to its LAST line
val discount = {
val base = 0.1
val bonus = 0.05
base + bonus // this is the block's value — the whole block evaluates to 0.15
}

A { } block’s value is whatever its final line evaluates to — every earlier line is executed for its side effects or to compute intermediate vals, but only the last expression’s value “escapes” the block. This is why Scala functions are so often written as a single expression rather than a sequence of statements with an explicit return — the function body’s last line is the return value, with no return keyword needed (and return is in fact rarely used in idiomatic Scala at all).

// for loops CAN be expressions too, using "yield" — this is a preview of for-comprehensions
val doubled = for (n <- List(1, 2, 3)) yield n * 2
// doubled: List[Int] = List(2, 4, 6)
// Without yield, a for loop is used purely for its side effect and produces Unit
for (n <- List(1, 2, 3)) println(n)

for...yield transforms each element and collects the results into a new collection — this is the gateway into for-comprehensions, covered in full depth in their own dedicated guide later in this series, since they’re genuinely one of Scala’s most distinctive and useful features.

// match is an expression too — arguably the most powerful one in the language
val description = age match {
case a if a < 13 => "child"
case a if a < 20 => "teenager"
case _ => "adult"
}

match (covered in full in its own guide) is Scala’s pattern matching construct, and like if and blocks, it evaluates to a value based on whichever case matched — this uniform “everything produces a value” design is what makes Scala code read as a series of composed expressions rather than a sequence of imperative steps mutating shared state.


String Interpolation

val name = "Alice"
val age = 30
println(s"$name is $age years old") // s-strings: simple variable interpolation
println(s"Next year, $name will be ${age + 1}") // ${} needed for expressions, not just bare variables
println(f"Pi is approximately ${math.Pi}%.2f") // f-strings: printf-style formatting
println(raw"Line1\nLine2") // raw strings: no escape sequence processing at all

The s"..." prefix enables interpolation — $name substitutes a simple identifier directly, while any expression more complex than a bare variable name (age + 1) needs curly braces (${age + 1}) to disambiguate where the expression ends. The f"..." interpolator adds printf-style format specifiers (%.2f for two decimal places), and raw"..." disables escape sequence processing entirely — useful for regex patterns or Windows-style file paths where you don’t want \n interpreted as a newline.


Tuples — Quick, Anonymous Groupings

val point = (3, 4) // Tuple2[Int, Int]
val person = ("Alice", 30, true) // Tuple3[String, Int, Boolean]
println(point._1) // 3 — 1-indexed, not 0-indexed
println(point._2) // 4
val (x, y) = point // destructuring — x = 3, y = 4

Tuples group a fixed, small number of values of possibly different types without needing a named class — useful for quick, local groupings (like returning two values from a function) where defining a full case class would be unnecessary ceremony for something used in exactly one place. Note the _1, _2 accessors are 1-indexed, a genuine Scala quirk that trips up people coming from almost any other language’s 0-indexed conventions.


Frequently Asked Questions

Should I ever use var? Sparingly — the idiomatic default is val, with var reserved for cases where mutation is genuinely the clearest local implementation detail (an accumulator inside a tight, self-contained loop that isn’t shared or observed from outside). Most Scala style guides and most experienced Scala codebases treat a var as something worth a second look during code review, not a forbidden construct.

Why does Scala make if an expression instead of a statement? Because it composes better — an expression can be nested, assigned, passed as an argument, or returned directly, while a statement can only be executed for its side effect. This uniformity is a deliberate design choice supporting Scala’s functional programming half, where composing small expressions into larger ones is the primary way programs are built.

Do I need a return keyword? Rarely — since a function’s body is itself an expression whose last line is automatically its result, explicit return is mostly unnecessary and, in fact, somewhat discouraged in idiomatic Scala, partly because return inside a nested function/lambda has surprising non-local-exit semantics that differ from what most developers expect coming from other languages.

What’s the actual difference between Int and Integer in Scala? There isn’t a separate Integer type the way Java has both int (primitive) and Integer (boxed object) — Scala’s Int unifies both roles at the language level, with the compiler handling boxing/unboxing to the JVM’s actual primitive int transparently where possible for performance.

Why are tuple accessors 1-indexed (_1, _2) when everything else in Scala is 0-indexed? This is a genuine historical inconsistency in the language, inherited from tuple conventions in some earlier functional languages. It’s worth simply memorizing as an exception rather than expecting it to follow the 0-indexed convention every collection type in Scala otherwise uses.


What’s Next

You now understand Scala’s core values-and-types vocabulary, and — more importantly — the expression-oriented mindset that makes the rest of the language click. Next: Functions in Scala, where this expression-based thinking extends into higher-order functions, currying, and treating functions themselves as ordinary values you can pass around.