Syntax and immutability
Values, inferred types, methods versus functions, and why Scala makes immutability the default you have to opt out of.
Values and types
Scala runs on the JVM and everything is an object, including numbers. val declares a binding that cannot be reassigned and var declares one that can. The type is usually inferred, but it is still checked by the compiler.
// scalac Main.scala && scala Main
object Main {
def main(args: Array[String]): Unit =
println("Hello, world")
}
// most files are small enough to run as a script: scala script.scala
val name = "Ada" // inferred String, cannot be reassigned
val count: Int = 3 // explicit type
var total = 0 // reassignable
total += count
// everything is an expression: if, match and blocks all return values
val label = if (count > 2) "many" else "few"
val doubled = { val base = count * 2; base + 1 }
// string interpolation, with an optional format
val message = s"$name has $count items"
val money = f"${1.5}%.2f" // braces are needed for expressions and formats| Declaration | Meaning |
|---|---|
val x = 1 | An immutable binding; the reference can never be reassigned |
var x = 1 | A mutable binding, to be used sparingly |
def f = ... | A method: evaluated again on every call |
lazy val x = ... | Evaluated once, on first use, thread-safely |
type Id = Long | A type alias; no new type is created |
final val Pi = 3.14 | Not overridable, and inlined by the compiler for literals |
Methods and functions
// a method: it belongs to an object
def add(a: Int, b: Int): Int = a + b
// a function value: an object you can pass around and store
val addFn: (Int, Int) => Int = (a, b) => a + b
// the underscore form for a short lambda
val double: Int => Int = _ * 2
// default and named arguments
def greet(name: String, greeting: String = "Hello"): String = s"$greeting, $name"
greet("Ada")
greet(name = "Grace", greeting = "Hi")
// a block body when the logic needs more than one line
def classify(n: Int): String = {
if (n < 0) "negative"
else if (n == 0) "zero"
else "positive"
} // the last expression is the result; no return keyword- A method is not a value:
addmust be converted, asadd _oradd(_, _), before it can be passed as an argument. Unitis the type of an expression that produces nothing useful, such asprintln.- Give public methods an explicit return type: it makes compiler errors point at the mistake instead of at the inferred type.
returnexists but is discouraged. Inside a lambda it throws rather than returning from the enclosing method.
Immutability by default
// prefer building a new value to changing an existing one
val xs = List(1, 2, 3)
val ys = xs :+ 4 // a new list; xs is unchanged
// mutable collections exist, but opt in deliberately and freeze before sharing
val buf = scala.collection.mutable.ArrayBuffer.empty[Int]
buf += 1
val snapshot = buf.toList
// copy a value with one field changed
case class User(name: String, age: Int)
val ada = User("Ada", 36)
val older = ada.copy(age = 37)💡
Immutability is what makes a value safe to share between threads and safe to cache without checking anything. Pushing mutation to the edges of a program — parse into immutable data, transform it, then write once — removes most concurrency bugs before they can occur.
FAQ
val or var?
val unless there is a concrete reason to reassign. Most loops are expressed as collection operations, so var is rarely needed outside a tight performance-sensitive loop.What is an object?
A singleton: exactly one instance, created lazily on first access.
object Main also serves as a program entry point, and a companion object holds the Java-style static members of a class.Related
Case classes and pattern matching Collections and functional style
Last refreshed 2026-09-18.