The questions that come up most often, each with a sample answer you can adapt into your own words. Read them out loud until the explanation feels natural.
Why does Scala favour immutability, and what is the val/var distinction?
Fundamentalsval declares an immutable binding and var a mutable one; idiomatic Scala uses val almost everywhere. Immutability makes code easier to reason about, eliminates a class of concurrency bugs (immutable data is inherently thread-safe), and is essential in distributed frameworks like Spark where data is partitioned across machines. You only reach for var when a local mutable accumulator is genuinely clearer.
What is a case class?
IntermediateA case class is an immutable value class for which the compiler generates a constructor without new, equals/hashCode/toString by structural value, a copy method, and an extractor that enables pattern matching. It is the standard way to model data and algebraic data types in Scala, analogous to Kotlin's data class. Combined with sealed traits it models closed sets of cases for exhaustive matching.
How does pattern matching work and why is it powerful?
IntermediateA match expression tests a value against a series of patterns and runs the first that matches, returning a value. Patterns can destructure case classes, match on type, bind variables, and add guard conditions. With sealed traits the compiler checks exhaustiveness and warns about missing cases, making it a type-safe, expressive replacement for long if/else or visitor chains.
What is the difference between Option, Either, and Try?
IntermediateOption<T> models presence or absence (Some/None), replacing null. Either<L, R> models a value that is one of two types, conventionally Left for an error and Right for success, carrying error detail. Try<T> models a computation that may throw, capturing the exception in a Failure or the value in a Success. All three let you compose with map/flatMap instead of try/catch, making the failure path explicit and type-checked.
What is a for-comprehension?
AdvancedA for-comprehension is syntactic sugar over map, flatMap, and filter that lets you sequence operations over monadic types (Option, Either, Future, collections) in a readable, imperative-looking style. For example, chaining several Option lookups in a for yields Some only if every step succeeds, short-circuiting to None otherwise. It is how Scala composes effectful computations without nesting flatMap calls.
What is a trait and how does it differ from a Java interface?
AdvancedA trait is like an interface that can also hold concrete method implementations and fields, and that can be mixed into a class with extends/with. Multiple traits can be composed (mixin composition), with a deterministic linearisation order resolving conflicts. This makes traits a flexible unit of reuse, more capable than classic Java interfaces and without the diamond problem multiple inheritance causes.
What are implicits (or givens in Scala 3) used for?
AdvancedImplicits let the compiler supply arguments automatically based on type, enabling type-class style polymorphism, extension methods, and context passing (like an execution context for Futures). Scala 3 renames and clarifies this with given instances and using parameters. The classic use is providing an Ordering or a serialisation type class for a type so generic functions work without the caller passing it explicitly.
Why is Scala popular for big data and Spark?
IntermediateSpark is written in Scala and its API is most natural there, with concise functional transformations (map, filter, reduce) over distributed datasets. Scala's immutability and pure functions fit the model where computation is shipped to data across a cluster, and its strong static typing catches errors before a long job runs. The JVM foundation also gives access to the whole Java big-data ecosystem.
What is the difference between map and flatMap?
Intermediatemap transforms each element and keeps the structure: mapping a List of n elements gives a List of n results, and mapping an Option transforms the inside of a Some. flatMap transforms each element into a container and then flattens one level, so a List becomes the concatenation of the produced lists, and chaining Option-returning lookups collapses to a single Option instead of nesting. flatMap is what for-comprehensions desugar to, so being fluent with it is being fluent with idiomatic Scala composition.
val userIds = List(1, 2, 3)
def ordersFor(id: Int): List[String] =
if (id == 2) List("a", "b") else List("c")
val nested = userIds.map(ordersFor) // List(List(...))
val flat = userIds.flatMap(ordersFor) // List("c", "a", "b", "c")
What does lazy val do?
IntermediateA lazy val defers evaluation of its initialiser until the first access, then caches the result for every later read. It is useful for expensive values that may never be needed, for breaking initialisation-order problems between vals in traits and objects, and for setting up resources only on demand. Evaluation is thread-safe, at the cost of synchronisation on first access. Contrast it with a def, which re-evaluates on every call, and a plain val, which evaluates eagerly at construction: choosing among the three is a small but telling design question.
What is currying and partial application?
AdvancedA curried function takes its arguments as separate parameter lists, so supplying only the first list returns a function awaiting the rest. Partial application is the act of fixing some arguments to produce a specialised function. Scala supports this directly in syntax, and it is used to build configured functions (a logger with a fixed prefix, a validator with fixed rules) and to help type inference, since earlier parameter lists inform later ones. It also underlies the pattern of passing the final argument as a block, which reads like a control structure.
def multiply(a: Int)(b: Int): Int = a * b
val double = multiply(2) _ // Int => Int
double(21) // 42
def logAt(prefix: String)(msg: String): String = s"$prefix $msg"
val warn = logAt("[warn]") _
warn("disk nearly full") // "[warn] disk nearly full"
What is variance (covariance and contravariance)?
AdvancedVariance describes how a generic type relates to subtyping of its parameter. List[+A] is covariant: a List[Cat] is a List[Animal], which is safe because lists are immutable and only produce values. Function1[-A, +B] is contravariant in its input: a function that handles any Animal can stand in where a Cat handler is required. Invariant types, like a mutable Array, permit neither direction, which is what keeps writes sound. You rarely author variance annotations day to day, but reading them correctly in library signatures separates candidates who understand the type system from those who pattern-match on error messages.