Skip to Content
Course content

59: ArrayBuffer and ListBuffer

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

When you first encounter ArrayBuffer and ListBuffer, it's easy to fall into a specific trap: thinking of them as the "correct" way to handle collections when you just happen to need a list that can change. You might think, "I need a list of users that I can add to as I read a file, so I'll just use a ListBuffer as my primary data structure for this class."

Let me show you why that's a dangerous path. Imagine you have a ListBuffer representing a shopping cart, and you pass that buffer to a DiscountCalculator service. If that service decides to "clean up" the cart by removing items it deems ineligible for a discount, it's mutating your original cart. Because buffers are mutable, every other part of your code holding a reference to that cart suddenly sees the items vanish. You've introduced a side effect that's a nightmare to debug in a large system.

Stop treating Buffers as State and start treating them as Builders

The real way to use buffers in Scala is to treat them as temporary construction sites. You use a buffer locally within a method to accumulate results—where mutation is fast and efficient—and then you call .toList or .toArray to "freeze" the result into an immutable collection before returning it to the rest of your app.

def filterExpensiveProducts(products: List[Product], threshold: Double): List[Product] = {
  val resultBuffer = new scala.collection.mutable.ListBuffer[Product]()
  
  for (p <- products) {
    if (p.price > threshold) {
      resultBuffer += p // Fast, local mutation
    }
  }
  
  resultBuffer.toList // Return an immutable snapshot
}

In this pattern, the mutation is contained. The caller of filterExpensiveProducts has no idea a buffer was ever used; they just get back a safe, immutable List. I almost always prefer this over creating a new immutable list in every iteration of a loop, which would involve copying the entire list over and over again.

ArrayBuffer vs ListBuffer: Which one actually fits?

You'll notice both provide almost identical APIs (like += and -=), but they work very differently under the hood. Choosing the wrong one can lead to subtle performance hits as your data grows.

ArrayBuffer is the workhorse. It's backed by an array that grows dynamically. Because it's contiguous in memory, it's incredibly fast for random access (getting the element at index 50) and generally has better cache locality. If you aren't sure which one to use, start with ArrayBuffer. It's usually the right choice for most "build-and-freeze" scenarios.

ListBuffer, on the other hand, is specifically designed to build a List. It maintains a pointer to the last element, allowing it to append items in constant time without needing to reallocate and copy a whole array. If your end goal is specifically an immutable List and you are doing a massive amount of appending, ListBuffer is slightly more elegant. However, I'll be honest: in 90% of the production code I write, ArrayBuffer is my default because its performance profile is more predictable across different access patterns.




📋 Practical Task

Build a Transaction Filtering Pipeline

You need to process a raw stream of transaction data. Some transactions are "pending" and should be ignored, while others are "completed" and should be kept. However, the raw data is messy and comes in as a simple sequence.

Write a function called processTransactions that takes a Seq[Transaction] and a minimumAmount: Double. Inside the function:

  • Use a mutable.ArrayBuffer to accumulate only the transactions that are completed AND have an amount greater than or equal to the minimumAmount.
  • Once the filtering is done, convert the buffer into an immutable Vector.
  • Return that Vector.

Case Class Definition:
case class Transaction(id: String, amount: Double, status: String)

Expected Behavior:
If you pass in a list of 5 transactions and only 2 meet the criteria, the function should return an immutable Vector containing exactly those 2 transactions, ensuring no one can mutate the result after it leaves your function.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.