Skip to main content

How to Build Feature Flags in Redis

Feature flags let you enable or disable functionality without deploying new code. They decouple releases from deployments: you can ship code to production with a feature turned off, then flip it on for specific users, a percentage of traffic, or everyone at once. This enables gradual rollouts, A/B testing, kill switches for problematic features, and beta access for specific customers.

Redis is a natural fit for feature flags because flag checks happen on nearly every request and need to be fast. A hash lookup in Redis takes microseconds, far faster than querying a relational database or making an API call to a feature flag service. This guide covers three approaches: simple on/off flags for global toggles, percentage-based rollouts for gradual releases, and user-specific targeting for enabling features for specific users or groups.

Which Redis data types we will use

String is used in two ways in this implementation:

  1. As boolean flags for simple on/off toggles. One key per feature, with a value of "1" for enabled or "0" for disabled. The GET command checks the flag, SET changes it, and MGET checks multiple flags at once.
  2. As percentage values for gradual rollouts. Store a number 0-100 representing the rollout percentage. Hash the user ID to a number 0-99 and compare against the percentage to determine access.

Set tracks which users have explicit access to a feature. Beta testers, internal employees, or customers who requested early access can be added to a set. The SISMEMBER command checks membership in O(1) time, letting you give specific users access regardless of percentage rollouts.

Hash stores rich flag metadata when needed. Combine the percentage, update timestamp, and admin who changed it in a single hash per feature. This keeps related data together and reduces key proliferation.

The combination of these data types gives you layered control: check user targeting first (Set), then percentage rollout (String), then global flag (String). Each layer is fast and the logic is straightforward.

Simple on/off flags for global toggles

The simplest feature flag is a key that exists or does not. If the key exists and has a truthy value, the feature is on. If it is missing or falsy, the feature is off. Redis strings work perfectly for this. One GET per flag check, one SET to change it.

This approach is ideal for global kill switches and features that should be fully on or fully off. When your payment provider has an outage, set a flag to disable checkout. When a new feature is ready for everyone, flip it on. The flag applies to all users equally.

The downside is the lack of granularity. You cannot roll out to 10% of users or enable a feature only for beta testers. For those use cases, you need the approaches below. Boolean flags also require a Redis round trip per check, though you can batch multiple flags with MGET or cache them briefly in your application.

# Enable a feature globally
SET feature:dark-mode 1
> OK

# Check if feature is enabled
GET feature:dark-mode
> "1" (enabled)

# Disable the feature
SET feature:dark-mode 0
> OK

# Or delete to use "missing means off" semantics
DEL feature:dark-mode
> 1

# Check multiple flags at once
MGET feature:dark-mode feature:new-checkout feature:beta-dashboard
> ["1", "0", nil]

Percentage-based rollouts for gradual releases

Percentage rollouts enable a feature for a fraction of users, letting you gradually increase exposure while monitoring for problems. The key insight is to use consistent hashing: hash the user ID to a number between 0 and 99, then check if that number falls below the rollout percentage. The same user always gets the same result, so their experience is consistent across requests.

Store the rollout percentage in Redis. When checking the flag, hash the user ID and compare against the stored percentage. To increase the rollout, just update the percentage. Users below the threshold stay in, and new users get added.

This approach works well for gradual rollouts where you want to start at 5%, watch metrics, bump to 25%, then 50%, then 100%. It also enables simple A/B testing by checking whether a user falls into the enabled group. The limitation is that you cannot target specific users. If a VIP customer hashes to 85 and you are at 50% rollout, they will not see the feature until you increase the percentage.

# Set rollout to 25% of users
SET feature:new-checkout:percent 25
> OK

# Application hashes user ID to 0-99
# hash("user123") % 100 = 42
# 42 >= 25, so feature is OFF for this user

# hash("user456") % 100 = 18
# 18 < 25, so feature is ON for this user

# Increase rollout to 50%
SET feature:new-checkout:percent 50
> OK
# Now user123 (hash 42) is also included

# Store additional metadata in a hash
HSET feature:new-checkout percent 50 updated_at 1699900000 updated_by admin@example.com
> 3

User-specific targeting for beta access

Sometimes you need a feature enabled for specific users regardless of percentage rollouts. Beta testers, internal employees, customers who requested early access, or users where you are debugging an issue. A Redis set stores which users have explicit access to a feature. Check set membership with SISMEMBER, which is O(1) and fast.

The typical pattern is to check user targeting first, then fall back to percentage rollout, then to global flag. This layered approach gives you fine-grained control: you can enable a feature for your QA team while it is at 0% rollout, then gradually roll out to real users while keeping beta testers in the enabled group.

User targeting adds operational complexity. Someone needs to manage who is in each set, and the sets can grow large for popular beta programs. Consider adding expiration or periodic cleanup for targeting sets, or use a hash to store additional metadata like when the user was added and by whom.

# Add users to a feature's allowlist
SADD feature:new-checkout:users user123 user456 user789
> 3

# Check if a specific user has access
SISMEMBER feature:new-checkout:users user123
> 1 (user has access)

SISMEMBER feature:new-checkout:users user999
> 0 (user does not have access)

# Remove a user from the allowlist
SREM feature:new-checkout:users user456
> 1

# See all users with access (careful with large sets)
SMEMBERS feature:new-checkout:users
> ["user123", "user789"]

# Count users in the allowlist
SCARD feature:new-checkout:users
> 2

Choosing an approach

Use boolean flags for global on/off switches where all users should have the same experience. Kill switches, maintenance mode, and fully-launched features all fit this pattern.

Use percentage rollouts when releasing new features gradually. Start at a low percentage, monitor error rates and performance, and increase as confidence grows. This catches problems before they affect all users.

Use user targeting when specific users need access regardless of rollout status. Beta testers, internal users, and customers with contractual access all benefit from explicit targeting.

In practice, most feature flag systems combine all three. Check user targeting first for explicit overrides, then percentage rollout for gradual releases, then fall back to a global default. This layered approach gives you the flexibility to handle any rollout strategy while keeping flag checks fast and simple.