Skip to main content

Scala engineer domain knowledge interview questions

9 questions on domain knowledge for scala engineer candidates. Expect prompts such as “Immutability by default in Scala and its performance implications” and “Why Parquet is the default format for Spark analytics”, each with a worked answer outline and the follow-ups interviewers push on.

Questions

Showing 1 to 9 of 9 domain knowledge questions.

As asked

Scala encourages immutability by default. What are the performance implications of using immutable data structures, and when is a mutable local variable (var) or a mutable collection acceptable?

Sample answer outline

Immutable data structures create copies on modification, which costs allocation and GC. For large collections modified in a tight loop, this is a real cost. Acceptable uses of mutable: a local var inside a pure function that is not visible outside (like a loop accumulator), or a mutable Builder (StringBuilder, ArrayBuffer) used to build a result that is then returned as immutable. The key is that mutation is not observable by callers. Persistent data structures like Vector use structural sharing to reduce copying costs.

Expect these follow-ups

  • What is structural sharing in a persistent data structure and how does Vector[A] implement it?
  • When would you reach for a scala.collection.mutable.HashMap over an immutable Map in a hot path?
immutabilityscalaperformancefunctional-programmingdomain-knowledge

As asked

Why is Parquet preferred over CSV or JSON for Spark analytical workloads? Explain the columnar storage model and how Catalyst uses Parquet metadata to avoid reading unnecessary data.

Sample answer outline

Parquet is columnar: data for each column is stored contiguously, so reading only selected columns avoids reading irrelevant data. Dictionary encoding and RLE compression work column-by-column and achieve much better ratios than row-based formats. Catalyst uses Parquet row group statistics (min/max per column per row group) for predicate pushdown: it skips entire row groups without reading them. Bloom filters (Parquet 2.0) add another layer. A strong candidate also mentions that Parquet preserves schema and supports nested types, unlike CSV.

Expect these follow-ups

  • What is a row group in Parquet and what is a good row group size for S3-based Spark jobs?
  • How does Delta Lake extend Parquet to support ACID transactions and time travel?
parquetsparkcolumnar-storagedomain-knowledgebig-data

As asked

Describe the Cats Effect 3 runtime. What is a fiber, how does the work-stealing scheduler work, and why can millions of fibers run on a small fixed-size thread pool?

Sample answer outline

Fibers are lightweight green threads managed by the CE3 runtime. The work-stealing scheduler maintains per-thread queues; idle threads steal from busy threads to balance load. Fibers suspend at IO boundaries (flatMap, async) and resume on any available thread. Because fibers do not hold OS threads while suspended, you can have millions of logical concurrent tasks with a thread pool of 8 threads. The JVM thread is only occupied during actual computation. This is fundamentally different from Future, where each task holds a thread from the global pool while waiting.

Expect these follow-ups

  • What is the difference between IOFiber and a Java virtual thread (Project Loom), and are they compatible?
  • How do you set the number of threads in the Cats Effect work-stealing pool and what is the default?
cats-effectfibersruntimeconcurrencydomain-knowledge

As asked

When would you choose ZIO over Cats Effect for a new Scala service, and vice versa? What are the meaningful differences in their models and ecosystems?

Sample answer outline

ZIO has a built-in environment (R) and typed error (E) channel, making dependency injection and error types explicit in the type signature. CE3 uses tagless final and the MTL approach for polymorphic code. ZIO has a richer built-in library (ZIO Stream, ZIO HTTP, STM). CE3 is more modular, often paired with http4s, fs2, and doobie. ZIO's error channel can be verbose for simple cases. CE3's typeclass hierarchy (Monad, MonadError) interoperates with a wider Scala ecosystem (Cats itself). The choice often comes down to team familiarity and existing ecosystem.

Expect these follow-ups

  • Can you use ZIO and Cats Effect in the same project? What are the interop considerations?
  • How does error handling in ZIO[R, E, A] compare to IO[Either[E, A]] in Cats Effect?
ziocats-effectdomain-knowledgescalafunctional-programming

As asked

What are the fundamental differences between Akka Typed and classic (untyped) Akka actors? Why did the Akka team redesign the actor model, and what guarantees does Akka Typed provide that classic does not?

Sample answer outline

Classic actors use Any for messages and receive partial functions, so the compiler cannot check that a message is valid for an actor. Akka Typed encodes the message type in ActorRef[T], so only T can be sent to that actor. This eliminates a whole class of runtime MatchError and wrong-message bugs. Supervision also moved from a lifecycle callback to a Behavior return, making it composable. The trade-off is that typed actors are more verbose for protocols that change over time (Behaviors.receive versus become).

Expect these follow-ups

  • How do you model an actor that handles two different protocols at different lifecycle stages in Akka Typed?
  • What is the Behaviors.setup and Behaviors.withTimers pattern in Akka Typed?
akka-typedactorsdomain-knowledgescalatype-safety

As asked

Trace the journey of a Spark SQL query from the SQL string to executable RDD operations. What are the four phases of the Catalyst optimizer and what transformation happens in each?

Sample answer outline

Four phases: (1) Analysis: parse SQL to an unresolved logical plan, then resolve column names and types using the catalog. (2) Logical optimization: apply rule-based optimizations like constant folding, predicate pushdown, and column pruning. (3) Physical planning: generate one or more physical plans and pick the best with a cost model (choosing broadcast join vs sort-merge join). (4) Code generation: Tungsten whole-stage codegen generates Java bytecode for the physical plan. A strong candidate can name at least two concrete rules from each phase.

Expect these follow-ups

  • What is whole-stage codegen and why does it improve performance over the volcano iterator model?
  • How do you view the execution plan of a DataFrame and what does the three-level indentation mean?
sparkcatalystquery-optimizationdomain-knowledgebig-data

As asked

Scala is both object-oriented and functional. Give me a concrete example of where using traits and class inheritance alongside type classes produces a better design than either paradigm alone.

Sample answer outline

Traits encode protocols (interface-like) without forcing inheritance chains. Type classes add behavior retroactively to types you do not control. A good design uses case classes for data (immutable, structural equality, pattern matching), sealed traits for sum types, and type classes for generic behavior. Example: a Json type class where third-party types get Encoder instances via companion implicits, while your own domain types use derives (Scala 3) or shapeless to avoid boilerplate. The candidate should contrast this with pure OOP where you would need to extend a JsonSerializable interface.

Expect these follow-ups

  • What is the expression problem and how do type classes solve it where OOP struggles?
  • How do traits with abstract members differ from type classes and when do you choose each?
functional-programmingoopscalatype-classesdomain-knowledge

As asked

When do you need Akka Streams' Graph DSL instead of a simple linear Source to Sink chain? Describe a real fan-out/fan-in use case and explain how GraphDSL.create handles it.

Sample answer outline

Graph DSL is needed for non-linear topologies: broadcast (one input, many outputs), merge (many inputs, one output), balance (round-robin distribution), and zip. Real use case: split a stream of orders into high-priority and normal, process them on separate flows with different parallelism, then merge results. GraphDSL.create builds a closed or partial graph by connecting ports explicitly. The builder uses ~> to connect stages. A strong candidate knows that the Graph DSL is closed at compile time for port connectivity checks.

Expect these follow-ups

  • What happens to backpressure when you use a Broadcast junction in Akka Streams?
  • How do you handle the case where one branch of a Broadcast is much slower than the other?
akka-streamsgraph-dslfan-outdomain-knowledgescala

As asked

How does JVM JIT compilation work and how does it specifically affect a Scala application's startup and steady-state throughput? What is the role of tiered compilation?

Sample answer outline

JIT starts with interpreted execution (tier 1), then compiles hot methods to native code via C1 (faster compile, less optimization) and C2 (slower compile, better optimization) based on execution counts. Tiered compilation (-XX:+TieredCompilation, on by default) blends both. For Scala, startup is slow because the class loader, implicit resolution overhead in the JVM, and JIT warm-up all combine. Steady-state throughput can match or exceed hand-written Java for hot paths. GraalVM native image eliminates JIT warm-up by AOT compiling, at the cost of some dynamic features.

Expect these follow-ups

  • What is JIT deoptimization and when does the JVM fall back from compiled to interpreted code?
  • How does GraalVM's native image affect Scala's reflection-based features like Spark's encoder derivation?
jvmjitperformancedomain-knowledgescala