Open chat

← All posts

engineering-practicefeature-flags

Feature flags: controlled rollout without architectural debt

Centralizing flag identifiers, evaluating at boundaries, and retiring flags so experiments do not become permanent branches.

Feature flags unlock progressive delivery, but undisciplined use produces nested conditionals, stale paths, and ambiguity between “experiment” and “configuration.” I treat flags like API surface: few names, explicit ownership, and scheduled retirement.

Single source of keys

export const FeatureFlag = {
  NEW_CHECKOUT_FLOW: "new_checkout_flow",
  BULK_EXPORT_ASYNC: "bulk_export_async",
} as const;

export type FeatureFlagName = (typeof FeatureFlag)[keyof typeof FeatureFlag];

export function isEnabled(
  flags: Record<string, boolean | undefined>,
  name: FeatureFlagName,
): boolean {
  return flags[name] === true;
}

Constants eliminate typo drift between server and client and keep audits grep-friendly.

Evaluate at boundaries

Flags are read at route handlers, job entrypoints, or layout shells—not buried inside shared utilities—so behavior remains traceable.

Lifecycle

Every long-lived flag needs an exit plan: merge, kill, or promote to durable configuration with documentation.

Takeaway: Flags accelerate shipping when they reduce risk; they destroy velocity when they multiply code paths without accountability.

← Back to portfolio