広告が嫌いですか? 行く 広告なし 今日

Feature Flags Done Right — Three Patterns That Don’t End in a 200-Line if Chain

更新日

A practical guide to env var booleans, config-file flags, and SDK-backed feature flag systems — when each makes sense and where each one breaks.

Feature Flags Done Right — Three Patterns That Don't End in a 200-Line if Chain 1

Here’s what a feature flag system looks like after three engineers and eighteen months:

if (process.env.FEATURE_NEW_CHECKOUT === 'true') {
  if (process.env.FEATURE_NEW_CHECKOUT_V2 === 'true') {
    if (process.env.FEATURE_NEW_CHECKOUT_V2_CANADA === 'true') {
      return renderCheckoutV2Canada();
    }
    if (process.env.ENABLE_TAX_DISPLAY) {
      return renderCheckoutV2WithTax();
    }
    return renderCheckoutV2();
  }
  if (user.isEnterprise && process.env.FEATURE_ENTERPRISE_CHECKOUT) {
    return renderEnterpriseCheckout();
  }
  return renderCheckoutV1New();
}
return renderCheckoutLegacy();

Nobody planned this. It grew. Each env var made sense when it was added — a quick toggle, a temporary workaround, a “we’ll clean this up later” branch. Later never came.

There are three patterns for feature flags, each with a real use case and a real break point. Knowing where each one cracks is how you avoid the above.

Pattern 1: Env var booleans

The simplest form. An environment variable gets read at startup, your code branches on it, done.

// Node.js
const ENABLE_NEW_SEARCH = process.env.ENABLE_NEW_SEARCH === 'true';

function search(query) {
  if (ENABLE_NEW_SEARCH) {
    return newSearchEngine(query);
  }
  return legacySearch(query);
}

This is a fine pattern when you genuinely need a flag that applies uniformly to all users, only changes at deploy time, and will be removed once the feature ships fully. Internal tooling, infrastructure toggles, CI-only features — env var booleans are right here. The mistake is reaching for them when the problem actually needs something else.

どこで破綻するか

No targeting. You can’t show the new feature to 10% of users, or only to beta accounts, or only in Germany. It’s on or off, globally.

No instant kill switch. To turn it off, you change the env var and redeploy. On a good CI/CD pipeline that’s 5 minutes. On a bad one, or on a Friday afternoon, it’s longer. If your new feature just took down checkout, 5 minutes is a long time.

The if chain compounds. Each new variation — V2, Canada-only, enterprise, mobile — needs another env var. The branching logic accumulates. You end up reading eight environment variables before rendering a single page.

Pattern 2: Config-file flags

A step up: instead of reading loose env vars, you maintain a dedicated flags config file that your application loads.

{
  "flags": {
    "new_search": { "enabled": true },
    "new_checkout": { "enabled": false },
    "dark_mode": { "enabled": true, "rollout_percent": 100 },
    "experimental_dashboard": { "enabled": true, "rollout_percent": 10 }
  }
}

This is already better than the env var soup. Flags live in one place, have a predictable shape, and are easy to audit. When you need to show which features are currently enabled, you open one file rather than grepping your entire infrastructure config.

If you need to inspect or format a flag payload like this, the JSON formatter handles it without installing anything.

Config-file flags also let you implement basic rollout logic in code — read the flag’s rollout_percent, hash the user ID, decide whether they’re in or out. Some teams get quite far with this approach before hitting the ceiling.

どこで破綻するか

Still requires a restart or reload. Depending on how your app reads the config, changing the file means a restart or a hot-reload mechanism you have to build yourself. Better than a redeploy, but not instant.

Targeting still needs custom code. The file doesn’t know about users. If you want to target by plan, geography, account age, or user ID range, you write that matching logic yourself — and it lives in your codebase, not in the flag system.

No audit trail. Who changed the flag? When? What was the value before? Unless you’re committing the config file to git and tagging changes, you don’t know. At 2 AM during an incident, you want to know.

The flag count problem. Config files work well up to maybe 20–30 flags. After that, they become their own kind of debt. Flags accumulate, nobody’s sure which ones are still active, and the rollout logic scattered across your codebase slowly diverges from what the file says.

Pattern 3: SDK-backed flag systems

LaunchDarkly, Unleash, Flipt, GrowthBook, Flagsmith — these all operate on the same core model: a server evaluates flag rules and streams decisions to your SDK in real time. Your code checks the SDK; the SDK never calls home for each evaluation.

const LDClient = require('@launchdarkly/node-server-sdk');
const client = LDClient.init(process.env.LD_SDK_KEY);

await client.waitForInitialization();

// In a request handler:
const context = { kind: 'user', key: req.user.id, country: req.user.country, plan: req.user.plan };
const useNewCheckout = await client.variation('new-checkout', context, false);

if (useNewCheckout) {
  return renderNewCheckout(req);
}
return renderLegacyCheckout(req);

The flag key ('new-checkout') is evaluated server-side, cached locally by the SDK, and re-evaluated with fresh rules whenever they change — without your app restarting. The SDK maintains an in-memory cache, so the latency per evaluation is in microseconds, not milliseconds. You’re not making an HTTP call on every page load.

Flag IDs in SDK responses are often UUIDs. If you’re wiring up flag configs via your dashboard’s API, a すべてのケースにカーソルが必要ではありません。オフセットページネーションが適しているのは次のケースです: is handy for creating identifiers during flag setup.

What you actually get

  • Instant kill switch. Flip a flag in the dashboard; it propagates to running instances within a few seconds via streaming. No redeploy, no restart.
  • Percentage rollouts. Roll a feature to 1%, watch the error rate, then move to 10%, 50%, 100% without code changes.
  • User targeting. Show the new checkout only to enterprise accounts, or only to users who signed up after a specific date, or to a list of user IDs you paste in.
  • Audit log. Every flag change is logged with who changed it and when. Table stakes for production incident management.

The real tradeoffs

Commercial pricing. LaunchDarkly’s paid plans start at around $10/seat/month, and the features that matter most — advanced targeting, audit logs, multi-environment support — push you to higher tiers. At 20+ engineers, budget accordingly before committing.

A new dependency. The SDK connects to an external service. What happens when that service is down? Good SDKs fall back to cached flag values and keep serving traffic. Test that failure path before you rely on it in production.

Unleash and Flipt are the credible self-hosted alternatives. Unleash (unleash.io) is actively maintained, used in production at scale, and ships with SDKs for every major language, percentage rollouts, and targeting — plus an admin UI that doesn’t require engineering to operate. The open-source version is not a crippled demo. Flipt leans more toward GitOps-style flag management and is worth considering if your team already manages config as code.

GrowthBook is worth a separate mention if your primary use case is A/B testing rather than feature gates. It integrates with your existing analytics warehouse (BigQuery, Redshift, Mixpanel, Amplitude) rather than asking you to route experiment data to a new platform. Open source and self-hostable, which is a meaningful advantage if your data governance team is strict about where experiment data goes.

Which pattern fits which situation

RequirementEnv varConfig fileSDK-backed
Global on/off at deploy time
Change without restartNeeds custom logic
Instant kill switch
Percentage rolloutNeeds custom logic
User/account targetingNeeds custom logic
Audit trail✗ (unless git)✗ (unless git)
Zero new dependencies
Zero costOnly if self-hosted

The decision is straightforward once you know what you actually need: if the flag only changes at deploy time and applies to everyone, env vars are fine. If you need to change it without a deploy but don’t need per-user targeting, a config file is reasonable. If you need a real-time kill switch, percentage rollouts, or targeting that doesn’t require a code change, use an SDK-backed system.

Avoiding the if chain regardless of which pattern you pick

The 200-line if chain is a flag hygiene problem, not a tooling problem. Even the best SDK won’t save you if you never clean up old flags.

Two rules that work in practice:

  • Set a removal date when the flag is created. Every flag gets a ticket: “remove after 2026-08-01” or “remove after the V2 rollout hits 100%.” If you don’t set a date, the flag lives forever.
  • One flag, one decision. A flag should ask “is this feature on?” — not “is this feature on for enterprise accounts in Canada on mobile?” That second question is a targeting rule, not a flag name. Put the logic in the targeting config, not in the env var.

The code at the top of this article existed because each engineer added one condition. The fix wasn’t better tooling — it was a commit that deleted seven flags and moved the targeting logic to where it belonged.

広告なしで楽しみたいですか? 今すぐ広告なしで

拡張機能をインストールする

お気に入りのブラウザにIOツールを追加して、すぐにアクセスし、検索を高速化します。

に追加 Chrome拡張機能 に追加 エッジ拡張 に追加 Firefox 拡張機能 に追加 Opera 拡張機能

スコアボードが到着しました!

スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!

ニュースコーナー 技術ハイライト付き

参加する

価値ある無料ツールの提供を継続するためにご協力ください

コーヒーを買って