When Deploying Code Doesn't Mean Releasing Features

Your team just finished a big refactor of the search algorithm. The code is tested, reviewed, and deployed to production. But here's the thing: nobody is using it yet. The new algorithm sits on the server, compiled and ready, but every user still gets results from the old one. That's intentional.

This scenario might sound strange if you're used to deployments where every change immediately affects users. But it's actually a powerful pattern that separates two things most teams treat as the same: deploying code and releasing features.

The Problem Deployment Alone Can't Solve

Canary deployments and staged rollouts solve one problem well: they control how much traffic reaches a new version of your application. You send 5% of users to the new version, watch for errors, then ramp up to 100%. That works when you want to roll out an entire version gradually.

But what if you want to activate a specific feature for only certain users, without deploying different code versions? Maybe you want internal testers to see the new search results while everyone else stays on the old algorithm. Maybe you want to enable a feature for users in one region first. Maybe you want to test a new checkout flow with 10% of users, but only if they're on the mobile app.

Canary and staged rollout don't solve this. They operate at the version level, not the feature level. You need something that lives inside your code and makes decisions per request, per user, per session.

Feature Flags: A Control Layer Inside Your Code

Feature flags are conditional checks in your code that determine whether a feature should be active for a given user. The key insight is that the condition is not based on which version of the code is running. It's based on configuration that can change at any time, without redeploying.

Here's what that looks like in practice. Your team finishes the new search algorithm. The code is deployed to production as part of the latest version. But inside the search function, there's a check:

Here's a JavaScript example of that same pattern:

const featureFlags = require('./feature-flags');

function search(query, user) {
  if (featureFlags.isEnabled('new-search', user)) {
    return newSearchAlgorithm(query);
  } else {
    return oldSearchAlgorithm(query);
  }
}

// The flag is off by default, so all users hit the old path.
// When ready, flip the flag on for testers via the dashboard.
if feature_flag.is_active("new_search_algorithm", user):
    return new_search_algorithm(query)
else:
    return old_search_algorithm(query)

The flag is off by default. Every user still gets the old algorithm, even though the new code is sitting on the same server. When you're ready, you flip the flag on for internal testers. They start seeing new results. You monitor their behavior. If something looks wrong, you flip the flag back off. No rollback. No redeploy. Just a configuration change that takes effect in seconds.

Why Separate Deploy from Release?

The first benefit is psychological and practical at the same time: deploying stops being a high-stakes event. When every deploy activates all changes immediately, you batch up work, test heavily, and hold your breath during release windows. When deploy and release are separate, you can deploy daily or even multiple times a day, knowing that new code sits dormant until you explicitly turn it on.

The second benefit is granular control. Feature flags aren't just on or off for everyone. You can define targeting rules:

  • Active for users with specific IDs
  • Active for users in certain regions
  • Active for internal testers or beta users
  • Active for a percentage of total users

These rules can stack. You might start with 5% of users, observe for a day, increase to 25%, then to 50%, then to 100%. Every change happens through a dashboard or API call. No code changes. No deployments.

The third benefit is the kill switch. When a feature causes problems after being activated, you can disable it from a single place. This is much faster than waiting for a rollback pipeline to complete. In seconds, the problematic feature is off, and users fall back to the old behavior. For production incidents, those seconds matter.

The Hidden Cost: Flag Debt

Feature flags add complexity to your codebase. Every flag introduces a conditional branch that must be maintained. If your team creates flags and never removes them, your code fills up with dead conditions that nobody remembers.

A common scenario: a feature flag is created for a new checkout flow. The flow rolls out successfully to 100% of users. The old checkout code is still there, guarded by a flag that's always off. Months later, a new developer sees the flag and wonders if it's still relevant. Nobody knows. The flag stays because removing it feels risky.

This is flag debt, and it's the main operational cost of feature flags. Flags need a lifecycle: create, activate, monitor, clean up. When a feature is fully rolled out to all users, the old code path and the flag should be removed. The flag served its purpose. Keeping it around only adds cognitive load and increases the surface area for bugs.

Choosing Your Flag Management Approach

For small teams or simple use cases, feature flags can start as environment variables or configuration files that the application reads at startup. This works but has a limitation: changing a flag requires a restart or a config reload.

For larger teams or more complex targeting rules, dedicated flag management platforms like LaunchDarkly, Split, or Flagr provide real-time flag evaluation, user targeting, and audit logs. These platforms let you change flags from a dashboard and see the effect immediately across all running instances.

The right choice depends on your scale. Start simple. Add complexity only when you need it.

Practical Checklist for Using Feature Flags

  • Each flag has a clear owner and purpose documented somewhere visible
  • Flags have a planned removal date or trigger condition (e.g., "remove when rollout reaches 100%")
  • Old code paths are removed when the flag is no longer needed
  • Flag changes are logged with who changed what and when
  • Critical flags have a monitoring dashboard showing who is seeing which variant
  • The team has a regular cleanup cadence for stale flags

The Takeaway

Feature flags give you a control layer that operates independently from your deployment pipeline. Canary and staged rollout control how much traffic reaches a new version. Feature flags control which users see which features within the same version. Use them together, and you get fine-grained control over how changes reach your users. Just remember that every flag you create is a piece of technical debt that needs to be cleaned up when its job is done.