Scala Variables, Types, and Type Inference
Scala's type system is one of its defining strengths. Unlike dynamically typed languages where type errors appear at runtime, Scala catches them at compile time — and unlike Java, it does so without requiring you to write types everywhere. Type inference deduces most types automatically, giving you safety without verbosity.
val vs var
Scala has two kinds of variable declarations:
val name = "Alice" // immutable — cannot be reassigned
var count = 0 // mutable — can be reassignedval is the default choice in idiomatic Scala. Immutability eliminates an entire class of bugs (unexpected mutation) and makes concurrent code easier to reason about. Use var only when mutation is genuinely necessary — it is relatively rare in well-written Scala.
Attempting to reassign a val is a compile error:
val x = 10
x = 20 // error: reassignment to valExplicit Type Annotations
Scala infers types automatically, but you can always annotate explicitly:
val name: String = "Alice"
val age: Int = 30
val pi: Double = 3.14159
val active: Boolean = trueThe syntax is name: Type = value. Explicit annotations are useful for documentation, for cases where inference is ambiguous, and when defining API boundaries.
Type Inference
Scala's compiler infers the type from the right-hand side expression:
val message = "Hello" // inferred: String
val count = 42 // inferred: Int
val ratio = 3.14 // inferred: Double
val items = List(1, 2, 3) // inferred: List[Int]Type inference works through function return types too:
def double(n: Int) = n * 2 // return type inferred as Int
def greet(name: String) = s"Hello, $name" // inferred: StringFor public APIs, it is good practice to annotate return types explicitly — it documents intent and catches errors when the implementation changes.
The Scala Type Hierarchy
Scala's type hierarchy unifies primitives and objects:
Any
├── AnyVal (value types — no heap allocation)
│ ├── Int, Long, Double, Float, Short, Byte
│ ├── Char, Boolean
│ └── Unit
└── AnyRef (reference types — heap allocated, = java.lang.Object)
├── String, List, Array, ...
└── All class instancesAny is the root of all Scala types. Every value is an Any.
AnyVal covers the JVM primitive types. Scala represents them as Java primitives at the bytecode level for efficiency, but treats them as objects in source code.
AnyRef is equivalent to Java's Object. All class instances, collections, and strings are AnyRef.
Unit is the Scala equivalent of void — the type of expressions that produce no meaningful value (like println). Its only value is ().
Nothing is at the bottom of the hierarchy — a subtype of every type. Methods that always throw an exception have return type Nothing.
Null is the type of the null reference — subtype of all AnyRef types. In idiomatic Scala, null is avoided entirely in favour of Option.
Numeric Types
val i: Int = 2_147_483_647 // 32-bit, max Int
val l: Long = 9_223_372_036L // 64-bit, L suffix
val d: Double = 3.14159 // 64-bit float
val f: Float = 3.14f // 32-bit float, f suffix
val b: Byte = 127 // 8-bit
val s: Short = 32767 // 16-bitScala does not perform implicit numeric widening — val x: Double = 42 works (Int literal fits in Double), but you cannot silently pass an Int where a Long is expected in generic contexts.
String Interpolation
Scala provides three string interpolator prefixes:
s-interpolation — evaluates expressions inside ${}:
val name = "Alice"
val age = 30
println(s"$name is $age years old") // Alice is 30 years old
println(s"Next year: ${age + 1}") // Next year: 31f-interpolation — like s but with printf-style format specifiers:
val pi = 3.14159
println(f"Pi is approximately $pi%.2f") // Pi is approximately 3.14raw-interpolation — no escape processing:
println(raw"Line 1
Line 2") // Line 1
Line 2 (no newline)Frequently Asked Questions
Q: When should I use var instead of val?
Use var when you genuinely need mutation — typically for performance-critical local loops, mutable accumulators inside a method, or interoperating with mutable Java libraries. In most application code, val with immutable data structures is the right default. If you find yourself reaching for var often, consider whether you can express the same logic with functional collection operations.
Q: What is the difference between Int and java.lang.Integer in Scala?
Scala's Int compiles to the Java primitive int at the bytecode level — it is not boxed by default. When you use Int in a generic context (e.g., List[Int]), the JVM boxes it to java.lang.Integer automatically. Scala handles this transparently. You rarely need to use java.lang.Integer directly in Scala code.
Q: How does Scala handle null compared to Java?
Scala has null for JVM compatibility, but idiomatic Scala avoids it entirely. Instead of returning null when a value might be absent, Scala uses Option[T] — either Some(value) or None. This forces callers to handle the absence case explicitly, eliminating NullPointerExceptions. The Option type is covered in depth in the Functional Programming phase.
Part of the Scala Mastery Course — Module 3 of 22.
