Feature Flags Architecture for SaaS

Feature flags are one of the highest-leverage architectural decisions a SaaS team can make. Done well, they decouple deployment from release, let you ship dark features to production before users see them, and give you a kill switch when something goes wrong. Done poorly, they create a tangled web of conditional logic that nobody wants to touch and flags that live forever because everyone forgot what they do.

This guide covers how to design a feature flags system that stays manageable as your product and team grow — from the first flag you ship to a multi-tenant system serving thousands of accounts.

The Four Types of Feature Flags

Not all feature flags serve the same purpose. Treating them all the same way leads to systems that are hard to reason about. The four categories below have different lifecycles, different owners, and different architectural requirements.

Release Flags

Release flags wrap new features that are complete but not yet publicly available. They let you merge code continuously without exposing incomplete or unvetted features to users. Release flags are short-lived — they should be removed within days to weeks of the feature going fully live.

Pattern: if (flags.isEnabled('new-billing-ui', user)) { showNewBillingUI() } else { showLegacyBillingUI() }

Experiment Flags

Experiment flags power A/B tests and multivariate experiments. They split traffic between variants and need to be consistent — the same user should always see the same variant. Experiment flags require analytics integration to measure outcomes. They live as long as the experiment runs, then get cleaned up with the losing variant.

Ops Flags (Kill Switches)

Ops flags are circuit breakers. They let you disable a feature, integration, or expensive code path without deploying new code. Every major feature that touches a third-party API or does heavy computation should have an ops flag. These flags tend to live permanently — as long as the feature exists, the kill switch should too.

Permission Flags

Permission flags gate features by plan, account, or role. They are effectively your entitlement system. Unlike release flags, permission flags are permanent — they encode your product's business logic about who gets what. In B2B SaaS, these often map to subscription tiers or explicit feature grants to specific accounts.

Flag Evaluation Architecture

How your application evaluates flags has significant implications for performance, consistency, and reliability. There are three main approaches.

Server-Side Evaluation (Recommended Default)

Your server requests flag evaluations from a flag service, which returns resolved values for the current user or account context. The flag service handles targeting rules, percentage rollouts, and overrides. Your application code only sees true/false (or variant) values.

Advantages: Targeting rules are not exposed to clients. Flag changes take effect without client deployments. You can target server-side features that clients never see.

Latency consideration: Synchronous flag evaluation on every request adds latency. The solution is local caching with a short TTL (typically 30-60 seconds) and background refresh. Most flag SDKs handle this automatically.

Client-Side Evaluation

Flag configurations are pushed to the client, which evaluates them locally. This eliminates network round-trips for flag evaluation but requires shipping targeting rules to the client.

Use when: You need instant flag evaluation without latency, your targeting rules can safely be exposed to clients, and you are doing front-end experiments where the client controls what renders.

Bootstrap Pattern

For applications where initial render matters (SPAs, mobile apps), bootstrap your flag state at session start and pass it through the application. Avoid per-component flag fetches that cause layout shifts or loading states.

Implementation: Evaluate all flags for the current user on the first API call (or during authentication). Return the full flag state as part of the session response. Cache client-side for the session duration.

Targeting Rules

The power of feature flags comes from their targeting capabilities. Here are the targeting primitives you need and how to implement them.

User-Level Targeting

Target specific user IDs, email addresses, or email domains. Useful for internal testing (target your team's email domain), beta programs (target specific user IDs), and debugging (target a specific user experiencing an issue).

Implementation: Maintain a list of user IDs or patterns per flag. Evaluate before percentage rollouts — explicit targeting takes precedence.

Account-Level Targeting

For B2B SaaS, account-level targeting is as important as user-level targeting. Target by account ID, account tier, account age, or any account property you track. This is how you give early access to a specific customer or enable a premium feature for all users in an enterprise account.

Percentage Rollouts

Gradually roll out a feature to increasing percentages of users or accounts. The critical requirement: rollouts must be sticky. The same user should consistently get the same variant, even across sessions. Implement stickiness by hashing the user ID (or account ID) with the flag name as a seed.

Formula: hash(userId + flagName) % 100 < rolloutPercentage

Segment Targeting

Segments are named groups of users or accounts that match a set of rules. Instead of repeating complex targeting rules on every flag, define the segment once and reference it. Useful for "power users", "accounts on the growth plan", "accounts signed up in the last 30 days".

Flag Lifecycle and Avoiding Flag Debt

Flag debt accumulates when flags are never removed after they are no longer needed. A codebase with 200 flags where 150 are permanently enabled and never evaluated is a maintenance liability — dead code paths, test complexity, and cognitive overhead for every developer who reads the flagged code.

Categorize Flags by Intended Lifetime

Flag Cleanup Protocol

For temporary flags: when the rollout hits 100% and has been stable for two weeks, open a ticket to remove the flag and the associated conditional code. Do not leave flags at 100% indefinitely — at that point they are just dead code with extra steps.

Create a flag registry with owner, creation date, intended removal date, and current status. Review the registry monthly. Any flag past its removal date that is still at 100% is a candidate for cleanup.

Naming Conventions

Consistent naming makes flags scannable and their purpose clear. Recommended pattern: [team]-[feature]-[stage]. Examples: billing-usage-dashboard-rollout, eng-new-query-engine-experiment, ops-stripe-integration-kill-switch. Avoid generic names like new-feature-v2 that tell you nothing.

Infrastructure: Build vs. Buy

At each stage of growth, the right infrastructure choice changes.

Pre-Seed / Seed: Start Simple

At the earliest stage, a simple database-backed flag system is sufficient. Store flags in a database table with columns: flag name, enabled boolean, targeting rules (JSON). Wrap evaluation in a service class. Add caching. This takes half a day to build and handles most early-stage needs.

The danger of starting with a sophisticated third-party service too early: you add operational complexity and cost before you understand what you actually need from a flag system.

Growth: Move to a Dedicated Service

Once you have more than 5-10 active flags, multiple teams shipping simultaneously, or the need for complex targeting, consider a dedicated flag service. Options at this stage:

Scale: Multi-Tenant Architecture

For B2B SaaS serving many accounts, your flag system needs to handle account-level targeting efficiently. Key requirements: evaluate flags with account context (not just user context), support per-account overrides, and track flag exposure at the account level for billing or compliance purposes.

Frequently Asked Questions