When deployment is a pain: why developers love feature flags
\n\nImagine: you've been working on a new feature for a week. You covered it with tests, ran code review, hit the deploy button. And then — boom! — everything breaks in production. Users are furious, the manager is twitching, you're frantically rolling back the version. Sound familiar?
\n\nNow imagine a different scenario: you enable the new feature for only 5% of users. You watch the metrics. You see everything is fine. Gradually increase the percentage to 100%. No stress, no rollbacks, just calm confidence.
\n\nThis is the magic of feature flags (also known as feature toggles). In TypeScript projects, this technique is especially great thanks to static typing and strict type control. Today we'll figure out how to implement feature flags in your project so you can sleep soundly and features can be enabled without pain.
\n\nWhat are feature flags in simple terms
\n\nA feature flag is a conditional switch in the code that allows you to enable or disable functionality without changing the code or redeploying. It looks something like this:
\n\nif (featureFlags.isEnabled('new-checkout')) {\n // new code\n} else {\n // old code\n}\n\n\nEverything ingenious is simple, right? But behind this simplicity lies a powerful tool used by Google, Netflix, Facebook, and other giants. They don't deploy every little thing — they manage application behavior through a centralized control panel.
\n\nWhen feature flags are really needed
\n\n- \n
- Canary release — rolling out a new feature to a small portion of users \n
- Dark launch — hidden testing, where the feature exists but users don't see it \n
- A/B testing — comparing two versions of functionality \n
- Kill switch — emergency disabling of problematic code without deployment \n
- Permit testing — testing on internal users or by roles \n
In TypeScript projects, feature flags take on a special charm: the compiler checks that you're using flags correctly, not confusing types, and not forgetting to handle all states.
\n\nArchitecture of a feature flag system
\n\nBefore writing code, let's figure out what a well-designed feature management system consists of.
\n\nBasic components
\n\n- \n
- Flag storage — where the values are stored (JSON, database, external service) \n
- Provider — a module that loads and caches flags \n
- Client side — code that checks the state of flags \n
- Control panel — interface for changing flags in real time \n
Simplest implementation in TypeScript
\n\nLet's start with the most basic example. Let's create a type for flags and a simple service:
\n\n// types/feature-flags.ts\nexport type FeatureFlag = \n | 'new-checkout'\n | 'dark-mode'\n | 'recommendations-v2'\n | 'beta-chat';\n\nexport interface FeatureFlagConfig {\n flag: FeatureFlag;\n enabled: boolean;\n rolloutPercentage?: number;\n userIds?: string[];\n}\n\n\nNow let's write the service itself:
\n\n// services/feature-flag.service.ts\nexport class FeatureFlagService {\n private flags: Map<FeatureFlag, FeatureFlagConfig> = new Map();\n\n constructor(initialFlags: FeatureFlagConfig[]) {\n initialFlags.forEach(flag => {\n this.flags.set(flag.flag, flag);\n });\n }\n\n isEnabled(flag: FeatureFlag, userId?: string): boolean {\n const config = this.flags.get(flag);\n if (!config) return false;\n\n if (!config.enabled) return false;\n\n // Check by specific users\n if (config.userIds && userId) {\n return config.userIds.includes(userId);\n }\n\n // Rollout by percent