Skip to Content
Course content

152: Common Scala Anti-Patterns to Avoid

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 var and the mutable ListBuffer.
  • Replace Await.result with a for-comprehension or flatMap.
  • 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)
  }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.