Back to blog

Feature Flags vs Environment Variables: When to Use Each

·10 min read

Environment variables and feature flags look like they do the same job. Both let you change how your application behaves without hardcoding a value. Both are read at some point and branched on. If you've ever set FEATURE_NEW_CHECKOUT=true to turn something on, you've used an environment variable as a feature flag.

So why do dedicated flag services exist? The difference isn't what the value is. It's when and how you can change it. That single distinction decides which tool is right for a given job, and it's the thing most "just use an env var" advice glosses over.

This post covers what environment variables are genuinely good at, where they break down as feature toggles, what flags add, and, honestly, when you should still reach for an env var instead.

What Environment Variables Are Good At

Environment variables are configuration read from the process environment, usually at startup. They're the standard way to inject per-environment values into an application without baking them into the code.

import os
DATABASE_URL = os.environ["DATABASE_URL"]
STRIPE_API_KEY = os.environ["STRIPE_API_KEY"]
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")

They're the right tool for a whole class of configuration:

  • Secrets and credentials: API keys, database passwords, signing keys. These must never live in your codebase or a public config.
  • Per-environment infrastructure: the database URL, the cache host, the third-party API endpoint. Different in dev, staging, and production, but stable within each.
  • Values that only change at deploy time anyway: log level, worker counts, the region you're running in.

The defining trait of all of these: they're read once when the process starts, and they change on the same schedule as your deploys. That's not a limitation for this kind of config. It's exactly the right lifecycle.

Where Environment Variables Break Down as Feature Toggles

The trouble starts when you use that same mechanism to control a feature. Say you gate a new checkout flow behind an env var:

if os.environ.get("FEATURE_NEW_CHECKOUT") == "true":
show_new_checkout()
else:
show_old_checkout()

This works, right up until you want to actually operate it. Four problems show up fast:

You Can't Change It at Runtime

An environment variable is read when the process starts. To flip it, you change the value and restart or redeploy the application. So the moment your new checkout misbehaves in production, your "toggle" requires a full deploy cycle to turn off. That's the exact opposite of what you want when something is on fire.

It's All or Nothing

An env var has one value for the whole process. You can't show the new checkout to 5% of users, or only to your own team, or only to enterprise accounts. It's on for everyone or off for everyone. There's no room for a gradual rollout or targeting.

No Gradual Rollout

Because it's all or nothing, the safe release pattern of "ship to 10%, watch the error rate, then 25%, then everyone" is off the table. Every release with an env-var toggle is a big-bang release. You find out it's broken from all of your users at once, not from a controlled slice.

No Audit Trail

When someone flips an env var and something breaks at 2 AM, there's no record of who changed what, or what the value was before. The change is buried in a deploy config or a platform dashboard, not in a log built to answer "what just changed?"

What Feature Flags Add

A feature flag is the same idea (a value your code branches on) but decoupled from your deploy lifecycle and enriched with targeting. The same checkout gate looks like this:

if client.enabled("new_checkout", user={"user_id": user.id}):
show_new_checkout()
else:
show_old_checkout()

Because a flag is evaluated on every call, you get:

  • Runtime changes: flip it in a dashboard and it takes effect in seconds. No deploy, no restart.
  • Targeting: enable it for a specific user, an internal team, or an attribute like plan = enterprise.
  • Gradual rollout: serve it to 10% of users, then 25%, then everyone. With consistent hashing, the same user always gets the same answer, so the experience is stable as you ramp.
  • A kill switch: when the feature misbehaves, turn it off from a dashboard instead of shipping a hotfix.
  • An audit trail: who changed the flag, when, and from what to what.

Env Vars vs Config Files vs Feature Flags

Config files (a settings.yaml, a .env committed to the repo) sit in between. They're more structured than raw env vars and easier to review in a pull request, but they share the fundamental constraint: you change them at deploy time, and they apply to the whole process. Here's the full picture:

Environment variablesConfig filesFeature flags
Changes at runtime?No, needs a restartNo, needs a redeployYes, in seconds
Per-user targeting?NoNoYes
Gradual rollout?NoNoYes
Audit trail?Not by defaultGit historyYes, built in
Who changes it?Whoever can deployWhoever can mergeAnyone with dashboard access
Good for secrets?YesNo, don't commit secretsNo, never

When to Still Use Environment Variables

Flags are not a replacement for environment variables. They solve a different problem, and reaching for a flag service where an env var belongs just adds a moving part. Keep using environment variables for:

  • Secrets. Always. This one is not a preference, it's a rule. Most flag services (Switchbox included) deliver flag configs from a public CDN so evaluation is fast and can't be taken down by their servers. That means a flag value is world-readable. Never put an API key, a password, or any secret in a feature flag. Secrets live in environment variables (or a secrets manager), full stop.
  • Per-environment infrastructure config: the database URL, the queue host, the CDN base URL. These are environment identity, not features you toggle.
  • Values that genuinely only change at deploy time: if it changes on the same schedule as your code and applies to the whole process, an env var is simpler and has one less dependency.

A good heuristic: if you'd want to change it without a deploy, or for some users but not others, it's a flag. If it's the same for the whole process and changes when you ship, it's an environment variable.

Migrating a Config Toggle to a Flag

If you already have a feature behind an env var and you're feeling the pain of redeploying to flip it, the migration is small. Replace the environment read with a flag check, and keep the safe default so nothing breaks if the flag service is unreachable.

# Before: change the value, then redeploy to flip it
if os.environ.get("FEATURE_NEW_CHECKOUT") == "true":
show_new_checkout()
# After: flip it from a dashboard, target users, roll out gradually
if client.enabled("new_checkout", user={"user_id": user.id}, default=False):
show_new_checkout()

Note the default=False. A well-behaved SDK evaluates flags locally against a cached config, so a flag check never blocks a request, and if the config can't be fetched at all, you fall back to the safe default (the old behavior). That's the same conservative posture you had with the env var, plus everything a flag gives you.

Getting Started

If you have a toggle that you keep wishing you could flip without a deploy, that's the one to move first. You don't need a big migration, just a single flag and an SDK.

Switchbox is a feature flag service that serves configurations from a CDN with a zero-dependency Python SDK:

pip install switchbox-flags
from switchbox import Switchbox
client = Switchbox(sdk_key="your-sdk-key-from-dashboard")
if client.enabled("new_checkout", user={"user_id": "42"}):
show_new_checkout()

The SDK fetches a static JSON file from a CDN, evaluates rules locally in under a millisecond, and polls for updates every 30 seconds. There's no server in the read path, so a flag check is as reliable as fetching a static file, and toggling a flag reaches your users without a deploy.

Environment variables aren't going anywhere. Keep them for secrets and infrastructure. But the next time you find yourself redeploying just to turn something on, that's your signal it wanted to be a flag all along. If you want the deeper background, start with our guide on what feature flags are and how they work.