Feature Flags in Production: Best Practices and Common Pitfalls

Supa DeveloperSupa Developer
··5 min read

Feature flags give you the ability to separate deployment from release - ship code anytime, then turn features on for real users when you're ready. But in production, a few habits separate teams that use them well from those that end up with a codebase full of dead toggles and invisible technical debt.

Here are the practices that matter most, and the traps that catch teams off guard.

Keep flags short-lived

Flags are not meant to live forever. Every flag you create should have a planned end state - either fully rolled out and removed, or permanently off and deleted.

Set a reminder when you create a flag. The moment a rollout completes, remove the flag from the code and the dashboard. A flag that's been true globally for four months is dead code wrapped in an if statement. It adds cognitive load for every developer who reads that code and creates the risk that someone accidentally disables it one day.

For long-lived flags (premium feature gates, kill switches), treat them differently from rollout flags. Document their purpose clearly and review them in regular engineering audits.

Name flags to communicate intent

A flag named new-admin-dashboard-flag tells you nothing useful at 2am during an incident. A flag named admin.product-dashboard.v2 tells you exactly what it controls, where it lives, and why it exists.

A consistent naming scheme also makes dashboard searches useful. Consider a pattern like {area}.{feature}.{variant}:

  • checkout.payment-modal.v2
  • api.rate-limiting.aggressive
  • mobile.offline-mode.beta

The name should answer: what does this control, and is it safe to disable right now?

Treat flags as part of your release logic

A flag isn't just a config value - it's a branch in your codebase that ships to production. Treat it accordingly:

  • Review flag additions in pull requests. The code path being gated and the fallback behaviour should both be visible.
  • Document each flag's purpose and who owns cleanup. A flag without an owner becomes everyone's problem.
  • Test both paths. CI should run tests against flag-on and flag-off states for any code with meaningful branching.

Log which flags are active per request

When something breaks in production, you want to know which flags were active for the affected users. Without this, tracing a bug to a specific flag state is guesswork.

Log the resolved flag values alongside your normal request logging:

logger.info('request processed', {
  path: req.path,
  userId: req.user.id,
  flags: {
    'new-checkout-flow': features['new-checkout-flow'],
    'payment-modal-v2': features['payment-modal-v2'],
  },
})

At minimum, structured logs or traces with active flag states cut debugging time significantly during incidents.

Roll out gradually - always

Even when you're confident in a feature, start at a small percentage. The production environment has a way of surfacing edge cases that staging never does: unusual data, unexpected traffic patterns, third-party dependencies behaving differently under load.

A practical rollout ladder:

StageAudienceWhat to watch
1%Internal usersErrors, crashes, obvious regressions
5%Early adoptersPerformance metrics, error rates
20%Broader usersBusiness metrics, conversion
50%Half of trafficFull statistical confidence
100%EveryoneClean up the flag

Each step should have a deliberate pause - hours or days depending on traffic volume - before moving forward.

Pair every rollout with monitoring

A flag without observability is a guess. Before you enable a flag in production, make sure you can answer:

  • What error rate is acceptable? What would trigger a rollback?
  • Are there latency implications? Are you watching p95 and p99, not just averages?
  • Are there business metrics to watch - conversion, retention, revenue?

Set up dashboards and alerts before rolling out, not after. The window between 5% and 100% is when most production issues are caught; make sure you'll see them.

Common pitfalls

Forgetting to remove flags after rollout. The single most common form of flag-related technical debt. A codebase with 50 stale flags is harder to reason about than one with none. Schedule cleanup as part of completing a rollout - not as a future task.

Too many flags at once. If every PR adds a new flag, complexity compounds fast. More flags mean more possible combinations of flag states, which makes debugging harder and testing more expensive. Use flags strategically, for the features that genuinely benefit from controlled rollout.

Enabling flags without monitoring. Turning a flag to 100% because "it's been fine at 20%" without watching metrics is how production incidents happen. Always watch for a period after increasing rollout percentage.

Managing flags through config files. Environment variables and config files make flag management painful at scale - there's no audit trail, no way to change values without a deploy, and no visibility into who changed what. A dedicated flag platform like Supaship gives you a dashboard, audit logs, and instant changes without touching code.

Flag dependencies. A flag that only works correctly when another flag is also on creates a hidden coupling that's easy to break. If you find yourself writing if (flagA && flagB), that's a signal to rethink the design.

The flag lifecycle

Every flag should pass through these stages cleanly:

Create → Deploy (off) → Roll out gradually → Reach 100% → Remove flag + old code path

The remove step is as important as the create step. Add it to your definition of done: a feature isn't "done" until the flag is cleaned up.


Ready for production-grade feature flags? Try Supaship - gradual rollouts, targeting, and audit logs built for production. Free forever up to 1M events/month. Pro plan is $30/month for your entire workspace.


Feedback

Got thoughts on this?

We're constantly learning how developers actually use these tools. Ideas, use cases, integration requests — every bit of feedback makes the platform better for everyone.

Thanks for being part of the journey — Supaship