50 Scala Interview Questions and Answers (2026)

TT

50 Scala Interview Questions and Answers (2026)

This module covers the most commonly asked Scala interview questions — from language basics to advanced type system, Spark, and Akka topics. Use this as your final review before any Scala engineering interview.


Language Fundamentals (Questions 1–15)

1. What is the difference between val and var in Scala? val declares an immutable reference — it can't be reassigned after initialization (like Java's final). var declares a mutable reference that can be reassigned. Scala prefers val for functional programming; use var only when mutation is genuinely needed.

2. What is type inference in Scala? The Scala compiler deduces the type of an expression automatically, so you don't always need to specify it. val x = 42 — the compiler infers x: Int. Type inference reduces verbosity while keeping full static typing.

3. What is the difference between == and eq in Scala? == calls the equals method and compares values (structural equality). eq compares object references (identity, like Java's ==). For case classes, == compares all fields. Avoid eq unless you specifically need reference equality.

4. What is a companion object? A companion object is a singleton object with the same name as a class, defined in the same file. It has access to the class's private members, and the class has access to the object's private members. Companion objects implement factory methods (apply) and class-level functionality that would be static in Java.

5. Explain the apply method. apply is a special method that lets you call an object like a function. List(1, 2, 3) calls List.apply(1, 2, 3). Defining apply in a companion object is the standard factory pattern for case classes and custom types.

6. What is a case class? What does it provide automatically? A case class is a class with auto-generated: equals/hashCode (structural), toString, copy, apply (no new needed), unapply (pattern matching). Case classes are immutable by default and are the primary tool for domain modeling in Scala.

7. What is a sealed trait? Why use it? A sealed trait restricts subtypes to those defined in the same file. This enables exhaustive pattern matching — the compiler warns (or errors) if you miss a case in a match expression. Sealed traits are the basis of algebraic data types (ADTs) in Scala.

8. What is the difference between a trait and an abstract class in Scala? A class can extend only one abstract class but can mix in multiple traits. Abstract classes can have constructor parameters; traits cannot (except in Scala 3). Use traits for mixins and capability composition. Use abstract classes for "is-a" hierarchies with constructor parameters or when interoperating with Java.

9. What is Option and why is it preferred over null? Option[A] is a container that's either Some(value) or None. It makes the possibility of absence explicit in the type — callers must handle both cases. null is implicit and leads to NullPointerExceptions at runtime. Option integrates with map, flatMap, and for-comprehensions for safe chaining.

10. What is pattern matching in Scala? Pattern matching is Scala's match/case expression. It's more powerful than switch: it can match types, destructure case classes, match on collections, add guards, and use custom extractors. It returns a value (it's an expression, not a statement).

11. What is a for-comprehension? How does it desugar? A for-comprehension with yield is syntactic sugar for flatMap, map, and withFilter. Each <- generator (except the last) desugars to flatMap. The last generator desugars to map. Guards desugar to withFilter. This means for works with any type that has these methods.

12. What is the difference between map and flatMap? map applies a function A => B to each element, returning the same container type with B elements. flatMap applies a function A => F[B] and flattens the result — so you don't get nested containers. flatMap is what makes Option, Future, and List composable.

13. What are higher-order functions? Functions that take other functions as parameters or return functions. Examples: map, filter, flatMap, foldLeft. They're central to functional programming in Scala and enable code reuse without inheritance.

14. What is currying in Scala? Currying transforms a function of multiple arguments into a chain of single-argument functions. def add(x: Int)(y: Int): Int = x + y creates a curried function. add(1) returns a function Int => Int. Currying enables partial application and is used in Scala for implicit parameter lists.

15. What is tail recursion and the @tailrec annotation? A tail-recursive function's recursive call is the last operation in the function. The Scala compiler optimizes tail recursion to a loop, avoiding stack overflow. Annotate with @tailrec to get a compile error if the function isn't actually tail-recursive.


Type System (Questions 16–22)

16. What is variance in Scala generics? Variance describes how generic types relate when their type parameter changes. Covariant (+T): if Dog <: Animal, then Box[Dog] <: Box[Animal]. Contravariant (-T): the relationship is reversed. Invariant (default): no relationship. Scala enforces variance through position rules at compile time.

17. What is an upper type bound? [A <: Animal] restricts A to be Animal or a subtype. Used when a method needs to call Animal methods on its parameter. Lower bounds (>: T) restrict to supertypes and are used with covariant containers.

18. What are implicits (Scala 2) / given/using (Scala 3)? Implicit values are automatically injected by the compiler when a method has an implicit parameter. The compiler searches the local scope, imports, and companion objects. In Scala 3, given declares the value and using marks the parameter. This powers type classes, extension methods, and dependency injection.

19. What is a type class? A type class defines an interface (trait) that can be implemented for any type after the fact — without modifying that type. Example: Ordering[A], Show[A], JsonEncoder[A]. The implementation is provided via implicit/given instances. This is called ad-hoc polymorphism and is more flexible than inheritance.

20. What is the difference between structural typing and nominal typing? Scala uses nominal typing: types are compatible based on their declared names/hierarchy. Structural typing ({ def foo(): Unit }) means any type with the required method is compatible. Scala supports structural types but they're rarely used due to reflection overhead.

21. What is Nothing in Scala? Nothing is a subtype of every type — it's at the bottom of Scala's type hierarchy. A function returning Nothing never returns (it throws or loops forever). Nil has type List[Nothing], which is why it's compatible with List[Int], List[String], etc. (because List is covariant).

22. What is Any, AnyVal, and AnyRef? Any is the top of Scala's type hierarchy. AnyVal is the parent of value types (Int, Double, Boolean, etc.). AnyRef is the parent of reference types (equivalent to Java's Object). Every Scala class extends Any — either via AnyVal or AnyRef.


Collections and Functional Programming (Questions 23–30)

23. What is the difference between List and Vector? List is a linked list — O(1) prepend, O(n) append and random access. Vector is a wide tree — effectively O(1) for random access, append, and prepend. Use List for recursive algorithms and head/tail patterns. Use Vector as the default sequence when you need random access.

24. What is foldLeft vs reduce? foldLeft(initial)(f) applies f to the accumulator and each element, starting from initial. reduce(f) does the same but uses the first element as the initial accumulator — it throws on empty collections. Prefer foldLeft for safety and flexibility.

25. What is a lazy collection in Scala? A lazy collection (LazyList, Stream) computes elements on demand rather than all at once. This enables infinite sequences and avoids computing elements you don't use. LazyList.from(1) is an infinite sequence of integers — but only the elements you access are computed.

26. What is the difference between groupBy and partition? groupBy(f) splits a collection into a Map[K, List[A]] keyed by the result of f. partition(p) splits into a tuple (List[A], List[A]) — matching and non-matching. Use groupBy for multiple categories, partition for two.

27. How does Either work with for-comprehensions? Either[L, R] is right-biased — map and flatMap operate on the Right value. In a for-comprehension, the chain short-circuits at the first Left. This makes sequential validation pipelines clean and readable: each step returns Right(value) on success or Left(error) on failure.

28. What is Try and when do you use it? Try[A] wraps code that might throw exceptions. Try(expr) returns Success(value) or Failure(exception). Use it when wrapping Java library code or any code that throws. For your own error handling, prefer Either with a typed error type.

29. What is the difference between view and LazyList? view creates a lazy wrapper around an existing collection — transformations are deferred until you iterate. LazyList is a fully lazy, potentially infinite linked list where elements are computed and memoized on first access. Use view to avoid intermediate collections in chains. Use LazyList for infinite or recursively-defined sequences.

30. Explain flatMap on Option. flatMap on Option applies a function A => Option[B] to the contained value. If the Option is None, it returns None. If it's Some(a), it applies the function and returns the result (which is itself an Option[B]). This is the key operation for chaining optional lookups without nested match expressions.


Apache Spark (Questions 31–38)

31. What is the difference between a transformation and an action in Spark? Transformations (map, filter, select) are lazy — they build a computation plan (DAG) without executing. Actions (count, collect, write) trigger execution. Spark optimizes the full DAG before running it, which is why laziness enables significant performance improvements.

32. What is the difference between RDD, DataFrame, and Dataset? RDD is the low-level API — unstructured, type-safe but no optimization. DataFrame is a structured API — Dataset[Row] with Spark's Catalyst optimizer but no compile-time type safety. Dataset[T] combines type safety with Catalyst optimization. Use DataFrame/Dataset for most work; use RDD only for unstructured data or low-level control.

33. What is reduceByKey and why is it preferred over groupByKey? Both group values by key, but reduceByKey performs partial aggregation on each partition before shuffling — minimizing data transfer. groupByKey shuffles all raw values first, which can cause OOM errors and slow performance on high-cardinality keys.

34. What is caching/persistence in Spark? rdd.cache() or rdd.persist(storageLevel) stores the RDD/DataFrame in memory (or disk) so it isn't recomputed on subsequent actions. Use caching when an RDD is used multiple times and is expensive to compute. Call unpersist() when done to free memory.

35. What is a shuffle in Spark? A shuffle redistributes data across partitions — typically triggered by groupByKey, reduceByKey, join, and repartition. Shuffles are expensive because they require serialization, network transfer, and disk I/O. Minimizing shuffles is key to Spark performance tuning.

36. What is Spark Structured Streaming? Spark Structured Streaming extends the DataFrame API to continuous data streams. It treats the stream as an unbounded table and runs micro-batch queries on it. It supports event-time processing, watermarks, windowing, and exactly-once fault tolerance via checkpointing.

37. What is a watermark in Structured Streaming? A watermark tells Spark how long to wait for late-arriving data. withWatermark("eventTime", "10 minutes") means Spark keeps state for windows up to 10 minutes behind the latest event time. Events older than the watermark are dropped. This bounds the state size and allows Spark to finalize window results.

38. What is the Catalyst optimizer? Catalyst is Spark's query optimizer for DataFrames/Datasets. It parses SQL/DataFrame code into a logical plan, applies optimization rules (predicate pushdown, column pruning, constant folding), generates physical plans, and selects the best one. This is why DataFrames are often faster than RDDs even though RDD code looks simpler.


Akka and Concurrency (Questions 39–44)

39. What is the Actor Model? The Actor Model is a concurrency paradigm where actors are independent units that communicate exclusively by message passing. Each actor has private state, processes one message at a time, and can create children. This eliminates shared-state concurrency bugs without locks.

40. What is the difference between ! and ? in Akka? ! (tell) sends a message fire-and-forget — no reply is expected. ? (ask) sends a message and returns a Future with the reply. Use ! for one-way messages and ? when you need a response. Overusing ? can cause performance issues — prefer message-driven pipelines.

41. What is Akka supervision? Every actor has a supervisor (its parent) that decides what to do when it fails: Resume (continue), Restart (recreate), Stop (terminate), or Escalate (pass to parent's supervisor). The "let it crash" philosophy — let actors fail and use supervision to recover — makes Akka systems resilient.

42. What is the difference between Akka Classic and Akka Typed? Akka Classic uses untyped Any messages — the compiler can't prevent sending wrong message types. Akka Typed makes the message type explicit in Behavior[Command] — only Command messages are accepted, enforced at compile time. Typed also removes the sender() method, which was a common source of bugs.

43. What is a Future in Scala? Future[A] represents an asynchronous computation that will eventually produce an A (or fail). It starts immediately on an ExecutionContext thread pool. Futures compose with map, flatMap, and for-comprehensions. Future.sequence runs multiple Futures in parallel.

44. How do you run multiple Futures in parallel? Start all Futures before combining them. val f1 = Future {...}; val f2 = Future {...}; Future.sequence(List(f1, f2)). Starting Futures inside a for-comprehension makes them sequential. The key is that Future {...} starts immediately when evaluated.


Career and Ecosystem (Questions 45–50)

45. What industries hire Scala developers? Financial services (banks, trading firms, fintech) heavily use Scala for high-performance systems. Big data companies use Scala for Spark jobs. Tech companies like Twitter, LinkedIn, and Databricks have large Scala codebases. Streaming and distributed systems companies use Scala with Akka.

46. What is SBT and how does it compare to Maven/Gradle? SBT (Scala Build Tool) is the standard build tool for Scala. It uses Scala DSL for configuration, supports incremental compilation (much faster rebuilds), and integrates tightly with the Scala ecosystem. Maven and Gradle also support Scala but are less idiomatic. For new Scala projects, SBT is the standard choice.

47. What is the difference between Scala 2 and Scala 3? Scala 3 (Dotty) introduces a redesigned type system, cleaner syntax (optional braces, given/using instead of implicits), union and intersection types, opaque types, and better metaprogramming. Scala 3 is backward compatible with most Scala 2 code. The ecosystem is still largely on Scala 2.13, but Scala 3 adoption is growing rapidly.

48. What are some popular Scala frameworks and libraries? Akka (actors/streams), Play Framework (web), http4s and Akka HTTP (HTTP), Cats and Scalaz (functional programming), Slick and Doobie (database), Circe and uPickle (JSON), ScalaTest and specs2 (testing), Apache Spark (big data), ZIO (effect system).

49. What is ZIO and how does it relate to Scala Futures? ZIO (Zero Dependency IO) is a functional effect system for Scala. It provides a composable, type-safe way to handle effects (IO, errors, concurrency, resources). Unlike Futures, ZIO is lazy (doesn't start immediately), carries error and environment types, and provides structured concurrency. ZIO is gaining significant adoption in the Scala ecosystem as an alternative to Akka and bare Futures.

50. What should a Scala developer know for senior-level roles? Senior Scala developers are expected to understand: the full type system (variance, type bounds, implicits), functional programming (type classes, monads, effect systems), the JVM (memory model, GC tuning, profiling), Apache Spark internals and optimization, Akka or ZIO for concurrency, clean architecture principles, and performance profiling tools (async-profiler, JMC). Domain knowledge in the hiring company's area (fintech, data engineering) matters significantly.


Frequently Asked Questions

Q: How long does it take to become proficient in Scala? With prior Java or functional programming experience, expect 3–6 months to become productive in Scala. Mastering the type system and functional programming idioms takes 1–2 years of regular practice. The steepest part of the learning curve is implicits and the type system — once those click, everything else follows quickly.

Q: Is Scala still worth learning in 2026? Yes — Scala remains the dominant language for Apache Spark development, which powers big data engineering at most major companies. Financial services companies pay premium salaries for Scala engineers. While the overall number of Scala jobs is smaller than Java or Python, Scala roles are consistently among the highest-paying in software engineering.

Q: What's the best way to practice for a Scala interview? Work through the exercises on Exercism.io (Scala track) and Coursera's Functional Programming in Scala specialization. Practice implementing common data structures and algorithms in a functional style. Study the Spark source code on GitHub. Build a small project — a REST API with http4s, a data pipeline with Spark, or an actor system with Akka. Hands-on coding solidifies the concepts far better than reading alone.


Part of Scala Mastery Course — Module 22 of 22.