TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
240: Typing Feature Flags Safely
We've all been there. You're pushing a high-risk feature to production, so you wrap it in a feature flag. It starts simple—maybe just a boolean in a config file or a value coming from a remote API like LaunchDarkly. But as the project grows, your feature flag logic usually evolves into a mess of "magic strings" scattered across the codebase. I've seen projects with hundreds of flags where nobody remembers what is_new_header_v2_final_updated actually does.
The danger of stringly-typed flags
The naive way to handle flags is to treat them as a generic dictionary. You'll often see something like this in the wild:
const flags: Record<string, boolean> = {
'new-checkout-flow': true,
'beta-search-bar': false,
};
if (flags['new-checkot-flow']) { // Typo here!
renderNewCheckout();
}
The problem here is that TypeScript is doing exactly what you told it to: allowing any string as a key. In the example above, I misspelled checkout as checkot. TypeScript doesn't blink. At runtime, flags['new-checkot-flow'] returns undefined, which is falsy. The feature simply doesn't turn on, and you spend two hours debugging why your "enabled" flag isn't working, only to realize it was a typo. It's a frustrating waste of time.
Moving toward a single source of truth
To fix this, we need to stop treating our flags as a generic map and start treating them as a first-class type. By defining a strict interface for your flags, you shift the burden of correctness from your memory to the compiler.
interface FeatureFlags {
newCheckoutFlow: boolean;
betaSearchBar: boolean;
experimentalTheme: 'light' | 'dark' | 'system';
}
const flags: FeatureFlags = {
newCheckoutFlow: true,
betaSearchBar: false,
experimentalTheme: 'system',
};
// Now, this would be a compile-time error:
if (flags.newCheckotFlow) {
renderNewCheckout();
}
I noticed in the example above that I added experimentalTheme as a union of strings. This is where the typed approach really pays off. Real-world feature flags aren't always binary; sometimes they are multivariate. If you use a Record<string, boolean>, you're forced to create three different boolean flags (e.g., isThemeLight, isThemeDark) which is clumsy and error-prone. A union type makes the intent explicit.
Dealing with the "Zombie Flag" problem
The biggest cost of a strictly typed system isn't the initial setup—it's the cleanup. Feature flags are meant to be temporary. Once a feature is 100% rolled out, the flag should be deleted. When you use magic strings, finding every instance of 'new-checkout-flow' across a massive repo is a game of "grep and pray."
With a typed FeatureFlags interface, you have a central registry. When it's time to kill a flag, you delete the property from the interface. Immediately, TypeScript will light up your entire IDE with red squiggles everywhere that flag was being used. I actually love this part of the process; it transforms the tedious task of "hunting for dead code" into a simple checklist of compiler errors. You can't accidentally leave a zombie flag lurking in a forgotten utility file because the code simply won't compile until the reference is gone.
📋 Practical Task
Refactoring the Beta Dashboard Flag System
You've inherited a codebase where the feature flags are handled via a Record<string, any>, leading to several runtime bugs. Your task is to bring type safety to the system.
// CURRENT NAIVE IMPLEMENTATION
const flags: Record<string, any> = {
'show-beta-dashboard': true,
'api-version': 'v2',
'enable-logging': false
};
function initializeApp() {
// BUG: This typo causes the dashboard to never show,
// but TypeScript doesn't warn us.
if (flags['show-beta-dashbord']) {
console.log("Loading Beta Dashboard...");
}
console.log(`Using API: ${flags['api-version']}`);
}
Your Goal:
- Create a
FeatureFlagsinterface that strictly defines the three flags present in the object. - Ensure
api-versionis restricted to specific allowed versions (e.g.,'v1' | 'v2') rather than a generic string. - Apply this interface to the
flagsconstant. - Fix the typo in the
initializeAppfunction so that the compiler helps you find and correct it.
There are no comments for now.