-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
231: Code Review Checklist for Production Scala Services
A few years ago, I was reviewing a PR for a high-throughput payment gateway. The developer had implemented a new retry mechanism using Future.sequence on a list of API calls. It looked elegant—very "functional"—and passed all the unit tests. But about an hour after it hit production, the service started flatlining. We weren't seeing CPU spikes or memory leaks; we were seeing thread pool exhaustion. Because they had launched thousands of Futures into the default global ExecutionContext without any throttling, the service spent more time context-switching than actually processing payments. The health check failed, the load balancer pulled the node, and we had a cascading failure across the cluster.
That's the thing about Scala: it gives you incredibly powerful tools to express complex logic concisely, but those same tools can hide production landmines if you aren't looking for them during code review. When you're reviewing Scala code for a production service, you can't just check if the logic is "correct." You have to check if the code is "stable" under load.
Taming the Concurrency Chaos
The first thing I look for in any PR is how Futures are being handled. If I see Await.result or Await.ready anywhere in the business logic, it's an immediate request for change. Blocking a thread in a non-blocking system is a cardinal sin; it’s the fastest way to kill your throughput. If the developer truly needs to block, they should be using a dedicated, separate ExecutionContext specifically tuned for blocking I/O, and they should wrap the call in a blocking { ... } block to hint to the ForkJoinPool that it needs to spawn a replacement thread.
I also keep an eye out for "Future leaks." This happens when a Future is created but its result is never handled, or worse, when a recursive function creates Futures without a proper termination condition or limiting mechanism. Always ask: "What happens if this external service takes 30 seconds to respond instead of 30 milliseconds?" If the answer is "we spawn 10,000 more Futures," the code isn't production-ready.
Eliminating the 'Throw' Mentality
In a production Scala service, throw new Exception() is a smell. Exceptions are for truly exceptional, unrecoverable failures—like the disk being full or the JVM running out of memory. For business-level errors (e.g., UserNotFound or InsufficientFunds), I expect to see Either[Error, Success] or a custom ADT.
When reviewing, look for try-catch blocks. If you see a giant try-catch wrapping a whole method, the developer is treating Scala like Java. Encourage them to wrap the risky call in a Try(...) and then map that into an Either. This forces the next developer in the chain to actually handle the error case rather than hoping the exception is caught somewhere up the stack. I've seen far too many production outages caused by an uncaught exception killing a critical background actor or stream processor.
Spotting Performance Leaks in the Type System
Finally, we need to talk about the "hidden" costs. Scala's collections are beautiful, but using the wrong one in a hot loop can be disastrous. If I see a List being accessed by index (list(i)) inside a loop, that's an O(n) operation that turns a linear process into a quadratic one. In those cases, I'll suggest a Vector or an Array.
I also scrutinize implicit conversions and lazy val usage in singletons. A lazy val is thread-safe by default, but it achieves this via a synchronized block under the hood. If you have a high-contention piece of code hitting a lazy val in a shared object, you might be introducing a bottleneck you didn't even know existed. I usually suggest replacing these with a simple val if the initialization cost is low, or a more explicit caching strategy if it's high.
📋 Practical Task
Refactoring the Leaky Payment Gateway Client
You have been assigned to review a snippet of code intended for the PaymentService. The current implementation is prone to thread exhaustion and fragile error handling. Your task is to refactor the processPayments method to meet production standards.
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
case class Payment(id: String, amount: Double)
case class PaymentResult(id: String, success: Boolean)
object PaymentGateway {
def callApi(p: Payment): Future[PaymentResult] = {
// Simulates an API call
Future {
Thread.sleep(100)
if (p.amount > 1000) throw new RuntimeException("Limit exceeded")
PaymentResult(p.id, true)
}
}
}
class PaymentService {
def processPayments(payments: List[Payment]): List[PaymentResult] = {
// PROBLEM: This blocks the main thread and doesn't handle errors safely
payments.map { p =>
val f = PaymentGateway.callApi(p)
Await.result(f, 2.seconds)
}
}
}
Requirements for your fix:
- Remove
Await.result. The method should now return aFuture[List[Either[Throwable, PaymentResult]]](or a similar non-blocking structure). - Replace the
throw new RuntimeExceptionlogic in the gateway simulation (or handle it in the service) usingTryorEitherso the entire batch doesn't fail if one payment exceeds the limit. - Ensure that the transformation from a list of payments to a list of results happens asynchronously.
There are no comments for now.