DEV Community

Rohit Jain
Rohit Jain

Posted on

Feature Flags: The Release Superpower You're Missing

Deploy code anytime. Release features on your terms.


1. Introduction

Every software team, at some point, faces the same uncomfortable situation: a feature is ready to ship, but shipping it means exposing it to every user, everywhere, all at once. If something goes wrong, you're scrambling for a rollback - and rollbacks are slow, stressful, and sometimes risky in their own right.

Feature flags change that equation entirely.

A feature flag is a simple but powerful technique that lets you control which users see which features - at runtime, without touching your codebase or pushing a new deployment. Modern engineering teams at companies like Google, Netflix, Facebook, and Airbnb rely on them heavily. They're how teams ship faster, test smarter, and sleep better at night.

This post will walk you through everything: what feature flags are, how they work under the hood, the different types, how to implement them, and the best practices that separate well-run systems from ones buried in technical debt.


2. The Problem Feature Flags Solve

Traditional deployments: coupling code and features

In a traditional software workflow, releasing a feature means deploying code. The two are inseparable:

  1. Developer writes feature code
  2. Code gets merged to the main branch
  3. The whole application is deployed
  4. The feature is now live - for everyone

This approach creates real problems:

  • All-or-nothing releases. You can't roll out to 5% of users first. It's either live for all or live for none.
  • Slow recovery. If a bug surfaces, fixing it requires a new deployment cycle - which takes time.
  • Long-lived branches. Developers working on big features create separate branches that diverge from the main codebase, leading to painful merge conflicts.
  • No ability to test in production. Staging environments rarely mirror production traffic, so surprises still happen post-launch.

The modern alternative

Feature flags decouple code deployment from feature release:

Without flags:   deploy code  =  release feature  (coupled)
With flags:      deploy code  ≠  release feature  (decoupled)
Enter fullscreen mode Exit fullscreen mode

You can deploy code on Monday with a feature hidden behind a flag, then release that feature to 1% of users on Wednesday, expand to 50% on Thursday, and roll it back in seconds if metrics dip - no new deployment needed.


3. What Are Feature Flags?

A feature flag (also called a feature toggle, feature switch, or feature gate) is a condition in your code that determines whether a particular feature runs or not - based on configuration that can be changed at runtime.

In its simplest form, it looks like this:

if (featureFlags.isEnabled("new_checkout")) {
  showNewCheckout();
} else {
  showOldCheckout();
}
Enter fullscreen mode Exit fullscreen mode

The magic is in isEnabled(). Instead of that value being hardcoded, it's looked up dynamically - from a database, a config file, an environment variable, or a dedicated flag management platform. Change that value, and the feature turns on or off instantly, for whoever you target, without touching or redeploying your code.


4. How Feature Flags Work

Here's the step-by-step flow of how a feature flag evaluation works in a real application:

Step 1: Flag configuration is defined

Somewhere - in a database, a JSON config file, or a flag management service - you define your flags:

{
  "new_checkout": {
    "enabled": true,
    "rollout": 10,
    "owner": "payments-team"
  },
  "dark_mode": {
    "enabled": false,
    "rollout": 0
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Application loads flag configuration

At startup (or periodically), your application loads this configuration. Production-grade SDKs (like LaunchDarkly or Unleash) download the full ruleset locally so that flag checks happen in memory - zero network latency.

Step 3: Feature flag is evaluated at runtime

When a user hits a code path that has a flag check, the flag service evaluates whether the feature should be enabled for that specific user:

Is the flag enabled?
  └── No  → run old code path
  └── Yes → Is there a rollout percentage?
              └── No  → run new code path
              └── Yes → Does this user fall in the rollout bucket?
                          └── Yes → run new code path
                          └── No  → run old code path
Enter fullscreen mode Exit fullscreen mode

Step 4: User gets the appropriate experience

The correct code path executes. The user sees either the new feature or the old one - and that decision was made entirely at runtime, without any deployment.

Step 5: Metrics are tracked

Good flag systems automatically emit analytics events every time a flag is evaluated. This lets you measure the impact of a feature change in real time.


5. Types of Feature Flags

Not all flags are created equal. Different problems call for different types of flags, each with its own lifecycle and purpose.


1. Release Flags

Purpose: Hide incomplete or work-in-progress features from users until they're ready.

Release flags let developers merge code into the main branch continuously, even if the feature isn't finished yet. The code ships, but it's invisible to users.

Example:

Your team is rebuilding the checkout flow. It'll take three sprints. Instead of working on a long-lived branch, you merge your in-progress code behind a new_checkout flag. The old checkout still runs for all users. When the new one is ready and tested, you flip the flag.

Lifespan: Short - remove the flag once the feature is fully rolled out.


2. Experiment Flags (A/B Testing)

Purpose: Show different experiences to different user segments and measure which performs better.

These flags split your user base into groups and expose each group to a different variant. You then compare metrics like conversion rate, click-through rate, or time on page.

Example:

Half your users see a "Buy Now" button in green. The other half see it in blue. After a week of data, you roll out whichever color converts better.

User A → bucket 0–49 → sees green button
User B → bucket 50–99 → sees blue button
Enter fullscreen mode Exit fullscreen mode

Lifespan: Short - once the experiment concludes and a winner is picked, ship the winner and delete the flag.


3. Ops Flags (Kill Switches)

Purpose: Act as circuit breakers for production systems. Disable a feature instantly when something goes wrong.

These are emergency controls. If a new recommendation engine is hammering your database, you flip a kill switch to disable it immediately - no rollback, no deployment, no downtime.

Example:

Your new AI-powered search feature causes a spike in database load during peak traffic. An ops flag lets you turn it off in seconds while the team investigates.

Lifespan: Long - kill switches are often kept around permanently as a safety net.


4. Permission Flags (Entitlement Flags)

Purpose: Gate features behind user roles, subscription tiers, or access levels.

These flags control who has access to what based on identity or plan - not just a percentage rollout.

Example:

Advanced analytics dashboards are available only to enterprise customers. Beta features are visible only to internal testers. Admin-only tools are hidden from regular users.

if (user.plan === "enterprise") → show advanced analytics
if (user.role === "admin")      → show admin panel
Enter fullscreen mode Exit fullscreen mode

Lifespan: Long - these often represent permanent product distinctions between plans.


5. Configuration Flags

Purpose: Change application behavior or parameters without redeploying.

Not quite a feature on/off switch - more like a dial. These let you tune system behavior dynamically.

Example:

Adjusting the maximum file upload size, switching between two third-party payment providers, or changing the timeout threshold on an API call - all without touching code.

Lifespan: Medium - depends on whether the configuration is temporary or permanent.


Summary

Flag Type Primary Use Lifespan
Release Gradual feature rollout Short
Experiment A/B testing Short
Ops / Kill Switch Emergency shutdown Long
Permission Role/plan-based access Long
Configuration Runtime behavior tuning Medium

6. Simple Implementation

Let's build a minimal but solid feature flag system from scratch, and walk through exactly what each piece does.

Step 1: Define your flags in a config

{
  "new_dashboard": { "enabled": true, "rollout": 20 },
  "dark_mode": { "enabled": false, "rollout": 0 }
}
Enter fullscreen mode Exit fullscreen mode
  • enabled - is the flag active at all?
  • rollout - what percentage of users should see this? (0–100)

Step 2: Build the flag evaluation service

class FeatureFlagService {
  constructor(flags) {
    // Store the flag configuration when the service starts
    this.flags = flags;
  }

  isEnabled(flagName, userId = null) {
    // Look up the flag by name
    const flag = this.flags[flagName];

    // If the flag doesn't exist or is disabled, return false immediately
    if (!flag || !flag.enabled) return false;

    // If rollout is 100%, everyone gets it - no need to calculate
    if (flag.rollout >= 100) return true;

    // For partial rollouts, determine which "bucket" the user falls into
    if (userId && flag.rollout > 0) {
      const bucket = this.getUserBucket(userId, flagName);
      return bucket < flag.rollout;
      // If rollout is 20, users with bucket 0–19 see the feature (20%)
    }

    return false;
  }

  getUserBucket(userId, flagName) {
    // Combine userId + flagName so the same user gets DIFFERENT buckets
    // for different flags (avoids correlation between rollouts)
    const input = `${flagName}.${userId}`;

    // Simple hash function: produces a number from the string
    let hash = 0;
    for (let i = 0; i < input.length; i++) {
      hash = (hash << 5) - hash + input.charCodeAt(i);
      hash |= 0; // Convert to 32-bit integer
    }

    // Return a stable number between 0 and 99
    return Math.abs(hash) % 100;
  }
}
Enter fullscreen mode Exit fullscreen mode

Line-by-line breakdown:

  • constructor(flags) - loads the flag config when the service initializes
  • isEnabled(flagName, userId) - the main method you call from your app
  • if (!flag || !flag.enabled) return false - early exit: flag is off or doesn't exist
  • getUserBucket(userId, flagName) - hashes user + flag name into a stable 0–99 number
  • return bucket < flag.rollout - if rollout is 20, buckets 0–19 return true (20% of users)

The consistent hashing is critical. A user who hashes to bucket 15 will always hash to bucket 15 for a given flag. This means they won't see a feature "flicker" - they're either in or out, every single time.


Step 3: Use it in your application

// Initialize with your config
const flags = new FeatureFlagService(loadFlagsFromConfig());

// Later, in your app code:
const userId = getCurrentUser().id;

if (flags.isEnabled("new_dashboard", userId)) {
  renderNewDashboard();
} else {
  renderOldDashboard();
}
Enter fullscreen mode Exit fullscreen mode

In a React frontend:

function Dashboard({ userId }) {
  const flags = useFeatureFlags(); // custom hook wrapping the service

  return flags.isEnabled("new_dashboard", userId) ? (
    <NewDashboard />
  ) : (
    <OldDashboard />
  );
}
Enter fullscreen mode Exit fullscreen mode

This is all it takes to get started. Real systems add more - targeting rules, streaming updates, audit logs - but the core logic is exactly this.


7. Real-World Example: Safe Deployment at Scale

Let's trace through a real scenario: your team is shipping a redesigned checkout experience.

The old way (no flags)

  1. Build the new checkout → merge to main → deploy to production
  2. 100% of users hit the new checkout immediately
  3. An edge case bug causes 3% of orders to fail silently
  4. Revenue is dropping before anyone notices
  5. Scramble to hotfix and redeploy - takes 45 minutes

The new way (with flags)

Week 1 - Deploy hidden:

The new checkout code ships to production behind a new_checkout flag set to enabled: false. Zero users see it. Zero risk.

Week 2 - Internal testing:

Set rollout: 0, but add a targeting rule: if user.role == "employee" → serve: true. Your whole team uses the new checkout in production, catching real bugs with real data.

Week 3 - Canary rollout:

Day 1:  rollout: 1    → 1% of users
Day 2:  rollout: 5    → monitor error rates, latency, conversion
Day 3:  rollout: 20   → still looking good
Day 5:  rollout: 50
Day 7:  rollout: 100  → full launch
Enter fullscreen mode Exit fullscreen mode

Something goes wrong at 20%?

Flip the flag back to rollout: 0 in under 10 seconds. No deployment needed. The other 80% never even knew anything happened.

After full launch:

Delete the flag and the old checkout code. Done.


8. Benefits of Feature Flags

Safer Deployments

Code changes are separated from feature visibility. A bug in new code only affects users in the rollout bucket, not everyone. And you can revert instantly - without a redeployment.

Faster Experimentation

Want to test a hypothesis? Define two variants, split your traffic, and get data from real users in real conditions. No waiting for the next release cycle.

Better Control Over Features

Permission flags let product teams control exactly who sees what - by plan, role, geography, or any custom attribute. You can give your enterprise customers early access, run a beta program, or offer a white-glove onboarding path without writing new deployment logic.

Continuous Delivery

Feature flags are the foundation of trunk-based development - everyone commits to main, and risky changes are always hidden behind a flag. This eliminates long-lived branches and merge conflicts, enabling teams to ship to production multiple times a day.

Instant Incident Response

Kill switches give your on-call engineers a lever to pull during an incident. Instead of a stressful 2am rollback, it's a one-click flag toggle. The problematic feature is gone in seconds.


9. Best Practices

Manage the Flag Lifecycle

Flags are temporary by design but become permanent by neglect. A flag that never gets removed is a liability:

  • Dead code branches no one dares to touch
  • Cognitive overhead: "Is this flag still active?"
  • Testing complexity grows exponentially (10 flags = 1,024 possible states)

Always define a flag with an expiry and an owner:

{
  "new_checkout": {
    "enabled": true,
    "rollout": 100,
    "owner": "payments-team",
    "created": "2025-01-10",
    "expires": "2025-04-10",
    "ticket": "PAY-1234"
  }
}
Enter fullscreen mode Exit fullscreen mode

Add a CI check that fails the build if a flag is past its expiry date. This forces someone to either extend it deliberately or clean it up.


Avoid Technical Debt from Stale Flags

The flag lifecycle should look like this:

Create → Deploy (hidden) → Gradual rollout → 100% rollout → REMOVE FLAG
Enter fullscreen mode Exit fullscreen mode

Most teams skip the last step. Don't. Schedule flag cleanup as real engineering work, not an afterthought.

Never nest flags:

// 🚫 Don't do this - impossible to reason about
if (flags.isEnabled("feature_a") && flags.isEnabled("feature_b")) { ... }
Enter fullscreen mode Exit fullscreen mode

Each flag should be independently understandable.


Monitor Feature Usage

A flag without metrics is just a guess. Instrument every flag evaluation:

flags.isEnabled("new_checkout", userId);
// Should automatically emit:
// { event: "flag_evaluated", flag: "new_checkout",
//   variant: true, userId, timestamp }
Enter fullscreen mode Exit fullscreen mode

Track per-variant:

  • Error rate - is the new variant causing more errors?
  • Conversion rate - is the new checkout actually performing better?
  • Latency - is the new code path slower?

Set automated alerts. If the error rate spikes within minutes of a flag flip, you want a Slack notification - not a 3am page two hours later.


Cache Flag Evaluations

Never call your flag server on every single operation in a loop:

// 🚫 Bad - 10,000 network calls
for (const item of items) {
  if (await flagServer.isEnabled("feature")) { ... }
}

// ✅ Good - evaluate once, use everywhere in the request
const featureEnabled = flags.isEnabled("feature", userId);
for (const item of items) {
  if (featureEnabled) { ... }
}
Enter fullscreen mode Exit fullscreen mode

Production SDKs like LaunchDarkly and Unleash handle this by downloading the full ruleset locally, so evaluations are in-memory lookups - zero network latency.


Self-Hosted vs. SaaS

Self-Hosted (Unleash, GrowthBook, Flagsmith) SaaS (LaunchDarkly, Split.io)
Cost Free (+ your infra cost) Paid plans (can be expensive)
Data control Full - data never leaves your infra Requires data processing agreements
Reliability You own uptime Provider's SLA
Features Solid for most use cases Best-in-class analytics & targeting

Recommendation:

  • Startups / small teams → Unleash or GrowthBook (open source, easy to self-host)
  • Growth-stage / enterprise → LaunchDarkly if budget allows; worth it for the reliability and analytics

10. Conclusion

Feature flags are one of the most practical tools in modern software engineering. They solve a real problem - the coupling of code deployment and feature release - and unlock capabilities that make teams faster, systems more stable, and incidents less painful.

Here's what to take away:

  • Feature flags decouple deployment from release. Ship code any time. Release features when you're ready.
  • There are five main types: Release, Experiment, Ops/Kill Switch, Permission, and Configuration - each serving a different purpose with a different lifespan.
  • The core implementation is simple: a hash function assigns users to stable buckets; flag configuration controls who's in which bucket.
  • Real-world value comes from canary releases: roll out gradually, monitor metrics, and revert instantly if something goes wrong.
  • The biggest pitfall is stale flags. Treat flag cleanup as real engineering work. Always set expiry dates and owners.
  • Instrument everything. A flag without metrics is a guess. A flag with metrics is a decision.

Start small - add a single flag to your next deployment. Once you see how it changes your relationship with production risk, you'll wonder how you shipped without them.

Thanks for Reading! 🚀

Questions? Thoughts? Something unclear? Or have a topic you'd like me to explore next? I’d love to hear from you in comment section below! 🔥👇

If you enjoyed this article, Like, Share, & Follow for more practical engineering insights from King Technologies

Read Recommendation: just is my go to command now ⚡

💬 Want to learn, build, and grow with a community of developers?
Join the King Technologies Discord — where code meets community! 🚀

Top comments (0)