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
3: Scala 2 vs Scala 3 Differences
If you've been skimming through old Scala forums or reading legacy documentation, you've probably run into the idea that Scala 3 is basically "Scala 2 with optional curly braces." It's a common misconception—the idea that the update was primarily aesthetic, designed to make the code look more like Python or Haskell without changing the underlying machinery.
I can tell you from experience: that's a dangerous way to approach the language. If you treat Scala 3 as just a syntax sugar update, you'll spend your time fighting the compiler because you're trying to write Scala 2 code without braces, rather than leveraging the actual architectural shifts. The real changes aren't about where the } goes; they're about how the type system handles data and how the compiler manages dependencies.
It's more than just missing curly braces
Yes, the indentation-based syntax is the first thing you'll notice. It's great, and I personally love not having to hunt for a missing closing brace in a 50-line method. But the shift is deeper. In Scala 2, we spent a lot of time creating "boilerplate" to simulate features that Scala 3 now treats as first-class citizens. For example, creating a proper Algebraic Data Type (ADT) in Scala 2 required a sealed trait and a handful of case objects or case classes. It worked, but it was verbose.
In Scala 3, we have enum. It's not just a Java-style enum; it's a powerful tool for defining data shapes that the compiler can actually reason about more efficiently.
// The Scala 2 way sealed trait Shape case class Circle(radius: Double) extends Shape case class Square(side: Double) extends Shape // The Scala 3 way enum Shape: case Circle(radius: Double) case Square(side: Double)Replacing the chaos of implicit with given and using
This is where most developers hit a wall. In Scala 2, the
implicitkeyword was the "Swiss Army Knife" of the language. It did everything: it defined implicit parameters, implicit returns, and implicit conversions. It was powerful, but it was a nightmare for readability. When you sawimplicit, you had to stop and ask, "Wait, is this providing a value, or is it changing the type of this object?"Scala 3 splits this into
givenandusing. I find this much more honest. Agivendefines a value that can be used implicitly, andusingexplicitly marks a parameter that the compiler should look for agivento fill.// Scala 2 implicit def jsonEncoder(d: User): String = s"{\"name\": \"${d.name}\"}" def printUser(u: User)(implicit enc: String) = println(enc) // Scala 3 given jsonEncoder: User => String = u => s"{\"name\": \"${u.name}\"}" def printUser(u: User)(using enc: String) = println(enc)The logic is the same, but the intent is clear. You aren't guessing anymore.
Actually using Union Types to handle multiple shapes
For years, if we wanted a method to accept either a
Stringor anInt, we had to wrap them in a custom trait or useAny(which is basically giving up on type safety). Scala 3 introduces Union Types via the|operator. This is a game-changer for API design.Imagine you're building a system that accepts a user ID as either a numeric database ID or a UUID string. In Scala 2, you'd be writing a wrapper class. In Scala 3, you just do this:
def findUser(id: Int | String): User = if id.isInstanceOf[Int]) { // logic for Int } else { // logic for String }It's clean, it's direct, and it removes a huge amount of the "wrapper" noise that used to clutter my projects.
📋 Practical Task
Refactoring the Legacy Payment Gateway Validator
You've inherited a piece of legacy Scala 2 code used to validate payment methods. It uses the old sealed trait pattern and a confusing implicit setup. Your task is to refactor this into idiomatic Scala 3.
The Legacy Code:
sealed trait PaymentMethod case class CreditCard(number: String) extends PaymentMethod case class PayPal(email: String) extends PaymentMethod implicit def paymentFormatter(p: PaymentMethod): String = p match { case CreditCard(n) => s"Card ending in ${n.takeRight(4)}" case PayPal(e) => s"PayPal account ${e}" } def logPayment(p: PaymentMethod)(implicit fmt: String) = println(fmt)Requirements for your refactor:
- Convert the
PaymentMethodhierarchy into a Scala 3enum. - Replace the
implicit defwith agiveninstance. - Update the
logPaymentmethod to use theusingkeyword for its implicit parameter. - Remove all unnecessary curly braces to follow Scala 3's indentation-based style.
There are no comments for now.