Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
TypeScriptJavaScriptJune 22, 20264 min read

TypeScript Feature Flags: Const Assertions & Mapped Types Guide

Master TypeScript feature flags using const assertions and mapped types. Learn to enforce compile-time safety and eliminate runtime configuration bugs.

TypeScriptFeature FlagsConst AssertionsMapped TypesType SafetyJavaScriptFrontend

Last month, I spent about three hours debugging a production incident caused by a simple typo in a feature flag key. The flag was toggled off in our dashboard, but the application code was still trying to read a legacy string key that didn't exist anymore.

We’ve all been there. Using raw strings for feature flags is a recipe for silent, catastrophic failures. If you want to move beyond "hope-based development," you need to leverage TypeScript to make your Feature Flags strictly typed. By combining Const Assertions and Mapped Types, we can turn our configuration into a source of truth that the compiler actually understands.

The Problem with String-Based Flags

In many older codebases, we treat flags as a loose dictionary:

TYPESCRIPT
const flags = {
  "new-checkout-flow": true,
  "beta-dashboard": false,
};

// Somewhere in the app
if (flags["new-checkout-flow"]) {
  // ...
}

This is brittle. If you rename a key or delete a flag, TypeScript won't warn you. You'll only find out when the app crashes or behaves unexpectedly in production. To fix this, we first need to lock down our configuration object.

Locking Down Configuration with Const Assertions

The first step toward Type Safety is ensuring our configuration object is read-only and its values are literal types, not just generic string or boolean. We use as const for this.

TYPESCRIPT
const featureFlags = {
  enableNewCheckout: true,
  enableBetaDashboard: false,
  maxItemsInCart: 5,
} as const;

By adding as const, TypeScript treats this object as a read-only set of literal values. enableNewCheckout is no longer just a boolean; it is specifically true.

However, this isn't enough yet. We need a way to derive types from this object so we can use them elsewhere in our application. This is where Mapped Types come into play. If you're interested in how similar techniques apply to other parts of your architecture, check out how I used TypeScript Mapped Types for Effortless API Integration Syncing to keep frontend models in sync.

Implementing Mapped Types for Dynamic Flags

Let’s say we want to create a FlagKey type that is automatically derived from our featureFlags object.

TYPESCRIPT
type FlagKey = keyof typeof featureFlags;

Now, if you try to pass a flag that doesn't exist to your lookup function, the compiler will scream at you. But we can take it a step further. What if we want to ensure that our application only handles flags that are explicitly defined as booleans?

We can use Mapped Types to create a specific interface for our configuration:

TYPESCRIPT
type BooleanFlags = {
  [K in keyof typeof featureFlags as(typeof featureFlags)[K] extends boolean ? K : never]: boolean;
};

This filters our keys. Only the flags that are booleans are included in the BooleanFlags type. This is the same logic I often use when I discuss TypeScript Configuration Patterns: Enforcing Type-Safe Partial Defaults to prevent invalid state configurations.

Why This Architecture Works

When I first started refactoring our flag system, I tried to use an enum. It felt right, but it caused issues with tree-shaking and became a nightmare to maintain when we started pulling flag values from an external API.

The const assertion approach is superior because:

  1. Zero Runtime Overhead: The compiler handles the validation. There is no extra code shipped to the browser.
  2. Auto-Completion: Your IDE will suggest the correct flags as you type, which is a massive productivity boost.
  3. Refactoring Confidence: If you rename a flag in the featureFlags constant, TypeScript will highlight every file in your project that needs an update.

Dealing with Complex Flag Types

Sometimes, a flag isn't just a boolean. Maybe it's a numeric limit or a configuration string. If you need to enforce deep structural integrity for these, you might want to look into TypeScript Recursive Conditional Types for Safer Configuration Objects.

For most feature flags, however, keeping it simple is the best path. Use a union type for keys and a constrained object for the values.

FAQ

Can I still fetch flags from an API? Yes. You can define the shape of your API response using the types we derived. Use a type guard to ensure the incoming JSON matches your expected featureFlags structure at runtime.

Does this work with dynamic feature flag names? If the flag name is truly dynamic (e.g., coming from a user-inputted string), you will eventually hit a wall where TypeScript can't help you. Use a branded type to wrap those strings, as discussed in TypeScript Branded Types: Enforcing Domain Integrity at Compile-Time, to track where these "unsafe" strings enter your system.

What if I have hundreds of flags? If you have a massive number of flags, I recommend splitting them into smaller, domain-specific objects rather than one giant configuration file. You can then compose these objects into a main AppFlags type.

Final Thoughts

I’m still experimenting with how to handle "stale" flags. While the types prevent errors, they don't automatically delete the keys from the code once they're no longer needed. I’m currently looking into custom ESLint rules to warn us when a flag has been marked as deprecated: true in our config object.

Don't over-engineer it. Start with as const, derive your FlagKey type, and watch how many runtime errors simply vanish from your logs.

Back to Blog

Similar Posts

Woman sitting on green rug working on laptop, surrounded by technology books in a modern room.
TypeScriptJavaScriptJune 21, 20264 min read

TypeScript Mapped Types for Effortless API Integration Syncing

TypeScript Mapped Types can automate your API integration, keeping frontend models in sync with backend contracts. Stop writing manual interfaces today.

Read more
TypeScript
JavaScript
June 21, 2026
4 min read

TypeScript Recursive Conditional Types for Safer Configuration Objects

TypeScript recursive conditional types help you prevent impossible states in complex configuration objects. Learn to enforce deep type safety at compile-time.

Read more
TypeScriptJavaScriptJune 21, 20264 min read

TypeScript Value Objects: Eliminating Primitive Obsession in Your Code

TypeScript Value Objects help you eliminate primitive obsession by wrapping raw data in domain-specific types. Learn to prevent bugs with better type safety.

Read more