-
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
229: Practice Exercise: Building a Type-Safe Feature Flag System
In a real-world production environment, you almost never want to deploy a major feature to 100% of your users the second the code hits the server. We use feature flags to decouple deployment from release. But if you've ever worked on a large codebase, you know the horror of "stringly-typed" flags—where a tiny typo in a configuration key like "new_checkout_flow" vs "new_checkout_flwo" results in a feature being silently disabled for everyone.
I want to show you how to build a system that makes those typos impossible by leveraging Scala's type system. We'll build a simple manager for an e-commerce site that handles a new AI-powered search and a promotional banner.
The danger of string-based keys
When I first started implementing flags, I did what most people do: I used a Map with String keys. It feels flexible, but it's a trap. Let's look at how I'd initially approach this, and why it fails.
case class User(id: String, isBetaTester: Boolean)
class FeatureFlagManager(flags: Map[String, Boolean]) {
def isEnabled(flag: String): Boolean = flags.getOrElse(flag, false)
}
val config = Map("ai_search" -> true, "promo_banner" -> false)
val manager = FeatureFlagManager(config)
// Everything looks fine until...
val status = manager.isEnabled("ai_serch") // Typo! Returns false.
The compiler has no idea that "ai_serch" isn't a real flag. In a large app with hundreds of flags, this leads to bugs that are incredibly tedious to track down because the code is technically "correct" (it's just returning a default value).
Letting the compiler catch our typos
To fix this, we need to move the definition of our flags from the configuration layer into the type layer. I prefer using a sealed trait or an enum for this. This way, the isEnabled method doesn't take a String; it takes a specific type that the compiler can verify.
sealed trait FeatureFlag
case object AiSearch extends FeatureFlag
case object PromoBanner extends FeatureFlag
case object NewCheckout extends FeatureFlag
class TypeSafeFeatureManager(flags: Map[FeatureFlag, Boolean]) {
def isEnabled(flag: FeatureFlag): Boolean = flags.getOrElse(flag, false)
}
val config = Map(
AiSearch -> true,
PromoBanner -> false
)
val manager = new TypeSafeFeatureManager(config)
// Now, this won't even compile if I mistype the object name:
// manager.isEnabled(AiSerch) // Compile error!
val status = manager.isEnabled(AiSearch) // Safe and verified
By doing this, we've shifted the failure from runtime (where a user doesn't see a feature) to compile-time (where the developer sees a red squiggly line). It's a small change that saves hours of debugging.
Adding user-specific overrides
Most of the time, you don't just want a global on/off switch. You want "Canary" releases—turning a feature on only for beta testers or internal employees. I like to handle this by passing a context object into the check. I'll modify the manager to handle a hierarchy: first check the user's status, then fall back to the global config.
case class User(id: String, isBetaTester: Boolean)
class AdvancedFeatureManager(globalFlags: Map[FeatureFlag, Boolean]) {
def isEnabled(flag: FeatureFlag, user: User): Boolean = {
flag match {
case AiSearch =>
// AI Search is open to all beta testers, regardless of global flag
if (user.isBetaTester) true else globalFlags.getOrElse(flag, false)
case _ =>
globalFlags.getOrElse(flag, false)
}
}
}
val manager = new AdvancedFeatureManager(Map(AiSearch -> false))
val betaUser = User("123", isBetaTester = true)
val regularUser = User("456", isBetaTester = false)
println(manager.isEnabled(AiSearch, betaUser)) // true
println(manager.isEnabled(AiSearch, regularUser)) // false
I've used a pattern match here because different flags often have different rollout logic. Some are binary, some are percentage-based, and some are user-group based. Putting that logic inside the manager keeps the rest of your business logic clean—your services just ask "is this enabled for this user?" and don't care about the underlying rules.
📋 Practical Task
Exercise: Implementing a Tiered Access Flag System
You are tasked with extending the AdvancedFeatureManager to support a "Premium" user tier. Currently, we only have isBetaTester.
Requirements:
- Add a
isPremium: Booleanfield to theUsercase class. - Add a new
FeatureFlagcalledPremiumAnalytics. - Update the
isEnabledlogic so thatPremiumAnalyticsis only available ifuser.isPremiumis true, regardless of what the global configuration says. - Ensure that
AiSearchremains available to both Beta Testers and whoever the global flag allows.
Write the complete implementation including the FeatureFlag trait, the User class, and the AdvancedFeatureManager. Test it by creating one Premium user and one Regular user, then printing the result of isEnabled(PremiumAnalytics, user) for both.
There are no comments for now.