Interviews

🎯 Interview Guides 12 guides · updated 2026

Real questions and structured answers for data, cloud, and AI engineering interviews β€” including the system-design and GenAI rounds now showing up everywhere.

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 variable
var count: Int = 0
count += 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 access
lazy val expensiveData: List[Int] = loadFromDatabase() // Not called until first access

Idiomatic 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 changes
val 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. toString
println(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 matching
alice 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(...) calls

Case 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 exist
def findUser(id: Int): Option[User] =
if (db.contains(id)) Some(db(id)) else None
val user: Option[User] = findUser(42)
// Pattern match
user match {
case Some(u) => println(s"Found: ${u.name}")
case None => println("Not found")
}
// Functional operations
val 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 code
import 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 element
val doubled = numbers.map(_ * 2) // List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
// filter β€” keep elements matching predicate
val evens = numbers.filter(_ % 2 == 0) // List(2, 4, 6, 8, 10)
// foldLeft β€” accumulate from left to right
val sum = numbers.foldLeft(0)(_ + _) // 55
val product = numbers.foldLeft(1)(_ * _) // 3628800
// flatMap β€” map then flatten
val words = List("hello world", "foo bar")
val allWords = words.flatMap(_.split(" ")) // List(hello, world, foo, bar)
// groupBy
val byParity = numbers.groupBy(_ % 2 == 0)
// Map(true -> List(2,4,6,8,10), false -> List(1,3,5,7,9))
// Composing functions
val processNumbers: List[Int] => Int = numbers =>
numbers
.filter(_ % 2 == 0) // keep evens
.map(_ * _ ) // square them
.sum // total
// Custom higher-order function
def applyTwice[A](f: A => A, x: A): A = f(f(x))
applyTwice(_ + 10, 5) // 25
applyTwice(_ * 2, 3) // 12

Q5. Explain Scala’s pattern matching and its advantages over Java’s switch.

sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case 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
}
// Guards
def 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 patterns
case 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 Animal
class Cage[T <: Animal](animal: T) {
def feed(): Unit = animal.eat()
}
// Lower bound β€” T must be a supertype of Dog
def 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 immutable
import 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 access
val 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 contains

Concurrency & 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 Future
val 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 β€” flatMap
val 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 futures
val 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 zip
val zipped = f2.zip(f3)
// Error handling
val safe: Future[Int] = Future(1 / 0)
.recover { case _: ArithmeticException => 0 }
.recoverWith { case _: NetworkError => fetchFallback() }
// Await (avoid in production β€” blocks thread)
import scala.concurrent.Await
import 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 cases
def parseInt(s: String): Int | String =
s.toIntOption.getOrElse(s"'$s' is not a number")
// 3. Intersection types
type 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 function

Q10. 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 class
trait 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 class
def 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 CounterCommand
case class Increment(by: Int) extends CounterCommand
case class GetCount(replyTo: ActorRef[Int]) extends CounterCommand
// Define actor behavior
def 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.