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
58: Mutable vs Immutable Collections In Depth
Imagine you're managing a project on a physical whiteboard in an office. Anyone can walk up to it, erase a task, and write in a new one. If you look at the board on Monday, it says one thing; by Tuesday, it's completely different. That whiteboard is mutable. The problem? If you need to know what the board looked like on Monday to figure out where a mistake happened, you're out of luck. It's gone.
Now, imagine instead that every time a change is made, you take a high-resolution Polaroid photo of the board and hand it to the team. The original photo never changes. If you want to add a task, you take a new photo that includes the old tasks plus the new one. This is immutable. You have a perfect, unchangeable history, and you can hand a photo to a teammate without worrying that they'll scribble over it while you're still looking at it.
Mapping the Polaroid to the Code
In Scala, this isn't just a philosophical choice; it's baked into the library hierarchy. Most of the time, you'll be using scala.collection.immutable. When you "add" an element to an immutable List, you aren't actually changing the list—you're creating a new one that shares most of its structure with the old one.
val initialInventory = List("Sword", "Shield")
val updatedInventory = "Potion" :: initialInventory
println(initialInventory) // Still List(Sword, Shield)
println(updatedInventory) // List(Potion, Sword, Shield)
In the example above, initialInventory is like that first Polaroid. It's locked. updatedInventory is a new photo. I love this because it eliminates a whole category of bugs where one part of your app accidentally changes a list that another part of your app was relying on.
The Cost of the "New Photo"
You might be thinking: "Wait, if I have 10,000 items, am I really copying the whole list every time I add one item? That sounds like a performance nightmare."
This is where Scala's "structural sharing" comes in. For a List, adding an element to the front (prepending) doesn't copy the rest of the list; the new list simply points to the old list as its "tail." It's incredibly efficient. However, if you need fast random access (like grabbing the 500th element), a List is slow because it has to walk from the start. That's why we have Vector. It's also immutable, but it uses a tree structure under the hood to make updates and lookups much faster.
When to Embrace the Whiteboard
I'll be honest with you: being a purist about immutability can sometimes lead to awkward code or poor performance in very specific scenarios. If you're in a tight loop updating a value 100,000 times a second, creating 100,000 immutable objects will put a lot of pressure on the Garbage Collector.
This is when you reach for scala.collection.mutable. An ArrayBuffer, for instance, is essentially that whiteboard. You modify it in place.
import scala.collection.mutable.ArrayBuffer
val scores = ArrayBuffer(10, 20, 30)
scores += 40 // This modifies the existing object in memory
println(scores) // ArrayBuffer(10, 20, 30, 40)
My rule of thumb? Start immutable. Always. If you find a specific bottleneck in your profiling, or if you're implementing a complex local algorithm where a mutable buffer makes the logic significantly cleaner, switch to a mutable collection. But keep that mutability local. Don't let a mutable collection leak out of a class or a function; return it as an immutable List or Vector so the rest of your system stays safe.
📋 Practical Task
Build a Versioned Game Inventory System
You need to create a system that tracks a player's inventory but allows them to "Undo" an item pickup. Since we want to preserve history, you cannot use a mutable collection for the history itself.
Requirements:
- Create a case class
GameStatethat holds an immutableList[String]of items. - Implement a
GameManagerclass that maintains aList[GameState]representing the history of the game. - Add a method
pickupItem(item: String): This should create a newGameStatewith the item added and push this new state onto the history list. - Add a method
undoPickup(): This should remove the most recent state from the history, effectively reverting the inventory to the previous version. - Add a method
currentInventory: This should return the items in the most recent state.
Goal: Demonstrate that by using a list of immutable states, you can travel back in time without ever manually "removing" an item from a mutable list.
There are no comments for now.