Scala Interview Questions and Answers
These questions cover Scalaβs functional and object-oriented features, type system depth, and ecosystem β whatβs tested in backend, data engineering, and distributed systems roles using Scala.
Language Fundamentals
Q1. What is the difference between val, var, and def in Scala?
// val β immutable binding (like Java's final variable)val name: String = "Alice" // Type inferred if omitted// name = "Bob" // Compile error!
// var β mutable variablevar count: Int = 0count += 1 // OK
// def β defines a method (evaluated on each call)def greeting: String = { println("Computed!") "Hello"}greeting // Prints "Computed!" each time
// lazy val β evaluated once, on first accesslazy val expensiveData: List[Int] = loadFromDatabase() // Not called until first accessIdiomatic Scala strongly prefers val and immutability. Use var only when mutation is genuinely needed (e.g., local accumulation in tight loops).
Q2. What are case classes in Scala and what do they give you automatically?
case class User(id: Int, name: String, email: String, active: Boolean = true)
// Auto-generated:// 1. apply constructor (no 'new' needed)val alice = User(1, "Alice", "alice@example.com")
// 2. Immutable copy with field changesval updated = alice.copy(email = "alice@company.com", active = false)
// 3. Structural equality (not reference)val alice2 = User(1, "Alice", "alice@example.com")println(alice == alice2) // true
// 4. toStringprintln(alice) // User(1,Alice,alice@example.com,true)
// 5. hashCode (consistent with equals)val userSet = Set(alice, alice2) // Only one element
// 6. unapply β enables pattern matchingalice match { case User(id, name, _, true) => s"Active user $name (id=$id)" case User(_, name, _, false) => s"Inactive user $name"}
// 7. Companion object with apply// User.apply(1, "Alice", ...) is what User(...) callsCase classes are immutable data containers β the idiomatic way to model domain objects in Scala.
Q3. What are Option, Either, and Try in Scala?
Scala uses these types to represent absence, errors, and exceptions without null or exceptions:
// Option β value may or may not existdef findUser(id: Int): Option[User] = if (db.contains(id)) Some(db(id)) else None
val user: Option[User] = findUser(42)
// Pattern matchuser match { case Some(u) => println(s"Found: ${u.name}") case None => println("Not found")}
// Functional operationsval email: Option[String] = user.map(_.email)val name: String = user.map(_.name).getOrElse("Guest")val admin: Option[User] = user.filter(_.active)
// Either β success or failure with error info (Right = success, Left = error)def parseAge(s: String): Either[String, Int] = s.toIntOption.filter(_ > 0) match { case Some(n) => Right(n) case None => Left(s"'$s' is not a valid age") }
parseAge("25") match { case Right(age) => println(s"Age: $age") case Left(err) => println(s"Error: $err")}
// Try β wraps potentially throwing codeimport scala.util.{Try, Success, Failure}
def divide(a: Int, b: Int): Try[Int] = Try(a / b)
divide(10, 2) match { case Success(result) => println(s"Result: $result") case Failure(ex) => println(s"Error: ${ex.getMessage}")}Functional Programming
Q4. What are higher-order functions in Scala? Give examples with collections.
Higher-order functions take functions as parameters or return functions:
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// map β transform each elementval doubled = numbers.map(_ * 2) // List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
// filter β keep elements matching predicateval evens = numbers.filter(_ % 2 == 0) // List(2, 4, 6, 8, 10)
// foldLeft β accumulate from left to rightval sum = numbers.foldLeft(0)(_ + _) // 55val product = numbers.foldLeft(1)(_ * _) // 3628800
// flatMap β map then flattenval words = List("hello world", "foo bar")val allWords = words.flatMap(_.split(" ")) // List(hello, world, foo, bar)
// groupByval byParity = numbers.groupBy(_ % 2 == 0)// Map(true -> List(2,4,6,8,10), false -> List(1,3,5,7,9))
// Composing functionsval processNumbers: List[Int] => Int = numbers => numbers .filter(_ % 2 == 0) // keep evens .map(_ * _ ) // square them .sum // total
// Custom higher-order functiondef applyTwice[A](f: A => A, x: A): A = f(f(x))applyTwice(_ + 10, 5) // 25applyTwice(_ * 2, 3) // 12Q5. Explain Scalaβs pattern matching and its advantages over Javaβs switch.
sealed trait Shapecase class Circle(radius: Double) extends Shapecase class Rectangle(width: Double, height: Double) extends Shapecase class Triangle(base: Double, height: Double) extends Shape
def area(shape: Shape): Double = shape match { case Circle(r) => Math.PI * r * r case Rectangle(w, h) => w * h case Triangle(b, h) => 0.5 * b * h}
// Guardsdef classify(n: Int): String = n match { case 0 => "zero" case n if n < 0 => "negative" case n if n % 2 == 0 => "positive even" case _ => "positive odd"}
// Nested patternscase class Order(user: User, items: List[String], total: Double)
def describeOrder(order: Order): String = order match { case Order(User(_, name, _, true), Nil, _) => s"$name has an empty order" case Order(User(_, name, _, true), items, t) if t > 100 => s"$name: premium order (${items.length} items, $$$t)" case Order(User(_, name, _, false), _, _) => s"Inactive user $name"}
// Tuple pattern matching(getStatus(), getUser()) match { case ("active", Some(user)) => serve(user) case ("maintenance", _) => showMaintenance() case (_, None) => redirect("/login")}Sealed traits + exhaustive pattern matching: the compiler warns if a case is missing.
Q6. What is Scalaβs type system and what are type bounds?
// Upper bound β T must be a subtype of Animalclass Cage[T <: Animal](animal: T) { def feed(): Unit = animal.eat()}
// Lower bound β T must be a supertype of Dogdef addToList[T >: Dog](item: T, list: List[T]): List[T] = item :: list
// Variance// Covariant [+T] β if Dog <: Animal, then List[Dog] <: List[Animal]class ImmutableBox[+T](val value: T) // Works since box is read-only
// Contravariant [-T] β if Dog <: Animal, then Printer[Animal] <: Printer[Dog]trait Printer[-T] { def print(value: T): Unit } // Works since printer is write-only
// Context bounds (type class constraints)def maxOf[T: Ordering](a: T, b: T): T = if (implicitly[Ordering[T]].lt(a, b)) b else a
maxOf(3, 5) // 5 (Int has Ordering[Int] in scope)maxOf("abc", "xyz") // xyz (String has Ordering[String] in scope)Scala Collections
Q7. What is the difference between Scalaβs immutable and mutable collections?
// Default imports are immutableimport scala.collection.immutable._ // Default
val list = List(1, 2, 3)val updated = list :+ 4 // Returns new List(1,2,3,4) β original unchanged
val map = Map("a" -> 1, "b" -> 2)val added = map + ("c" -> 3) // New map with c
// Mutable (opt-in)import scala.collection.mutable
val buffer = mutable.ListBuffer(1, 2, 3)buffer += 4 // Mutates in place
val mutableMap = mutable.HashMap("a" -> 1)mutableMap("b") = 2 // Mutates in place
// Vector β immutable, efficient indexed accessval vec = Vector(1, 2, 3, 4, 5)vec(3) // 4 β O(log32 n) effective O(1)vec.updated(2, 99) // New Vector(1,2,99,4,5)
// Performance characteristics// List β O(1) prepend, O(n) append/random access// Vector β O(1) effective for all operations (32-way branching tree)// Map β immutable HashMap: O(1) average lookup// Set β immutable HashSet: O(1) average containsConcurrency & Futures
Q8. What are Scala Futures and how do you compose them?
import scala.concurrent.{Future, ExecutionContext}import scala.concurrent.ExecutionContext.Implicits.global
// Create a Futureval f1: Future[Int] = Future { Thread.sleep(100) // Runs on a thread pool 42}
// Transform with map (non-blocking)val doubled: Future[Int] = f1.map(_ * 2)
// Sequence-dependent futures β flatMapval result: Future[String] = for { userId <- fetchUserId("alice") // Future[Int] user <- fetchUser(userId) // Future[User] posts <- fetchPosts(user.id) // Future[List[Post]]} yield s"${user.name} has ${posts.length} posts"
// Parallel independent futuresval f2: Future[User] = fetchUser(1)val f3: Future[List[Post]] = fetchPosts(1)
val combined: Future[(User, List[Post])] = for { user <- f2 // Both start immediately and run in parallel posts <- f3 } yield (user, posts)
// Or with zipval zipped = f2.zip(f3)
// Error handlingval safe: Future[Int] = Future(1 / 0) .recover { case _: ArithmeticException => 0 } .recoverWith { case _: NetworkError => fetchFallback() }
// Await (avoid in production β blocks thread)import scala.concurrent.Awaitimport scala.concurrent.duration._val value = Await.result(f1, 5.seconds)Scala 3
Q9. What are the key improvements in Scala 3 (Dotty)?
Scala 3 (released 2021) introduces significant syntax and type system improvements:
// 1. Optional braces (indentation-based syntax)def greet(name: String): String = val greeting = "Hello" s"$greeting, $name!"
// 2. Union types β replace sealed traits for simple casesdef parseInt(s: String): Int | String = s.toIntOption.getOrElse(s"'$s' is not a number")
// 3. Intersection typestype Named = { def name: String }type Aged = { def age: Int }type Person = Named & Aged // Has both name and age
// 4. Enums (algebraic data types)enum Color: case Red, Green, Blue
enum Result[+T]: case Ok(value: T) case Err(message: String)
// 5. Extension methods (replacing implicit classes)extension (s: String) def isPalindrome: Boolean = s == s.reverse def wordCount: Int = s.split("\\s+").length
"racecar".isPalindrome // true
// 6. Given/Using (replacing implicits)given Ordering[String] = Ordering.fromLessThan(_.length < _.length)
def sort[T](list: List[T])(using ord: Ordering[T]): List[T] = list.sorted
// 7. Type lambdas (cleaner syntax)type MapF[K] = [V] =>> Map[K, V] // Type-level functionQ10. What are type classes in Scala and how do they differ from OOP inheritance?
Type classes define behavior for types you donβt own, without modifying them:
// Define the type classtrait Serializable[A]: def serialize(value: A): String def deserialize(s: String): Option[A]
// Instances for existing types (no modification to Int or String needed!)given Serializable[Int] with def serialize(n: Int): String = n.toString def deserialize(s: String): Option[Int] = s.toIntOption
given Serializable[String] with def serialize(s: String): String = s"\"$s\"" def deserialize(s: String): Option[String] = if s.startsWith("\"") && s.endsWith("\"") then Some(s.drop(1).dropRight(1)) else None
// Generic function constrained by type classdef roundTrip[A](value: A)(using s: Serializable[A]): Option[A] = s.deserialize(s.serialize(value))
roundTrip(42) // Some(42)roundTrip("hello") // Some(hello)OOP inheritance requires modifying or extending a class. Type classes add behavior retroactively to any type, including Int and String. This is the foundation of libraries like Cats and Circe.
Q11. What is Akka and when would you use actors vs. Futures in Scala?
Akka is a toolkit for building concurrent, distributed, and fault-tolerant systems. The Actor model treats computation as isolated actors communicating via messages:
import akka.actor.typed._import akka.actor.typed.scaladsl._
// Define messages (protocol)sealed trait CounterCommandcase class Increment(by: Int) extends CounterCommandcase class GetCount(replyTo: ActorRef[Int]) extends CounterCommand
// Define actor behaviordef counterBehavior(count: Int = 0): Behavior[CounterCommand] = Behaviors.receive { (ctx, message) => message match { case Increment(n) => counterBehavior(count + n) // Return new behavior with updated state case GetCount(replyTo) => replyTo ! count Behaviors.same } }Use Futures for: one-off async computations, parallel data fetching, request/response HTTP calls.
Use Actors for: stateful systems that receive many messages, distributed systems across nodes, location transparency, supervision hierarchies (restart crashed actors), streams of events.
Akka Streams (built on actors) handles backpressure automatically for data pipelines.