Scala
Completed
-
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
152: Common Scala Anti-Patterns to Avoid
You've probably noticed by now that Scala is incredibly flexible. It lets you write code that looks like Java, code that looks like Haskell, and everything in between. The danger here is that it's very easy to write "Java with Scala syntax," which is an anti-pattern I see constantly. When you do that, you're fighting the language rather than using it, and you end up missing out on the safety and conciseness that make Scala worth using in the first place.
Fighting the urge to use var for aggregation
One of the first habits I had to break when moving to Scala was the addiction to var and mutable collections for simple data transformation. In a language like Java, if you want to filter a list of orders and sum up the totals, you'd probably create a mutable double sum = 0, loop through the list, and add to it. In Scala, the naive approach looks like this:
def calculateTotal(orders: List[Order]): Double = {
var total = 0.0
orders.foreach { order =>
if (order.isValid) {
total += order.amount
}
}
total
}
This works, but it's "noisy." You're managing state manually, and as your logic grows, that total variable becomes a liability. If this method were to grow and involve concurrency, you'd suddenly be worrying about race conditions. The better way is to treat your data as a flow. Use filter and sum, or a foldLeft if the logic is more complex.
def calculateTotal(orders: List[Order]): Double = {
orders
.filter(_.isValid)
.map(_.amount)
.sum
}
By shifting to a declarative style, you've eliminated the mutable state entirely. The code describes what you want to happen, not how to move the bits around. I usually find that once I stop reaching for var, my bugs decrease because there are fewer moving parts to keep track of in my head.
The hidden cost of Await.result
Another trap I see experienced engineers fall into is treating Future like a Promise in JavaScript or a Task in C#, specifically by using Await.result to "get the value out" of the future. It feels intuitive: you start an async operation, and you just want the result right now so you can move to the next line.
def getUserDetails(userId: String): UserDetails = {
val userFuture = userRepository.findUser(userId) // returns Future[User]
val detailsFuture = profileRepository.findProfile(userId) // returns Future[Profile]
// The Anti-Pattern: Blocking the thread
val user = Await.result(userFuture, 5.seconds)
val profile = Await.result(detailsFuture, 5.seconds)
UserDetails(user, profile)
}
This is dangerous. When you call Await.result, you are physically blocking a thread from the execution context. In a high-throughput system, you can easily starve your thread pool. If every request blocks two threads while waiting for the database, your application will grind to a halt even if your CPU usage is low. You've effectively turned an asynchronous system back into a synchronous one, but with all the overhead of futures.
The professional way to handle this is to keep the computation "inside" the future using a for-comprehension. This allows the thread to be released back to the pool while the I/O is happening.
def getUserDetails(userId: String): Future[UserDetails] = {
for {
user <- userRepository.findUser(userId)
profile <- profileRepository.findProfile(userId)
} yield UserDetails(user, profile)
}
Now, the method returns a Future[UserDetails]. You aren't blocking; you're defining a pipeline. The "cost" here is that the calling method must also now handle a Future, which ripples up through your architecture. Some people find this annoying, but it's a necessary trade-off for a system that can actually scale. It forces you to be honest about where the latency in your application exists.
📋 Practical Task
Refactoring a Blocking Order Pipeline
You have been handed a legacy service that processes customer orders. The current implementation is riddled with the anti-patterns we discussed: it uses mutable state to accumulate totals and blocks threads using Await.result. Your task is to refactor the processOrders method to be purely functional and non-blocking.
Requirements:
- Remove the
varand the mutableListBuffer. - Replace
Await.resultwith a for-comprehension orflatMap. - Ensure the final return type is
Future[OrderSummary].
case class Order(id: String, amount: Double, status: String)
case class OrderSummary(totalAmount: Double, processedIds: List[String])
class OrderService(repo: OrderRepository) {
def processOrders(customerId: String): OrderSummary = {
// BAD: Blocking the thread to get the list of orders
val orders = Await.result(repo.fetchOrdersForCustomer(customerId), 10.seconds)
var total = 0.0
val processedIds = new scala.collection.mutable.ListBuffer[String]()
orders.foreach { order =>
if (order.status == "COMPLETED") {
total += order.amount
processedIds += order.id
}
}
OrderSummary(total, processedIds.toList)
}
}There are no comments for now.