Skip to main content

Feature Flags for Capstone Experimentation

What This Concept Is

A feature flag is a runtime switch that turns a piece of code on or off without redeploying. In a capstone it earns its place in exactly two roles:

  • Release toggle: decouple deploy from release. Code ships dark (flag off); you flip it on after smoke tests pass and soak has elapsed.
  • Ops toggle (kill switch): a runtime switch for a risky feature if it misbehaves in prod, without a redeploy.

It does not earn its place as:

  • a permanent config switch that never moves (that is config, not a flag)
  • a user-segmentation engine masquerading as a feature flag (segmentation belongs in a purpose-built tool)
  • a substitute for proper authorization (don't use a flag to hide an admin-only feature; enforce it in code)
  • an A/B experimentation platform (unless your capstone specifically studies that; real A/B testing is its own module)

Feature flags should be introduced reluctantly and retired aggressively. Martin Fowler's "Feature Toggle" taxonomy separates release toggles (short-lived, tied to a deploy) from permission toggles (may be long-lived, user-scoped); capstones should mostly see the first kind.

Why It Matters Here (In the Capstone)

Flags are deploy-safety tools. A feature behind a flag can ship with code that is wrong without breaking users. A feature without a flag forces you to ship-or-don't. For the capstone, having one or two flags over a semester is evidence of real delivery discipline. Having 40 flags is evidence of process decay -- the codebase becomes a config file with code attached.

Flags also integrate with rollback: a flag flip is a faster rollback than a redeploy for anything flag-gated, and the two mechanisms reinforce each other. Concept 10's trigger list should reference flag flips where applicable.

Concrete Example(s)

Minimal feature flag setup without a SaaS:

// src/flags.ts
import { config } from "./config";

// Retire by: 2026-06-01. Owner: @me.
// Gates /billing/invoices endpoint + new billing page.
export const flags = {
newBillingFlow: config.FEATURE_NEW_BILLING, // boolean from env
} as const;
// in a request handler
if (flags.newBillingFlow) {
return renderNewBilling();
}
return renderLegacyBilling();

To release: change the env var in the secret store from false to true. No deploy needed.

To kill-switch: set it back to false. Observe recovery within the app's cache-TTL window (typically 30-300s).

For a more dynamic flag system (capstone-appropriate when you have 3+ flags), read flags from a small store periodically:

// Re-read flags every 60s from an external store (GCS, S3, or a tiny KV).
setInterval(async () => {
const latest = await fetchFlags();
Object.assign(flags, latest);
}, 60_000);

A retirement scanner, run as a CI step:

# Find any flag whose retirement date has passed.
grep -RE "Retire by: [0-9]{4}-[0-9]{2}-[0-9]{2}" src/ | \
awk -F'Retire by: ' '{ print $2 }' | \
while read -r d _; do
if [[ "$d" < "$(date -I)" ]]; then
echo "EXPIRED: $d"; exit 1
fi
done

Common Confusion / Misconceptions

  • "I'll make everything a flag, so I can change anything in prod." That is how you end up with a config file that is the real source of truth instead of the code. The capstone reviewer sees a codebase full of if (flag) {...} branches and cannot tell what the app actually does today. Each live flag is a conditional branch to reason about.
  • "A flag replaces a rollback." Sometimes -- but only for features you owned behind the flag from day one. A flag cannot save you from a bug in code paths that were never flag-gated, or from a bad migration, or from a bad Terraform apply.
  • "Flag flips are free." They are not. A flag flip is a production change: it alters runtime behavior for real users. Announce the flip, run smoke, and be ready to flip back. Teams that treat flag flips as "free" accumulate incidents that look mysterious in deploy logs because no deploy happened.
  • "Keep the dead branch of a retired flag, just in case." Don't. Dead code rots. When a flag retires, delete both branches and the flag itself in one commit. "Just in case" is how codebases grow 20% dead over 18 months.
  • "Defaults don't matter." They matter most of all. If the flag store is unreachable, what does the app do? Fail-closed (feature off) is usually safer for new features; fail-open may be required for a kill-switched feature you cannot disable.

How To Use It (In Your Capstone)

  1. Every flag gets a retirement date and an owner, enforced by a lint rule or a periodic grep in CI.
  2. Default state in dev should match the long-term intent (true if it will stay on; false if it is a kill switch).
  3. Keep the total count of live flags below 5 for a capstone. If it is growing, you are using flags as architecture -- stop and refactor.
  4. When a flag retires, delete both branches and the flag declaration in a single commit. No commented-out dead branch.
  5. Every flag flip is a deploy event: record it in CHANGELOG.md with ### Why and ### Risk sections, and run smoke after the flip.
  6. Decide each flag's fail-behavior on store unavailability: fail-closed (off) by default; explicit opt-in for fail-open.
  7. Do not use flags for authorization. Enforce security-critical booleans in code; a leaked flag value must not bypass a permission check.

Flag Flip Is a Deploy Event

Even though flipping a flag does not trigger a new build, it is a production change and should be treated as one:

  • announce the flip in CHANGELOG.md with the same ### Why and ### Risk sections
  • run the smoke test after the flip, not just after the deploy that introduced the flag
  • be ready to flip it back if smoke fails -- the flag is its own rollback
  • note the flag flip in library/raw/rollback-rehearsals/ if it is non-trivial

The flag flip was the deploy. Treat it that way.

See also (integrative)

Check Yourself

  1. How many flags are live in your codebase right now, and which are closest to their retirement date?
  2. What is blocking retirement of the oldest live flag, in one sentence?
  3. If the flag store is unreachable, what is the app's default behavior per flag?
  4. Was your last flag flip recorded in CHANGELOG.md with ### Why and ### Risk?
  5. Which of your flags could be safely replaced by static config (remove the flag, remove the dead branch)?
  6. Do any of your flags encode authorization decisions? Why?

Mini Drill or Application (Capstone-scoped)

  1. One ship behind a flag. Pick one feature you will ship in the next two weeks. Gate it behind a flag. Ship the code with the flag off. After smoke passes and a 24h soak, flip the flag on. Observe for one day. Retire the flag the following sprint.
  2. Retirement audit. List every flag in the repo with its date and owner. Any missing date or owner gets filled in or the flag gets deleted today.
  3. Flip-as-deploy changelog entry. For your next flag flip, write the CHANGELOG.md entry before flipping. If you cannot articulate ### Why and ### Risk, the flip is not ready.

Source Backbone

Capstone deployment applies cloud, delivery, and operations material. These books are the source backbone for the delivery decisions.