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
59: ArrayBuffer and ListBuffer
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.ArrayBufferto accumulate only the transactions that arecompletedAND have an amount greater than or equal to theminimumAmount. - 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.
There are no comments for now.