<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Domenico Giordano</title>
    <description>The latest articles on Forem by Domenico Giordano (@domenico_giordano_e441224).</description>
    <link>https://forem.com/domenico_giordano_e441224</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2672712%2F2bd6e425-3412-4f19-8bf3-d48f0bd43609.jpg</url>
      <title>Forem: Domenico Giordano</title>
      <link>https://forem.com/domenico_giordano_e441224</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/domenico_giordano_e441224"/>
    <language>en</language>
    <item>
      <title>How to Add Feature Flags to React in 5 Minutes</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Fri, 24 Apr 2026 21:27:30 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/how-to-add-feature-flags-to-react-in-5-minutes-mck</link>
      <guid>https://forem.com/domenico_giordano_e441224/how-to-add-feature-flags-to-react-in-5-minutes-mck</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This was originally published on &lt;a href="https://rollgate.io/blog/feature-flags-react" rel="noopener noreferrer"&gt;rollgate.io/blog/feature-flags-react&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Feature Flags in React?
&lt;/h2&gt;

&lt;p&gt;Every React application reaches a point where you need to control what users see — without redeploying. Maybe you want to test a new checkout flow with 10% of users. Maybe you need a kill switch for a feature that's causing performance issues. Maybe your PM wants to launch a feature on Tuesday at 9am, not whenever the deploy pipeline finishes.&lt;/p&gt;

&lt;p&gt;Feature flags in React solve all of these problems. They let you wrap components, routes, or entire features behind a toggle that you control from a dashboard — no code changes, no redeployment, no downtime.&lt;/p&gt;

&lt;p&gt;If you've ever used an environment variable like &lt;code&gt;NEXT_PUBLIC_ENABLE_NEW_UI&lt;/code&gt; and redeployed just to flip it, you already understand the concept. Feature flags are the same idea, but dynamic: you change the value in a dashboard, and every connected client picks it up in real time.&lt;/p&gt;

&lt;p&gt;In this guide, we'll go from zero to production-ready React feature flags in 5 minutes. We'll start with the simplest possible approach (environment variables), show its limitations, and then implement a proper solution with &lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt; that supports gradual rollouts, A/B testing, and user targeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Simplest Approach: Environment Variables
&lt;/h2&gt;

&lt;p&gt;Before reaching for a library, many teams start with environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// feature-flags.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;newPricing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_NEW_PRICING&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;darkMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_DARK_MODE&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PricingPage.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./feature-flags&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PricingPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newPricing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewPricingTable&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LegacyPricingTable&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for simple cases, but it has serious limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requires a redeploy&lt;/strong&gt; to change any flag value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No gradual rollouts&lt;/strong&gt; — it's all-or-nothing for every user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No user targeting&lt;/strong&gt; — you can't enable a feature for beta testers only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No instant kill switch&lt;/strong&gt; — if a feature breaks, you need to redeploy to disable it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No audit trail&lt;/strong&gt; — who changed what, when?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anything beyond the simplest use case, you need a feature flag library. Let's set one up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start: React Feature Flags in 5 Minutes
&lt;/h2&gt;

&lt;p&gt;We'll use &lt;code&gt;@rollgate/sdk-react&lt;/code&gt;, which gives you a React-native API with hooks and providers. The entire setup takes three steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install the SDK
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @rollgate/sdk-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Wrap Your App with RollgateProvider
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.tsx (or _app.tsx, layout.tsx — wherever your root component lives)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;
      &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_API_KEY"&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;RollgateProvider&lt;/code&gt; connects to Rollgate's edge API, fetches your flag configuration, and makes it available to every component in the tree via React context. It also opens an SSE connection for real-time updates — when you toggle a flag in the dashboard, every connected client picks it up within seconds.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;user&lt;/code&gt; object is optional but recommended. It enables targeting rules (e.g., "enable this flag for users on the Pro plan") and consistent rollout assignments (the same user always sees the same variant).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Use the useFlag Hook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CheckoutPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewCheckout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;showNewCheckout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewCheckout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LegacyCheckout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Three steps, five minutes, and you have feature flags in React. The &lt;code&gt;useFlag&lt;/code&gt; hook returns the flag's current value and re-renders the component when it changes.&lt;/p&gt;

&lt;p&gt;Now let's go deeper.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useFlag Hook
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;useFlag&lt;/code&gt; hook is the primary way to read feature flag values in React components. It accepts two arguments: the flag key and a default value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flag-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Boolean Flags
&lt;/h3&gt;

&lt;p&gt;The most common type. Returns &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark-mode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  String Flags
&lt;/h3&gt;

&lt;p&gt;Useful for A/B testing and multivariate experiments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout-layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// variant: 'control' | 'single-page' | 'multi-step'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Number Flags
&lt;/h3&gt;

&lt;p&gt;For numeric configuration like rate limits or thresholds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart-max-items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JSON Flags
&lt;/h3&gt;

&lt;p&gt;For complex configuration objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pricing-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;showAnnual&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Default Values Matter
&lt;/h3&gt;

&lt;p&gt;The default value serves two purposes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fallback&lt;/strong&gt; — if the flag doesn't exist or the SDK can't reach the server, your app uses this value instead of crashing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type inference&lt;/strong&gt; — TypeScript infers the return type from the default value, so &lt;code&gt;useFlag('dark-mode', false)&lt;/code&gt; returns &lt;code&gt;boolean&lt;/code&gt; and &lt;code&gt;useFlag('variant', 'control')&lt;/code&gt; returns &lt;code&gt;string&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Always choose a safe default. If your flag controls a new, untested feature, default to &lt;code&gt;false&lt;/code&gt;. If it controls a critical flow, default to the value that keeps the existing behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  The useFlags Hook
&lt;/h3&gt;

&lt;p&gt;If you need multiple flags at once, &lt;code&gt;useFlags&lt;/code&gt; returns all flags as an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlags&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlags&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-sidebar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewSidebar&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;analytics-v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AnalyticsV2&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conditional Rendering with Feature Flags
&lt;/h2&gt;

&lt;p&gt;React feature flags shine in conditional rendering. Here are the most common patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component-Level Gating
&lt;/h3&gt;

&lt;p&gt;The simplest pattern — show or hide an entire component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SettingsPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showBetaFeatures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beta-features&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GeneralSettings&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SecuritySettings&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showBetaFeatures&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BetaFeatures&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Route-Level Gating
&lt;/h3&gt;

&lt;p&gt;Control access to entire pages based on a flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Navigate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AnalyticsPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analyticsEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;analytics-page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;analyticsEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Navigate&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard"&lt;/span&gt; &lt;span class="na"&gt;replace&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AnalyticsDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prop-Level Gating
&lt;/h3&gt;

&lt;p&gt;Pass flag values as props to control component behavior without the child knowing about feature flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showReviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product-reviews&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxImages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product-gallery-max&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductDetail&lt;/span&gt;
      &lt;span class="na"&gt;showReviews&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showReviews&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;maxGalleryImages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;maxImages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wrapper Component Pattern
&lt;/h3&gt;

&lt;p&gt;For reusable gating, create a wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;FeatureGate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isEnabled&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FeatureGate&lt;/span&gt; &lt;span class="na"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"new-header"&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OldHeader&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewHeader&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;FeatureGate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gradual Rollouts in React
&lt;/h2&gt;

&lt;p&gt;Gradual rollouts let you release a feature to a percentage of users instead of everyone at once. This is one of the most powerful &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;feature flag patterns&lt;/a&gt; and the primary reason teams adopt feature flags.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;When you create a flag in Rollgate with a percentage rollout (e.g., 20%), the SDK uses consistent hashing to determine whether a specific user should see the feature. The hash is based on the user ID and the flag key, which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;same user&lt;/strong&gt; always gets the &lt;strong&gt;same result&lt;/strong&gt; for a given flag (no flickering between sessions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different flags&lt;/strong&gt; hash independently (being in the 20% for flag A doesn't mean you're in the 20% for flag B)&lt;/li&gt;
&lt;li&gt;The distribution is &lt;strong&gt;uniform&lt;/strong&gt; — 20% means roughly 20% of users, not exactly 20% of requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting Up a Gradual Rollout
&lt;/h3&gt;

&lt;p&gt;In the Rollgate dashboard, set your flag's rollout to a percentage. In your React code, nothing changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CheckoutPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This might return true for 20% of users and false for the rest&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useNewCheckout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;useNewCheckout&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewCheckout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LegacyCheckout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK handles everything. When you increase the rollout from 20% to 50%, users who were already seeing the feature continue to see it (no disruption), and new users are added to the cohort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why User IDs Matter
&lt;/h3&gt;

&lt;p&gt;For consistent hashing to work, the SDK needs a stable user identifier. This is why passing a &lt;code&gt;user&lt;/code&gt; object to &lt;code&gt;RollgateProvider&lt;/code&gt; is important for rollouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_API_KEY"&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a user ID, the SDK generates a random identifier per session, which means the same person might see the feature in one session and not in another.&lt;/p&gt;

&lt;h2&gt;
  
  
  A/B Testing with React Feature Flags
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rollgate.io/blog/ab-testing-feature-flags" rel="noopener noreferrer"&gt;A/B testing with feature flags&lt;/a&gt; takes gradual rollouts a step further. Instead of just on/off, you assign users to named variants and measure which performs better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up an A/B Test
&lt;/h3&gt;

&lt;p&gt;Create a flag in Rollgate with string variants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;control&lt;/code&gt; — 50% of users (the existing experience)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;variant-a&lt;/code&gt; — 25% of users&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;variant-b&lt;/code&gt; — 25% of users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then use it in React:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PricingPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pricing-experiment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;variant-a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PricingAnnualFirst&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;variant-b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PricingCompact&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PricingDefault&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tracking Conversions
&lt;/h3&gt;

&lt;p&gt;To measure the impact of your experiment, track events when users convert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRollgate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PricingPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pricing-experiment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRollgate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;flagKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pricing-experiment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;eventName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subscription-started&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;variationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PricingTable&lt;/span&gt;
      &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onSubscribe&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubscribe&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Rollgate dashboard shows conversion rates per variant, so you can see which pricing layout drives more subscriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags with Next.js
&lt;/h2&gt;

&lt;p&gt;Next.js adds complexity with Server Components, server-side rendering, and middleware. Here's how to handle each.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client Components
&lt;/h3&gt;

&lt;p&gt;For client components, the setup is identical to standard React. Wrap your layout with &lt;code&gt;RollgateProvider&lt;/code&gt; and use hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_ROLLGATE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server Components
&lt;/h3&gt;

&lt;p&gt;For Next.js Server Components, use the Node.js SDK instead of the React SDK. The Node SDK evaluates flags server-side, which means no client-side flash of content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/dashboard/page.tsx (Server Component)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;serverKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_SERVER_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DashboardPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewDashboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getCurrentUserId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;showNewDashboard&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LegacyDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Middleware Pattern
&lt;/h3&gt;

&lt;p&gt;For route-level gating in Next.js middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;serverKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_SERVER_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUserIdFromCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;betaEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beta-access&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/beta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;betaEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern lets you gate entire routes before they render, which is cleaner than checking flags inside every page component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags with React Native
&lt;/h2&gt;

&lt;p&gt;If you're building a mobile app with React Native, Rollgate provides &lt;code&gt;@rollgate/sdk-react-native&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @rollgate/sdk-react-native
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;
      &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_API_KEY"&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MainNavigator&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomeScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewOnboarding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mobile-onboarding-v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API is nearly identical to &lt;code&gt;@rollgate/sdk-react&lt;/code&gt;. The React Native SDK handles mobile-specific concerns like background/foreground state, offline caching, and network reconnection. See the &lt;a href="https://dev.to/docs/sdk/react-native"&gt;React Native SDK docs&lt;/a&gt; for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for React Feature Flags
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Keep Flag Checks Out of Hot Render Paths
&lt;/h3&gt;

&lt;p&gt;Feature flag evaluation is fast, but in components that render hundreds of times per second (e.g., list items, animation frames), avoid calling &lt;code&gt;useFlag&lt;/code&gt; inside the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad — evaluates the flag on every list item render&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showBadge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-badge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Called N times&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductCard&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;showBadge&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showBadge&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Good — evaluate once, pass as prop&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showBadge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-badge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Called once&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductCard&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;showBadge&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showBadge&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Memoize Components Behind Flags
&lt;/h3&gt;

&lt;p&gt;When a flag controls which component renders, use &lt;code&gt;React.memo&lt;/code&gt; or &lt;code&gt;useMemo&lt;/code&gt; to prevent unnecessary re-renders when the flag value hasn't changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard-layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CompactDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wide&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WideDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DefaultDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Test Both Flag Paths
&lt;/h3&gt;

&lt;p&gt;Every feature flag creates two code paths. Test both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// __tests__/CheckoutPage.test.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CheckoutPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./CheckoutPage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requireActual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CheckoutPage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders new checkout when flag is on&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CheckoutPage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New Checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders legacy checkout when flag is off&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CheckoutPage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Legacy Checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mocking &lt;code&gt;useFlag&lt;/code&gt; directly lets you control flag values in tests without hitting the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Clean Up Stale Flags
&lt;/h3&gt;

&lt;p&gt;Feature flags are temporary. Once a feature is fully rolled out, remove the flag and the conditional logic. Stale flags create confusion, increase code complexity, and make your flag dashboard cluttered. Set a review date when you create each flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Name Flags Descriptively
&lt;/h3&gt;

&lt;p&gt;Use kebab-case and be specific about what the flag controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Good&lt;/strong&gt;: &lt;code&gt;checkout-single-page&lt;/code&gt;, &lt;code&gt;pricing-annual-toggle&lt;/code&gt;, &lt;code&gt;onboarding-v2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bad&lt;/strong&gt;: &lt;code&gt;flag1&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;new-feature&lt;/code&gt;, &lt;code&gt;temp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Feature-Gated Routes
&lt;/h3&gt;

&lt;p&gt;Combine React Router with feature flags to gate entire sections of your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AppRoutes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analyticsEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;analytics-module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reportsEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reports-module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;analyticsEnabled&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/analytics/*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AnalyticsModule&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;reportsEnabled&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/reports/*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReportsModule&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NotFound&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Permission-Based Rendering
&lt;/h3&gt;

&lt;p&gt;Combine feature flags with user permissions for fine-grained access control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AdminPanel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;advancedAdmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;advanced-admin-tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserManagement&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;advancedAdmin&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AdvancedTools&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;superadmin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SystemConfig&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Beta Programs
&lt;/h3&gt;

&lt;p&gt;Let users opt into beta features, then use feature flags to control the experience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;BetaOptIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;betaAvailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beta-program-open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;betaAvailable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CardHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CardTitle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Beta Program&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;CardTitle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;CardHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CardContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Get early access to new features.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Switch&lt;/span&gt;
          &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;betaOptIn&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;onCheckedChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;betaOptIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;checked&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;CardContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a user opts in, their &lt;code&gt;betaOptIn&lt;/code&gt; attribute is sent to Rollgate via the user context, and targeting rules can enable beta features specifically for them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading States
&lt;/h3&gt;

&lt;p&gt;Handle the initial SDK load gracefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRollgate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isReady&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRollgate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Skeleton&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"h-screen w-full"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MainApp&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;isReady&lt;/code&gt; flag becomes &lt;code&gt;true&lt;/code&gt; once the SDK has fetched the initial flag configuration. This prevents a flash of default values before the real values arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do React feature flags affect bundle size?
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;@rollgate/sdk-react&lt;/code&gt; package is lightweight — under 5 KB gzipped. It uses the browser SDK under the hood and adds only the React-specific bindings (provider, hooks). There is no impact on your build time, and tree-shaking removes any unused exports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do feature flags cause a flash of content?
&lt;/h3&gt;

&lt;p&gt;By default, the SDK renders with default values until the flag configuration is fetched. This can cause a brief flash. To prevent it, use the &lt;code&gt;isReady&lt;/code&gt; check from &lt;code&gt;useRollgate()&lt;/code&gt; to show a loading state, or use server-side evaluation with the Node SDK in Next.js Server Components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use feature flags with React Server Components?
&lt;/h3&gt;

&lt;p&gt;Yes. Use &lt;code&gt;@rollgate/sdk-node&lt;/code&gt; for Server Components and &lt;code&gt;@rollgate/sdk-react&lt;/code&gt; for Client Components. See the Next.js section above.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do feature flags work with SSR?
&lt;/h3&gt;

&lt;p&gt;For server-side rendering, evaluate flags on the server with &lt;code&gt;@rollgate/sdk-node&lt;/code&gt; and pass the results as props to your client components. This ensures the initial HTML matches the flag state and prevents hydration mismatches.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if the Rollgate API is unavailable?
&lt;/h3&gt;

&lt;p&gt;The SDK uses local caching and a circuit breaker pattern. If the API is unreachable, it falls back to the last known flag values (cached in memory and optionally in localStorage). Your app continues to work with the most recent flag configuration. The default value you pass to &lt;code&gt;useFlag&lt;/code&gt; is used only on the very first load if no cached values exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are feature flag evaluations counted per render?
&lt;/h3&gt;

&lt;p&gt;No. Flag evaluation happens when the SDK fetches the configuration, not on each &lt;code&gt;useFlag&lt;/code&gt; call. The hook simply reads from an in-memory cache, so calling it is essentially free — comparable to reading from React context.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;You now have everything you need to implement feature flags in React. To recap:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;@rollgate/sdk-react&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Wrap your app with &lt;code&gt;RollgateProvider&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;useFlag&lt;/code&gt; to read flag values&lt;/li&gt;
&lt;li&gt;Use the dashboard to control rollouts, run A/B tests, and target users&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're new to feature flags, start with the fundamentals: &lt;a href="https://rollgate.io/blog/what-are-feature-flags" rel="noopener noreferrer"&gt;What Are Feature Flags?&lt;/a&gt; covers the core concepts, &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;Gradual Rollouts Guide&lt;/a&gt; explains percentage-based releases in depth, and &lt;a href="https://rollgate.io/blog/ab-testing-feature-flags" rel="noopener noreferrer"&gt;A/B Testing with Feature Flags&lt;/a&gt; walks through experimentation patterns.&lt;/p&gt;

&lt;p&gt;For the full React SDK API reference, see the &lt;a href="https://dev.to/docs/sdk/react"&gt;React SDK documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ready to get started? &lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;Create a free Rollgate account&lt;/a&gt; — it takes 30 seconds, no credit card required.&lt;/p&gt;

</description>
      <category>featureflags</category>
      <category>react</category>
      <category>tutorial</category>
      <category>sdk</category>
    </item>
    <item>
      <title>Feature Flags in Go: A Practical Guide with Examples</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:57:45 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/feature-flags-in-go-a-practical-guide-with-examples-25e7</link>
      <guid>https://forem.com/domenico_giordano_e441224/feature-flags-in-go-a-practical-guide-with-examples-25e7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This was originally published on &lt;a href="https://rollgate.io/blog/feature-flags-golang" rel="noopener noreferrer"&gt;rollgate.io/blog/feature-flags-golang&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Feature Flags in Go?
&lt;/h2&gt;

&lt;p&gt;Go is the language of choice for microservices, API backends, CLI tools, and infrastructure software. Teams shipping Go code in production face the same challenge: how do you release features safely without redeploying your entire service?&lt;/p&gt;

&lt;p&gt;Feature flags in Go solve this by decoupling &lt;strong&gt;deployment&lt;/strong&gt; from &lt;strong&gt;release&lt;/strong&gt;. You deploy code to production behind a flag, then control who sees it — without touching your CI/CD pipeline or restarting your binary.&lt;/p&gt;

&lt;p&gt;Here is why feature flags are particularly useful in Go applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microservices&lt;/strong&gt;: Roll out a new endpoint or algorithm to 5% of traffic, monitor error rates, then gradually increase. If something breaks, disable the flag instantly — no redeployment across your fleet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API backends&lt;/strong&gt;: Ship a v2 of your API handler behind a flag. Route beta users to v2 while everyone else stays on v1. Migrate at your own pace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI tools&lt;/strong&gt;: Enable experimental commands for internal testers before exposing them to all users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure&lt;/strong&gt;: Toggle between database backends, caching strategies, or queue implementations without code changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are not familiar with the concept yet, check out our &lt;a href="https://rollgate.io/blog/what-are-feature-flags" rel="noopener noreferrer"&gt;complete guide to feature flags&lt;/a&gt; for the fundamentals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start: Feature Flags in Go
&lt;/h2&gt;

&lt;p&gt;Let's get a feature flag running in Go in under two minutes. Install the Rollgate Go SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/rollgate/sdk-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use it in your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;

    &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="s"&gt;"github.com/rollgate/sdk-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;APIKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"your-api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-pricing-page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Showing new pricing page"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Showing old pricing page"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire setup. The SDK fetches flags from Rollgate's API, caches them locally, and keeps them updated via background polling. The second argument to &lt;code&gt;IsEnabled&lt;/code&gt; is the default value — what to return if the flag does not exist or the client is not ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DIY Approach
&lt;/h2&gt;

&lt;p&gt;Before reaching for a feature flag service, you might try building your own. Here is the simplest version using a config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;featureflags&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Flags&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;    &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a &lt;code&gt;flags.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"new-search"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dark-mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"v2-api"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for prototypes. But it hits a wall quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No real-time updates&lt;/strong&gt;: Changing a flag requires editing the file and restarting the process. In a fleet of 20 microservices, that means 20 restarts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No targeting&lt;/strong&gt;: You cannot enable a flag for specific users, segments, or percentages. It is all-or-nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No audit trail&lt;/strong&gt;: Who changed what, when? Config files do not track that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No gradual rollouts&lt;/strong&gt;: You cannot roll out to 5% of users, then 25%, then 100%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No kill switch&lt;/strong&gt;: When production is on fire, you want to disable a feature in seconds, not minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anything beyond a side project, you need a proper feature flag system. Let's look at how the Rollgate Go SDK handles all of these cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Rollgate Go SDK
&lt;/h2&gt;

&lt;p&gt;The Rollgate Go SDK is designed for Go idioms: &lt;code&gt;context.Context&lt;/code&gt; for cancellation, interfaces for testability, functional options for flexibility, and proper error handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="s"&gt;"github.com/rollgate/sdk-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;APIKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ROLLGATE_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;BaseURL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;"https://api.rollgate.io"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;RefreshInterval&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;EnableStreaming&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// Real-time updates via SSE&lt;/span&gt;
        &lt;span class="n"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CacheConfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;TTL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;StaleTTL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Identify a user for targeted evaluations&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserContext&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"user-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Attributes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"plan"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"identify failed: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Check a flag&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;showNewDashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Per-Evaluation User Context
&lt;/h3&gt;

&lt;p&gt;Sometimes you need to evaluate a flag for a different user without changing the client-level identity. The SDK supports functional options for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Evaluate for a specific user without changing client state&lt;/span&gt;
&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"beta-feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user-456"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"plan"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"enterprise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Evaluation Details
&lt;/h3&gt;

&lt;p&gt;When you need to know &lt;strong&gt;why&lt;/strong&gt; a flag returned a specific value (for debugging or logging), use &lt;code&gt;IsEnabledDetail&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabledDetail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"checkout-v2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flag=%s value=%v reason=%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"checkout-v2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the evaluation reason: whether the flag matched a targeting rule, a percentage rollout, or fell through to the default value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags in HTTP Handlers
&lt;/h2&gt;

&lt;p&gt;Go's HTTP ecosystem (Chi, Gin, standard &lt;code&gt;net/http&lt;/code&gt;) makes it easy to integrate feature flags at the handler level. Since Rollgate's own API is built with Chi, here are examples for all three.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chi Router (Recommended)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/go-chi/chi/v5"&lt;/span&gt;
    &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="s"&gt;"github.com/rollgate/sdk-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Route based on feature flag&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"userID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flagClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"search-v2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;searchV2Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;searchV1Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Standard net/http
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;featureFlagHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getUserID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flagClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-endpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newResponse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;legacyResponse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Feature Flags in gRPC Services
&lt;/h2&gt;

&lt;p&gt;For gRPC services, feature flags work naturally as unary interceptors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;

    &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="s"&gt;"github.com/rollgate/sdk-go"&lt;/span&gt;
    &lt;span class="s"&gt;"google.golang.org/grpc"&lt;/span&gt;
    &lt;span class="s"&gt;"google.golang.org/grpc/metadata"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;featureFlagInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flagClient&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;grpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnaryServerInterceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;grpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnaryServerInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="n"&gt;grpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnaryHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Extract user ID from gRPC metadata&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromIncomingContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-user-id"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Inject flag values into context&lt;/span&gt;
        &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"search-v2"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;flagClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"search-v2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="s"&gt;"new-ranking"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;flagClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-ranking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flagsContextKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// In your gRPC service implementation&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;SearchService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flagsContextKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"search-v2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;searchV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;searchV1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gradual Rollouts in Go
&lt;/h2&gt;

&lt;p&gt;Gradual rollouts let you release a feature to a percentage of users, increasing over time. This is the safest way to ship changes in production. For a deep dive on rollout strategies, see our &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;gradual rollouts guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Rollgate SDK handles rollout percentages server-side using consistent hashing. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same user always gets the same result for a given flag (no flickering).&lt;/li&gt;
&lt;li&gt;Users in the 5% rollout are always included in the 25% rollout.&lt;/li&gt;
&lt;li&gt;Distribution is statistically uniform.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// In the Rollgate dashboard, set "new-checkout" to 10% rollout.&lt;/span&gt;
&lt;span class="c"&gt;// In your code, just check the flag:&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;checkoutHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getUserID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// The SDK evaluates the rollout percentage using consistent&lt;/span&gt;
    &lt;span class="c"&gt;// hashing of flagKey + userID. No extra code needed.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flagClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;newCheckoutFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;currentCheckoutFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You control the percentage from the Rollgate dashboard. Start at 1%, monitor your metrics, bump to 10%, then 50%, then 100%. If errors spike at any stage, set it back to 0% — instantly, without redeploying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags with User Targeting
&lt;/h2&gt;

&lt;p&gt;Beyond percentage rollouts, you can target specific users, segments, or attributes. This is useful for beta programs, enterprise features, or regional launches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attribute-Based Targeting
&lt;/h3&gt;

&lt;p&gt;Set up targeting rules in the Rollgate dashboard. For example: enable &lt;code&gt;advanced-analytics&lt;/code&gt; for users where &lt;code&gt;plan&lt;/code&gt; equals &lt;code&gt;pro&lt;/code&gt; or &lt;code&gt;enterprise&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Identify the user with attributes&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserContext&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Attributes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"plan"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c"&gt;// "free", "starter", "pro", "enterprise"&lt;/span&gt;
        &lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c"&gt;// "US", "DE", "JP"&lt;/span&gt;
        &lt;span class="s"&gt;"created_at"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c"&gt;// signup date&lt;/span&gt;
        &lt;span class="s"&gt;"team_size"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TeamSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;// number&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"identify error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Now flag evaluations consider these attributes&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"advanced-analytics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Only users matching the targeting rules see this&lt;/span&gt;
    &lt;span class="n"&gt;showAdvancedAnalytics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Segment Examples
&lt;/h3&gt;

&lt;p&gt;Common targeting patterns you can configure in the dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Beta testers&lt;/strong&gt;: email ends with &lt;code&gt;@yourcompany.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise features&lt;/strong&gt;: plan equals &lt;code&gt;enterprise&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional rollout&lt;/strong&gt;: country in &lt;code&gt;US, CA, GB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Power users&lt;/strong&gt;: team_size greater than 50&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New users&lt;/strong&gt;: created_at after a specific date&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SDK evaluates targeting rules locally after the initial fetch, so there is no per-evaluation API call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with Feature Flags
&lt;/h2&gt;

&lt;p&gt;Testability is a first-class concern in Go. The Rollgate SDK is designed around interfaces, making it easy to mock in tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define a Flag Client Interface
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// featureflags.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;featureflags&lt;/span&gt;

&lt;span class="c"&gt;// Client defines the interface for feature flag evaluation.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flagKey&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mock Implementation for Tests
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// featureflags_test.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;featureflags&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MockClient&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Flags&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MockClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flagKey&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;flagKey&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MockClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test Both Paths
&lt;/h3&gt;

&lt;p&gt;Always test both the enabled and disabled paths. Feature flags that are never tested in both states are bugs waiting to happen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestCheckoutHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="n"&gt;flagValue&lt;/span&gt;  &lt;span class="kt"&gt;bool&lt;/span&gt;
        &lt;span class="n"&gt;wantStatus&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
        &lt;span class="n"&gt;wantBody&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;}{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"new checkout enabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;flagValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantStatus&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantBody&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;`{"version":"v2"}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"new checkout disabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;flagValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantStatus&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantBody&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;`{"version":"v1"}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;MockClient&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s"&gt;"new-checkout"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flagValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewCheckoutHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRecorder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wantStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"got status %d, want %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wantStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pattern we use heavily in Rollgate's own API. See &lt;a href="https://rollgate.io/blog/feature-flags-vs-feature-branches" rel="noopener noreferrer"&gt;feature flags vs feature branches&lt;/a&gt; for more on how flags improve your testing workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flag Middleware Pattern
&lt;/h2&gt;

&lt;p&gt;For applications that check multiple flags across many handlers, a middleware pattern keeps your code DRY. Evaluate flags once per request and inject the results into &lt;code&gt;context.Context&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="s"&gt;"github.com/rollgate/sdk-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;contextKey&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FlagsKey&lt;/span&gt; &lt;span class="n"&gt;contextKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"feature-flags"&lt;/span&gt;

&lt;span class="c"&gt;// FeatureFlags is a map of flag keys to their boolean values.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;FeatureFlags&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;

&lt;span class="c"&gt;// WithFeatureFlags evaluates a set of flags for each request&lt;/span&gt;
&lt;span class="c"&gt;// and stores the results in the request context.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;WithFeatureFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getUserIDFromRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;evaluated&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FeatureFlags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;evaluated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;FlagsKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// GetFlags retrieves the evaluated flags from the request context.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;GetFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FeatureFlags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FlagsKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FeatureFlags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;FeatureFlags&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// IsEnabled is a convenience function to check a single flag from context.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flagKey&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;GetFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;flagKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it with Chi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// Apply middleware — flags are evaluated once per request&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithFeatureFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flagClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"search-v2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"new-pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"dark-mode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"search-v2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;searchV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;searchV1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern evaluates all flags once at the middleware layer, avoiding repeated &lt;code&gt;IsEnabled&lt;/code&gt; calls throughout your handlers. It also makes testing straightforward: just inject a context with the flags you want to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance: Local Evaluation vs API Calls
&lt;/h2&gt;

&lt;p&gt;A common concern with feature flags in Go is latency. The Rollgate SDK is built for performance-critical services with multiple layers of optimization.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the SDK Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initial fetch&lt;/strong&gt;: On &lt;code&gt;Init()&lt;/code&gt;, the SDK fetches all flag values from the API in a single HTTP request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local evaluation&lt;/strong&gt;: After initialization, &lt;code&gt;IsEnabled()&lt;/code&gt; reads from an in-memory map. There is no network call per evaluation. This means sub-microsecond flag checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background updates&lt;/strong&gt;: The SDK keeps flags fresh via either polling (default: every 30 seconds) or SSE streaming (real-time, sub-second updates).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-layer caching&lt;/strong&gt;: Flags are cached in memory with configurable TTL. If the API is unreachable, stale cache values are used as fallback.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  SSE Streaming for Real-Time Updates
&lt;/h3&gt;

&lt;p&gt;For applications where flag changes must propagate instantly (kill switches, incident response), enable SSE streaming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;APIKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ROLLGATE_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;EnableStreaming&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// Real-time updates via Server-Sent Events&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With streaming enabled, flag changes propagate to your Go service in under one second. No polling delay.&lt;/p&gt;

&lt;h3&gt;
  
  
  Circuit Breaker
&lt;/h3&gt;

&lt;p&gt;The SDK includes a built-in circuit breaker that protects your service when the Rollgate API is down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After 5 consecutive failures, the circuit opens and the SDK stops making requests.&lt;/li&gt;
&lt;li&gt;Cached flag values are used as fallback during the outage.&lt;/li&gt;
&lt;li&gt;After 30 seconds, the circuit enters half-open state and tests with a single request.&lt;/li&gt;
&lt;li&gt;After 3 successful requests, the circuit closes and normal operation resumes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Monitor circuit breaker state&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnCircuitOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rollgate circuit breaker opened — using cached flags"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rollgate.circuit_open"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnCircuitClosed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rollgate circuit breaker closed — back to normal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means your Go service never fails because of a feature flag service outage. Flags degrade gracefully to cached values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;IsEnabled()&lt;/code&gt; call&lt;/td&gt;
&lt;td&gt;&amp;lt; 1 microsecond (in-memory lookup)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initial flag fetch&lt;/td&gt;
&lt;td&gt;~50-100ms (single HTTP request)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background refresh (polling)&lt;/td&gt;
&lt;td&gt;Every 30s, non-blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE update propagation&lt;/td&gt;
&lt;td&gt;&amp;lt; 1 second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallback to cache&lt;/td&gt;
&lt;td&gt;Automatic, zero-latency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do feature flags affect Go binary size?
&lt;/h3&gt;

&lt;p&gt;The Rollgate Go SDK adds minimal overhead to your binary. It has zero external dependencies beyond the Go standard library. Typical binary size increase is around 2-3 MB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use feature flags in Go without a third-party service?
&lt;/h3&gt;

&lt;p&gt;Yes. You can start with the DIY approach using config files or environment variables. However, you will quickly need targeting, gradual rollouts, and real-time updates — which require a service like Rollgate.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do feature flags work with Go's concurrency model?
&lt;/h3&gt;

&lt;p&gt;The Rollgate SDK is fully goroutine-safe. The internal flag store is protected by &lt;code&gt;sync.RWMutex&lt;/code&gt;, so multiple goroutines can call &lt;code&gt;IsEnabled()&lt;/code&gt; concurrently without contention. Background polling and SSE streaming run in separate goroutines.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if the Rollgate API is unreachable?
&lt;/h3&gt;

&lt;p&gt;The SDK uses a multi-layer fallback strategy: retry with exponential backoff, circuit breaker to prevent cascading failures, and stale cache as a last resort. Your service continues to operate with the last known flag values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use polling or SSE streaming?
&lt;/h3&gt;

&lt;p&gt;Use polling (the default) for most services. It is simpler and works well when flag changes can take up to 30 seconds to propagate. Use SSE streaming when you need real-time updates, such as kill switches or incident response flags.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I clean up old feature flags?
&lt;/h3&gt;

&lt;p&gt;Remove the flag from your code first, deploy, then archive it in the Rollgate dashboard. Never remove a flag from the dashboard before removing the code — you would get the default value instead of the intended behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Ready to add feature flags to your Go application?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;Create a free Rollgate account&lt;/a&gt;&lt;/strong&gt; — no credit card required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/docs/sdk/go"&gt;Read the Go SDK docs&lt;/a&gt;&lt;/strong&gt; — full API reference and advanced configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;Learn about gradual rollouts&lt;/a&gt;&lt;/strong&gt; — ship features safely with percentage-based rollouts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Feature flags in Go let you ship faster, roll back instantly, and target specific users — all without redeploying your service. Start with a simple boolean flag and expand to gradual rollouts and user targeting as your needs grow.&lt;/p&gt;

</description>
      <category>featureflags</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Feature Flags in Python: Django, FastAPI &amp; Flask Guide</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:57:25 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/feature-flags-in-python-django-fastapi-flask-guide-25kd</link>
      <guid>https://forem.com/domenico_giordano_e441224/feature-flags-in-python-django-fastapi-flask-guide-25kd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This was originally published on &lt;a href="https://rollgate.io/blog/feature-flags-python" rel="noopener noreferrer"&gt;rollgate.io/blog/feature-flags-python&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Feature Flags in Python?
&lt;/h2&gt;

&lt;p&gt;Python powers everything from web applications and REST APIs to data pipelines and machine learning systems. Across all of these, you face the same challenge: how do you ship new features safely without risky deployments?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rollgate.io/blog/what-are-feature-flags" rel="noopener noreferrer"&gt;Feature flags&lt;/a&gt; solve this by decoupling deployment from release. You deploy code with the feature wrapped in a conditional, then control who sees it — without touching your codebase.&lt;/p&gt;

&lt;p&gt;Here is where feature flags shine in Python projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web applications&lt;/strong&gt; — Roll out new UI components or API endpoints to a subset of users in Django, FastAPI, or Flask.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST APIs&lt;/strong&gt; — Version API behavior dynamically without maintaining separate endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data pipelines&lt;/strong&gt; — Toggle new ETL logic or processing steps without redeploying Airflow DAGs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML model rollouts&lt;/strong&gt; — A/B test model versions in production, compare accuracy, and roll back instantly if metrics degrade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have ever been burned by a production deploy that broke something for all users at once, feature flags are the safety net you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start: Feature Flags with the Rollgate Python SDK
&lt;/h2&gt;

&lt;p&gt;The fastest way to add feature flags to any Python application is with the Rollgate Python SDK. Install it with pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;rollgate-python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize the client and start evaluating flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollgateClient&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize once at application startup
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sdk_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-sdk-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;poll_interval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# refresh flags every 30 seconds
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Evaluate a boolean flag
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-dashboard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;show_new_dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;show_legacy_dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Evaluate with user context for targeting
&lt;/span&gt;&lt;span class="n"&gt;user_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;beta-feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;enable_beta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client fetches flag configurations from Rollgate and caches them locally. Evaluations happen in-memory with no network calls, so they add virtually zero latency to your application.&lt;/p&gt;

&lt;p&gt;You can get your SDK key by &lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;creating a free Rollgate account&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Simple Approach: Environment Variables
&lt;/h2&gt;

&lt;p&gt;Before reaching for a feature flag service, many teams start with environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;NEW_CHECKOUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FF_NEW_CHECKOUT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;NEW_CHECKOUT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;new_checkout_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;legacy_checkout_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for simple on/off switches, but it hits limitations fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requires a redeploy&lt;/strong&gt; to change a flag value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No gradual rollouts&lt;/strong&gt; — it is all-or-nothing for every user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No user targeting&lt;/strong&gt; — you cannot enable a feature for specific users or segments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No audit trail&lt;/strong&gt; — who changed what and when?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No instant rollback&lt;/strong&gt; — reverting means changing the env var and redeploying.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Environment variables are fine for infrastructure configuration (&lt;code&gt;DATABASE_URL&lt;/code&gt;, &lt;code&gt;LOG_LEVEL&lt;/code&gt;). For feature releases, you need something more flexible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags in Django
&lt;/h2&gt;

&lt;p&gt;Django applications benefit from feature flags at multiple layers: middleware, views, templates, and Django REST Framework serializers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Middleware
&lt;/h3&gt;

&lt;p&gt;Apply feature flags globally across all requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# myapp/middleware.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollgateClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdk_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-sdk-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureFlagMiddleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_authenticated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;free&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_nav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-nav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dark_mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dark-mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  View Decorator
&lt;/h3&gt;

&lt;p&gt;Create a reusable decorator for protecting views behind flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# myapp/decorators.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Http404&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;feature_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_authenticated&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;Http404&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="nd"&gt;@feature_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-analytics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analytics_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analytics/new.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Template Tags
&lt;/h3&gt;

&lt;p&gt;Expose flags directly in Django templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# myapp/templatetags/feature_flags.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;

&lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Library&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@register.simple_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;takes_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;feature_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature_flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{% load feature_flags %}

{% feature_enabled "new-nav" as show_new_nav %}
{% if show_new_nav %}
    {% include "nav/new_nav.html" %}
{% else %}
    {% include "nav/legacy_nav.html" %}
{% endif %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Django REST Framework
&lt;/h3&gt;

&lt;p&gt;Use flags in DRF views and serializers to control API behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# myapp/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v2-product-response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProductV2Serializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;many&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProductV1Serializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;many&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Feature Flags in FastAPI
&lt;/h2&gt;

&lt;p&gt;FastAPI's dependency injection system makes feature flags clean and testable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Injection Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/dependencies.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollgateClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdk_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-sdk-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_flag_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;require_feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_flag_context&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dependency&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/routes/search.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.dependencies&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;require_feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_flag_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_flag_context&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai-search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ai_powered_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;keyword_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/search/semantic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require_feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;semantic-search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;semantic_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This entire endpoint is gated behind a feature flag.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_semantic_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Middleware
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/middleware.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;starlette.middleware.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseHTTPMiddleware&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureFlagMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseHTTPMiddleware&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maintenance_mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maintenance-mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maintenance_mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;We are performing scheduled maintenance.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Feature Flags in Flask
&lt;/h2&gt;

&lt;p&gt;Flask's simplicity pairs well with decorator-based feature flags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decorator Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/flags.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollgateClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdk_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-sdk-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;feature_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapped&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/experiments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@feature_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;experiments-page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;experiments&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;experiments.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  before_request Hook
&lt;/h3&gt;

&lt;p&gt;Evaluate flags once per request and make them available globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/__init__.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.before_request&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_feature_flags&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_pricing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-pricing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;export_csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;export-csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- templates/pricing.html --&amp;gt;&lt;/span&gt;
{% if g.flags.new_pricing %}
    {% include "pricing/new.html" %}
{% else %}
    {% include "pricing/legacy.html" %}
{% endif %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gradual Rollouts
&lt;/h2&gt;

&lt;p&gt;Instead of flipping a feature on for everyone, &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;gradual rollouts&lt;/a&gt; let you increase exposure incrementally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Percentage-Based Rollouts
&lt;/h3&gt;

&lt;p&gt;Configure a rollout percentage in the Rollgate dashboard (for example, 10%), and the SDK handles consistent user assignment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The SDK uses consistent hashing on the user key.
# The same user always gets the same result for a given percentage.
&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-checkout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# This user is in the rollout group
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;new_checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;legacy_checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical rollout schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Day 1:   5%  → internal team + early adopters
Day 3:  25%  → monitor error rates and latency
Day 5:  50%  → check support tickets
Day 7: 100%  → full release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If error rates spike at any stage, set the rollout back to 0% instantly from the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Targeting with Attributes
&lt;/h3&gt;

&lt;p&gt;Target specific users or segments by passing attributes in the context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# "free", "starter", "pro"
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# "US", "IT", "DE"
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# In the Rollgate dashboard, you can create rules like:
# - Enable for plan = "pro"
# - Enable for country IN ["US", "DE"]
# - Enable for 20% of free plan users
&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;advanced-analytics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;full_analytics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;basic_analytics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Feature Flags for ML Model Rollouts
&lt;/h2&gt;

&lt;p&gt;Machine learning models in production need careful rollout strategies. A new model might have better accuracy on your test set but degrade on edge cases in production. Feature flags let you &lt;a href="https://rollgate.io/blog/ab-testing-feature-flags" rel="noopener noreferrer"&gt;A/B test&lt;/a&gt; model versions safely.&lt;/p&gt;

&lt;h3&gt;
  
  
  A/B Test Model Versions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollgateClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdk_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-sdk-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommendation-model-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommendation_v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;log_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommendation_v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;log_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;prediction&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the &lt;code&gt;recommendation-model-v2&lt;/code&gt; flag at 5%, monitor prediction quality metrics, and increase gradually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shadow Mode
&lt;/h3&gt;

&lt;p&gt;Run both models simultaneously, serve the old one, and log the new one for comparison:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;predict_with_shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Always run the current production model
&lt;/span&gt;    &lt;span class="n"&gt;v1_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommendation_v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;v1_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v1_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Shadow mode: run v2 in parallel, log results, but don't serve them
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shadow-model-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;v2_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommendation_v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;v2_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v2_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;log_shadow_comparison&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;v1_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;v2_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;v1_result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;v2_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v1_result&lt;/span&gt;  &lt;span class="c1"&gt;# always serve v1 during shadow mode
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once shadow mode data confirms v2 performs well, switch to the A/B test approach to serve v2 to real users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with Feature Flags
&lt;/h2&gt;

&lt;p&gt;Feature flags should not make your test suite unpredictable. Use fixtures and mocks to control flag state in tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pytest Fixtures
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# conftest.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MagicMock&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mock_rollgate_all_enabled&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Both Code Paths
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# test_checkout.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_legacy_checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myapp.checkout.client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;legacy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_new_checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myapp.checkout.client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flag-Specific Mocking
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_partial_flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;side_effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;enabled_flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-checkout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dark-mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;enabled_flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;side_effect&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myapp.views.client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_rollgate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/checkout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Async Support
&lt;/h2&gt;

&lt;p&gt;Modern Python applications often use asyncio. The Rollgate Python SDK supports async evaluation for non-blocking flag checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollgateClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdk_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-sdk-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Flag evaluation is local (cached), so it's fast even in sync mode.
&lt;/span&gt;    &lt;span class="c1"&gt;# For async initialization and polling:
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;async-feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;async_new_logic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;async_legacy_logic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since flag evaluations happen against locally cached data, they are inherently fast. The async support is primarily useful for the initial flag fetch and background polling, ensuring they do not block your event loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FastAPI example with async
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.on_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;startup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;startup&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Initialize the client at startup
&lt;/span&gt;    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdk_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-sdk-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/recommendations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recommendations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ml-recommendations-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_ml_recommendations_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_ml_recommendations_v1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do feature flags add latency to my Python app?
&lt;/h3&gt;

&lt;p&gt;No. The Rollgate SDK fetches flag configurations on startup and polls for updates in the background. All flag evaluations happen locally in memory, adding microseconds of overhead — not milliseconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use feature flags with Celery tasks?
&lt;/h3&gt;

&lt;p&gt;Yes. Initialize the Rollgate client once in your Celery worker startup, and evaluate flags inside tasks just like you would in a view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Celery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollgateClient&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Celery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdk_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-sdk-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-report-format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;generate_new_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;generate_legacy_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How do I handle flags in database migrations?
&lt;/h3&gt;

&lt;p&gt;Do not use feature flags in migrations. Migrations should be deterministic and repeatable. Instead, use flags in application code to control which schema or logic path is active.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if the Rollgate service is unreachable?
&lt;/h3&gt;

&lt;p&gt;The SDK caches the last known flag state locally. If it cannot reach Rollgate to poll for updates, it continues using cached values. You can also set default values that apply when a flag has never been fetched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use feature flags with Django's test client?
&lt;/h3&gt;

&lt;p&gt;Yes. Mock the Rollgate client in your Django test setup, or use the middleware approach and override &lt;code&gt;request.feature_flags&lt;/code&gt; directly in tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Using Feature Flags in Python
&lt;/h2&gt;

&lt;p&gt;Feature flags transform how you ship Python applications. Whether you are building a Django web app, a FastAPI microservice, or a Flask API, the pattern is the same: wrap new code in a flag check, deploy safely, and control the rollout from your dashboard.&lt;/p&gt;

&lt;p&gt;Ready to get started?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;Create a free Rollgate account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Install the SDK: &lt;code&gt;pip install rollgate-python&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://dev.to/docs/sdk/python"&gt;Python SDK documentation&lt;/a&gt; for the full API reference&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No credit card required. Free tier includes 500K requests/month and unlimited flags.&lt;/p&gt;

</description>
      <category>featureflags</category>
      <category>python</category>
      <category>django</category>
      <category>fastapi</category>
    </item>
    <item>
      <title>Feature Flag Platform Comparison 2026: An Honest Self-Audit</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:57:09 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/feature-flag-platform-comparison-2026-an-honest-self-audit-5433</link>
      <guid>https://forem.com/domenico_giordano_e441224/feature-flag-platform-comparison-2026-an-honest-self-audit-5433</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This was originally published on &lt;a href="https://rollgate.io/blog/rollgate-product-audit-2026" rel="noopener noreferrer"&gt;rollgate.io/blog/rollgate-product-audit-2026&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why We're Publishing Our Own Product Audit
&lt;/h2&gt;

&lt;p&gt;Most SaaS companies publish comparison pages that make them look perfect. We're doing something different: a brutally honest audit of our own platform, scored by the same criteria we'd use to evaluate any feature flag tool.&lt;/p&gt;

&lt;p&gt;We built Rollgate because we were tired of &lt;a href="https://rollgate.io/blog/feature-flags-pricing-comparison" rel="noopener noreferrer"&gt;paying five figures for a configuration service&lt;/a&gt;. But being cheaper isn't enough — you need to be &lt;em&gt;good&lt;/em&gt;. So we asked ourselves: if a senior product owner with 10 years of experience building &lt;a href="https://rollgate.io/blog/what-are-feature-flags" rel="noopener noreferrer"&gt;feature flag platforms&lt;/a&gt; evaluated Rollgate today, what would they say?&lt;/p&gt;

&lt;p&gt;Here's the result. Every feature area scored 1-5, compared against LaunchDarkly, Flagsmith, ConfigCat, and GrowthBook. No cherry-picking, no spin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Feature Flags — 4.5/5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;: Four flag types (boolean, string, number, JSON), four categories (release, experiment, kill switch, ops), full lifecycle management with scheduling, 1-click rollback, history, tags, and multi-environment support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's strong&lt;/strong&gt;: Scheduled changes are available on every paid plan. LaunchDarkly restricts scheduling to Enterprise tier. Our rollback system stores the last 20 states per flag — one click restores the exact previous configuration including targeting rules, percentage, and variations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt;: Flag dependencies (enable flag B only if flag A is enabled). Stale flag detection (warn when a flag has been at 100% for weeks). Approval workflows for critical flags. These are enterprise features that matter at scale but aren't critical for our current target market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs competitors&lt;/strong&gt;: On par with Flagsmith and ConfigCat for core flag management. Below LaunchDarkly for enterprise workflows (approval gates, change requests). Ahead of GrowthBook, which treats flags as secondary to experimentation. For a detailed comparison, see our &lt;a href="https://rollgate.io/blog/launchdarkly-alternative" rel="noopener noreferrer"&gt;LaunchDarkly alternative guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Targeting &amp;amp; Segmentation — 4/5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;: 18 targeting operators including semver comparison, regex, numeric ranges, and list membership. Two segment types: rule-based (define conditions) and list-based (explicit user lists). Segments are reusable across flags. Percentage rollouts use consistent hashing so the same user always sees the same variant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 18 operators&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;equals&lt;/code&gt;, &lt;code&gt;not_equals&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;not_contains&lt;/code&gt;, &lt;code&gt;starts_with&lt;/code&gt;, &lt;code&gt;ends_with&lt;/code&gt;, &lt;code&gt;in&lt;/code&gt;, &lt;code&gt;not_in&lt;/code&gt;, &lt;code&gt;gt&lt;/code&gt;, &lt;code&gt;gte&lt;/code&gt;, &lt;code&gt;lt&lt;/code&gt;, &lt;code&gt;lte&lt;/code&gt;, &lt;code&gt;regex&lt;/code&gt;, &lt;code&gt;is_set&lt;/code&gt;, &lt;code&gt;is_not_set&lt;/code&gt;, &lt;code&gt;semver_gt&lt;/code&gt;, &lt;code&gt;semver_lt&lt;/code&gt;, &lt;code&gt;semver_eq&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's strong&lt;/strong&gt;: Semver targeting is rare — most competitors don't offer it. This matters for mobile teams doing version-based rollouts. Rule-based segments with reuse across flags reduce duplication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt;: Segment match count preview ("this rule matches ~2,340 users" before saving). CSV import for list-based segments. Nested rule groups (currently AND/OR, but not nested groups of groups).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs competitors&lt;/strong&gt;: On par with ConfigCat and Flagsmith. LaunchDarkly has more sophisticated targeting with prerequisites and custom attributes indexing. GrowthBook has strong experimentation targeting but simpler flag targeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  A/B Testing &amp;amp; Experimentation — 3/5
&lt;/h2&gt;

&lt;p&gt;This is our most honest score. A/B testing works, but it's not yet where it needs to be for data-driven product teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;: Full experiment lifecycle (draft → running → paused → ended). Per-variation statistics with exposures, conversions, and conversion rates. Z-test statistical significance with confidence intervals. Lift calculation. Winner declaration with optional auto-rollout to the winning variation. Event tracking integrated into all 13 SDKs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's strong&lt;/strong&gt;: Auto-rollout of the winning variation is a genuine time-saver — declare a winner and the flag automatically updates to serve the winning variant to 100% of users. Event tracking is built into every SDK, not bolted on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt;: Mutual exclusion between experiments (critical when running overlapping tests). Sample size calculator (users don't know how much traffic they need for statistical significance). Guardrail metrics (automatically stop an experiment if a critical metric degrades). Bayesian statistics as an alternative to frequentist testing. Experiment report export.&lt;/p&gt;

&lt;p&gt;These gaps matter. Without mutual exclusion, overlapping experiments can produce invalid results. Without sample size estimation, teams either run experiments too short (inconclusive) or too long (wasted time).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs competitors&lt;/strong&gt;: Below GrowthBook, which is experimentation-first and offers Bayesian stats, sequential testing, and warehouse integration. On par with Flagsmith (which has no native A/B testing). Below LaunchDarkly's Experimentation add-on. Above ConfigCat, which has zero A/B capability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we're building next&lt;/strong&gt;: Mutual exclusion layers and a sample size calculator are our top experimentation priorities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-Time &amp;amp; Performance — 5/5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;: Server-Sent Events (SSE) streaming across 12 of 13 SDKs (Flutter uses polling). Redis caching with 60-second TTL. ETag and Last-Modified conditional requests. Cache invalidation on flag changes. Bulk flag fetching. Per-plan SSE connection limits. &lt;strong&gt;Local evaluation mode&lt;/strong&gt; — server SDKs download the full ruleset via SSE and evaluate flags locally without network round-trips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's strong&lt;/strong&gt;: Local evaluation is the same architecture LaunchDarkly uses with their Relay Proxy, but we built it directly into the SDK. Flag evaluation happens in-memory with sub-microsecond latency. The API responds in 2-3ms server-side. Circuit breakers in every SDK ensure your application never fails because the flag service is down.&lt;/p&gt;

&lt;p&gt;Every SDK ships with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Circuit breaker (CLOSED → OPEN → HALF_OPEN state machine)&lt;/li&gt;
&lt;li&gt;Exponential backoff with jitter&lt;/li&gt;
&lt;li&gt;Request deduplication&lt;/li&gt;
&lt;li&gt;Stale-while-revalidate caching&lt;/li&gt;
&lt;li&gt;ETag support for bandwidth efficiency&lt;/li&gt;
&lt;li&gt;Distributed tracing (W3C Trace Context)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt;: Flutter SSE (currently polling-only). Edge evaluation via CDN workers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs competitors&lt;/strong&gt;: On par with LaunchDarkly (SSE + local eval). Ahead of ConfigCat (polling only, no local eval). Ahead of GrowthBook (local eval but no SSE). On par with Flagsmith (SSE support). The resilience features (circuit breaker, retry, tracing in every SDK) are a differentiator — most competitors add these as optional plugins, not defaults. For implementation details, see our &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;gradual rollouts guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  SDK Experience — 4.5/5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;: 13 SDKs, all published and production-ready:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SDK&lt;/th&gt;
&lt;th&gt;Registry&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;Unit tests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Core&lt;/strong&gt; (shared)&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;166&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;React&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Angular&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Svelte&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;React Native&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;0*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go modules&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PyPI&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Java&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maven&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.NET&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NuGet&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;0*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flutter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;pub.dev&lt;/td&gt;
&lt;td&gt;1.2.3&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;0*&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;*These SDKs rely on the 124 cross-SDK contract tests for behavior verification instead of individual unit test suites.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;: All TypeScript SDKs share a common &lt;code&gt;sdk-core&lt;/code&gt; package that implements caching, circuit breaker, retry, metrics, events, and tracing. Framework SDKs (React, Vue, Angular, Svelte) wrap &lt;code&gt;sdk-browser&lt;/code&gt;, which wraps &lt;code&gt;sdk-core&lt;/code&gt;. Native SDKs (Go, Java, Python, .NET, Flutter) implement the same patterns independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's strong&lt;/strong&gt;: 13 SDKs is more than GrowthBook (8) and on par with ConfigCat (12). Every SDK — not just the popular ones — ships with circuit breaker, retry, caching, event tracking, and distributed tracing. The consistent API surface means switching languages doesn't mean learning a new SDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt;: &lt;a href="https://openfeature.dev/" rel="noopener noreferrer"&gt;OpenFeature&lt;/a&gt; provider (the emerging vendor-neutral standard for feature flags). Offline-first mode for mobile SDKs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs competitors&lt;/strong&gt;: Below LaunchDarkly (25+ SDKs) but with better feature parity across SDKs. On par with Flagsmith (18 SDKs). Ahead of GrowthBook (8). On par with ConfigCat (12). See our SDK tutorials for &lt;a href="https://rollgate.io/blog/feature-flags-react" rel="noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://rollgate.io/blog/feature-flags-nextjs" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;, &lt;a href="https://rollgate.io/blog/feature-flags-golang" rel="noopener noreferrer"&gt;Go&lt;/a&gt;, and &lt;a href="https://rollgate.io/blog/feature-flags-python" rel="noopener noreferrer"&gt;Python&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard UX — 4/5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;: Complete flag management with search, filter, sort, and bulk actions. Six-step onboarding checklist that auto-tracks progress. Command palette (Cmd+K) for quick navigation. Drag-and-drop rules editor. Audit log with entity and action filtering. Usage monitoring with progress bars against plan limits. Dark mode. Fully responsive for mobile. Keyboard shortcuts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's strong&lt;/strong&gt;: The onboarding checklist is better than what LaunchDarkly, Flagsmith, or ConfigCat offer for new users. It tracks actual progress (did you create a project? create a flag? make an SDK call?) rather than just showing a tutorial. The command palette is a power-user feature that none of our competitors have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt;: Rule simulation ("if I save this rule, would user X see the flag?"). Environment diff view (compare flag state between staging and production side by side). Collaborative editing awareness (two people editing the same flag don't see each other's changes). In-app help tooltips on complex features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs competitors&lt;/strong&gt;: On par with or ahead of ConfigCat and Flagsmith for UX quality. Below LaunchDarkly for enterprise features (approval workflows, change requests, scheduled approvals).&lt;/p&gt;

&lt;h2&gt;
  
  
  Billing &amp;amp; Packaging — 4.5/5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;: Paddle as Merchant of Record (automatic VAT/tax handling). Four plans (Free, Starter €39-45/mo, Pro €99-119/mo, Growth €299-349/mo) plus custom Enterprise. Monthly and annual billing with 15-18% annual discount. Full usage tracking with monthly history. Plan limit enforcement with fail-open design. Complete checkout, cancel, resume, and plan change flows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Starter&lt;/th&gt;
&lt;th&gt;Pro&lt;/th&gt;
&lt;th&gt;Growth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SDK requests/mo&lt;/td&gt;
&lt;td&gt;500K&lt;/td&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;3M&lt;/td&gt;
&lt;td&gt;12M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE connections&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Projects&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team members&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flags&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled changes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit log&lt;/td&gt;
&lt;td&gt;3 days&lt;/td&gt;
&lt;td&gt;14 days&lt;/td&gt;
&lt;td&gt;90 days&lt;/td&gt;
&lt;td&gt;1 year&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What's strong&lt;/strong&gt;: The free tier (500K requests/month, unlimited flags) is the most generous in the market. ConfigCat limits you to 10 flags on free. The fail-open design means SDK calls never hard-fail when you exceed limits — they return cached values and the API returns proper rate limit headers. Pricing is transparent and public, unlike LaunchDarkly's "contact sales" model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt;: Free trial for paid plans (14-day Pro trial would help conversion). Proactive email alerts when approaching usage limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs competitors&lt;/strong&gt;: Pricing is our strongest competitive advantage against LaunchDarkly. A team at 100K MAU pays ~€99/month on Rollgate vs ~$4,000/month on LaunchDarkly. That's a 40x difference. See our detailed &lt;a href="https://rollgate.io/blog/feature-flags-pricing-comparison" rel="noopener noreferrer"&gt;pricing comparison&lt;/a&gt; and &lt;a href="https://rollgate.io/blog/configcat-alternative" rel="noopener noreferrer"&gt;ConfigCat comparison&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational — 3.5/5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we built&lt;/strong&gt;: Seven webhook event types with HMAC signing, delivery tracking, and automatic retry. Partial OpenAPI specification (~40 of 114 endpoints). Redis-based rate limiting with sliding window. Complete audit log with IP tracking. Health, readiness, and liveness endpoints. Prometheus metrics endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's strong&lt;/strong&gt;: The webhook system is production-grade — HMAC signing prevents spoofing, delivery history with response codes aids debugging, and retry with backoff handles transient failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt;: Complete OpenAPI spec (only 35% of endpoints documented). CLI tool for flag management from terminal. Terraform/Pulumi provider for infrastructure-as-code workflows. Native Slack/Teams notifications. Data export (flag configurations, audit log).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs competitors&lt;/strong&gt;: Below LaunchDarkly and Flagsmith (both have CLI tools and Terraform providers). On par with ConfigCat. The incomplete OpenAPI spec is our biggest operational gap — developers evaluating the API want complete, interactive documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scorecard
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;vs LD&lt;/th&gt;
&lt;th&gt;vs Flagsmith&lt;/th&gt;
&lt;th&gt;vs ConfigCat&lt;/th&gt;
&lt;th&gt;vs GrowthBook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core Flags&lt;/td&gt;
&lt;td&gt;4.5/5&lt;/td&gt;
&lt;td&gt;Below&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targeting&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Below&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A/B Testing&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;Below&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;td&gt;Below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-Time&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDKs&lt;/td&gt;
&lt;td&gt;4.5/5&lt;/td&gt;
&lt;td&gt;Below*&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Below&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing&lt;/td&gt;
&lt;td&gt;4.5/5&lt;/td&gt;
&lt;td&gt;Above&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational&lt;/td&gt;
&lt;td&gt;3.5/5&lt;/td&gt;
&lt;td&gt;Below&lt;/td&gt;
&lt;td&gt;Below&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;td&gt;Even&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;*LaunchDarkly has more SDKs (25+) but Rollgate has better feature parity across all 13.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overall: 4.1/5&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building Next
&lt;/h2&gt;

&lt;p&gt;Based on this audit, here are our priorities ordered by impact:&lt;/p&gt;

&lt;h3&gt;
  
  
  High Priority
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Free trial for Pro plan&lt;/strong&gt; — 14 days, no credit card. Let users experience scheduled changes and rollback before committing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutual exclusion for A/B tests&lt;/strong&gt; — Without this, overlapping experiments produce invalid data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample size calculator&lt;/strong&gt; — Show users how much traffic they need before starting an experiment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete OpenAPI spec&lt;/strong&gt; — Cover all 114 endpoints with interactive documentation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Medium Priority
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rule simulation&lt;/strong&gt; — "Test this targeting rule against user X" before saving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI tool&lt;/strong&gt; — Manage flags from terminal, integrate with CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack notifications&lt;/strong&gt; — Alert channels when flags change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment diff view&lt;/strong&gt; — Compare flag state across environments side by side.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Quick Wins (In Progress)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Stale flag detection banner&lt;/li&gt;
&lt;li&gt;Usage warning indicators&lt;/li&gt;
&lt;li&gt;Contextual upgrade prompts&lt;/li&gt;
&lt;li&gt;Environment diff badges&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Rollgate a good alternative to LaunchDarkly?
&lt;/h3&gt;

&lt;p&gt;For teams under 50 developers, yes. Rollgate covers the same core use cases (feature flags, targeting, rollouts, kill switches) at a fraction of the cost. The main gaps are enterprise features like approval workflows and SSO SAML. See our &lt;a href="https://rollgate.io/blog/launchdarkly-alternative" rel="noopener noreferrer"&gt;detailed LaunchDarkly comparison&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Rollgate compare to open-source tools like Flagsmith and GrowthBook?
&lt;/h3&gt;

&lt;p&gt;Rollgate scores higher on real-time infrastructure (SSE + local evaluation) and SDK resilience (circuit breakers in every SDK). Flagsmith has more SDKs (18 vs 13) and a self-hosted option. GrowthBook has stronger A/B testing with Bayesian statistics. Rollgate sits between them — stronger than Flagsmith on experimentation, stronger than GrowthBook on feature flag management.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are Rollgate's biggest weaknesses?
&lt;/h3&gt;

&lt;p&gt;A/B testing (3/5) and operational tooling (3.5/5). Specifically: no mutual exclusion between experiments, no sample size calculator, incomplete OpenAPI documentation, and no CLI tool. We're actively building all of these.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Rollgate production-ready?
&lt;/h3&gt;

&lt;p&gt;Yes. 13 published SDKs, all with circuit breakers and graceful degradation. The API runs on Hetzner in Germany (EU data residency) with PostgreSQL and Redis. The fail-open design means your application continues working even if the Rollgate API is temporarily unreachable.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does Rollgate cost compared to competitors?
&lt;/h3&gt;

&lt;p&gt;A team with 100K monthly active users pays ~€99/month on Rollgate vs ~$4,000/month on LaunchDarkly. That's a 40x difference. See the full &lt;a href="https://rollgate.io/blog/feature-flags-pricing-comparison" rel="noopener noreferrer"&gt;pricing breakdown&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Summary
&lt;/h2&gt;

&lt;p&gt;Rollgate is a solid 4/5 platform that covers the core feature flag use case better than most alternatives, at a fraction of the cost. Our real-time infrastructure and SDK resilience are best-in-class. Our A/B testing and operational tooling need work.&lt;/p&gt;

&lt;p&gt;If you're a team of 2-50 developers who need feature flags, gradual rollouts, and basic experimentation without spending $25K+/year, Rollgate is built for you. If you need enterprise approval workflows, a 25-SDK ecosystem, or advanced statistical experimentation, LaunchDarkly or GrowthBook might be better fits today.&lt;/p&gt;

&lt;p&gt;We're shipping improvements every week. This audit will be updated quarterly — follow our &lt;a href="https://dev.to/blog"&gt;changelog&lt;/a&gt; or &lt;a href="https://discord.gg/tdtdX23BVy" rel="noopener noreferrer"&gt;join the Discord&lt;/a&gt; to stay in the loop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;Try Rollgate free&lt;/a&gt; — 500K requests/month, all 13 SDKs, no credit card required.&lt;/p&gt;

</description>
      <category>productupdate</category>
      <category>featureflags</category>
      <category>comparison</category>
      <category>transparency</category>
    </item>
    <item>
      <title>How to Implement Gradual Rollouts Without Breaking Production</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:56:49 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/how-to-implement-gradual-rollouts-without-breaking-production-2ape</link>
      <guid>https://forem.com/domenico_giordano_e441224/how-to-implement-gradual-rollouts-without-breaking-production-2ape</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This was originally published on &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;rollgate.io/blog/gradual-rollouts-guide&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Is a Gradual Rollout?
&lt;/h2&gt;

&lt;p&gt;A gradual rollout (also called progressive delivery or incremental rollout) is the practice of releasing a feature to a small subset of users first, then progressively expanding to the full user base. Instead of going from 0% to 100% in one step, you control the pace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Day 1: 1% of users → Monitor
Day 2: 5% of users → Monitor
Day 3: 25% of users → Monitor
Day 5: 100% of users → Done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something goes wrong at any stage, you roll back to 0% instantly. No code changes, no redeployment, no downtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Gradual Rollouts Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reduce Blast Radius
&lt;/h3&gt;

&lt;p&gt;A bug that affects 1% of users is very different from a bug that affects 100%. Gradual rollouts contain the impact of issues, giving you time to detect and fix problems before they reach everyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build Confidence
&lt;/h3&gt;

&lt;p&gt;Shipping a big feature to all users at once is stressful. Gradual rollouts let you validate in production with real traffic, real data, and real user behavior — at a safe scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable Data-Driven Decisions
&lt;/h3&gt;

&lt;p&gt;By monitoring metrics at each stage (error rates, latency, conversion), you make rollout decisions based on data, not gut feeling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instant Rollback
&lt;/h3&gt;

&lt;p&gt;Traditional rollbacks require reverting code, running CI, and redeploying. With feature flags, rollback is a single toggle that takes effect in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollout Strategies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Percentage-Based Rollout
&lt;/h3&gt;

&lt;p&gt;The most common approach. You specify a percentage of users who should see the new feature. The feature flag service uses consistent hashing to ensure the same users always see the same variant (so a user at 5% who sees the feature will still see it at 25%).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your feature flag dashboard:&lt;/span&gt;
&lt;span class="c1"&gt;// new-search-algorithm: 10% rollout&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useNewSearch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-search-algorithm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: General feature releases, UI changes, algorithm updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Canary Release
&lt;/h3&gt;

&lt;p&gt;Start with a tiny group (0.1–1%) of users. These are your "canaries in the coal mine." If metrics look good after a set period, expand to a larger group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typical canary schedule:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0.1% for 1 hour → Check error rates&lt;/li&gt;
&lt;li&gt;1% for 4 hours → Check performance&lt;/li&gt;
&lt;li&gt;10% for 24 hours → Check user feedback&lt;/li&gt;
&lt;li&gt;50% for 24 hours → Final validation&lt;/li&gt;
&lt;li&gt;100% → Full release&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Backend changes, infrastructure updates, anything with high risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ring Deployment
&lt;/h3&gt;

&lt;p&gt;Expand through predefined rings of users, from least to most critical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ring 0&lt;/strong&gt;: Internal team (dogfooding)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ring 1&lt;/strong&gt;: Beta users / early adopters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ring 2&lt;/strong&gt;: 10% of general users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ring 3&lt;/strong&gt;: 50% of general users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ring 4&lt;/strong&gt;: All users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach is popular at Microsoft and gives you structured checkpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Enterprise software, B2B platforms, features with compliance requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. User Segment Targeting
&lt;/h3&gt;

&lt;p&gt;Instead of random percentages, target specific user segments first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable for users on the "Pro" plan first&lt;/li&gt;
&lt;li&gt;Enable for users in a specific region&lt;/li&gt;
&lt;li&gt;Enable for users who opted into the beta program
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Rollgate supports targeting rules:&lt;/span&gt;
&lt;span class="c1"&gt;// If user.plan == "pro" → enable&lt;/span&gt;
&lt;span class="c1"&gt;// Else → 10% rollout&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showAdvancedAnalytics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;advanced-analytics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Tiered features, regional launches, B2B features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Gradual Rollouts: Step by Step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Feature Flag
&lt;/h3&gt;

&lt;p&gt;In your feature flag dashboard, create a flag with a clear name and description:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key&lt;/strong&gt;: &lt;code&gt;new-checkout-flow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: Redesigned checkout with one-page form&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type&lt;/strong&gt;: Boolean&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default&lt;/strong&gt;: &lt;code&gt;false&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Add the Flag to Your Code
&lt;/h3&gt;

&lt;p&gt;Wrap the new feature behind the flag check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rollgate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Rollgate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useNewCheckout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout-flow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useNewCheckout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout-v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout-v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Deploy with Flag Off
&lt;/h3&gt;

&lt;p&gt;Merge and deploy your code. The flag is off, so all users see the old checkout. Nothing changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Enable for Internal Team
&lt;/h3&gt;

&lt;p&gt;Set a targeting rule: enable for users with &lt;code&gt;email&lt;/code&gt; ending in &lt;code&gt;@yourcompany.com&lt;/code&gt;. Test the full flow with real production data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Expand Gradually
&lt;/h3&gt;

&lt;p&gt;Once internal testing passes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable for 5% of users&lt;/li&gt;
&lt;li&gt;Monitor for 24 hours: error rates, latency, conversion rate&lt;/li&gt;
&lt;li&gt;If metrics are healthy, increase to 25%&lt;/li&gt;
&lt;li&gt;Monitor for another 24 hours&lt;/li&gt;
&lt;li&gt;Increase to 100%&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 6: Clean Up
&lt;/h3&gt;

&lt;p&gt;After successful full rollout:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove the flag check from your code&lt;/li&gt;
&lt;li&gt;Delete the old code path&lt;/li&gt;
&lt;li&gt;Archive the flag in your dashboard&lt;/li&gt;
&lt;li&gt;Update documentation&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What to Monitor During Rollout
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Error Rates
&lt;/h3&gt;

&lt;p&gt;Compare error rates between users with the flag on vs off. A spike in errors for the flag-on group means something is wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;Measure p50, p95, and p99 latency. New features sometimes introduce unexpected performance regressions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Metrics
&lt;/h3&gt;

&lt;p&gt;Track conversion rates, engagement, or whatever KPI the feature is meant to improve. If the new checkout reduces conversion, you want to know at 5%, not at 100%.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Feedback
&lt;/h3&gt;

&lt;p&gt;Watch support tickets and feedback channels. Sometimes metrics look fine but users are confused or frustrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rolling Out Too Fast
&lt;/h3&gt;

&lt;p&gt;Going from 1% to 100% in one jump defeats the purpose. Give each stage enough time to surface issues. A 24-hour soak period at each stage is a good default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Having a Rollback Plan
&lt;/h3&gt;

&lt;p&gt;Before starting a rollout, define your rollback criteria. "If error rate increases by more than 2%, disable the flag." Don't wait to decide in the middle of an incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ignoring Sticky Sessions
&lt;/h3&gt;

&lt;p&gt;Users should consistently see the same variant. If a user sees the new checkout on Monday but the old one on Tuesday, the experience is confusing and your metrics are unreliable. Use consistent hashing on user ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forgetting to Clean Up
&lt;/h3&gt;

&lt;p&gt;A gradual rollout that reaches 100% is not done until the flag is removed from code. Schedule flag cleanup as part of your rollout plan, not as an afterthought.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Monitoring
&lt;/h3&gt;

&lt;p&gt;A gradual rollout without monitoring is just a slow release. The entire point is to observe metrics at each stage and make informed decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Gradual rollouts are one of the highest-leverage practices in modern software delivery. They let you ship faster with less risk, validate changes with real production traffic, and roll back instantly when things go wrong.&lt;/p&gt;

&lt;p&gt;The key ingredients are simple: a feature flag service, percentage-based rollout rules, and disciplined monitoring. Start with your next feature — create a flag, roll out to 5%, watch the metrics, and expand from there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;Try Rollgate free&lt;/a&gt; and implement your first gradual rollout in minutes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;: New to feature flags? Start with &lt;a href="https://rollgate.io/blog/what-are-feature-flags" rel="noopener noreferrer"&gt;What Are Feature Flags?&lt;/a&gt;. Already using flags? Learn about &lt;a href="https://rollgate.io/blog/ab-testing-feature-flags" rel="noopener noreferrer"&gt;A/B testing with feature flags&lt;/a&gt;, &lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;scheduled releases&lt;/a&gt;, and how feature flags compare to &lt;a href="https://rollgate.io/blog/feature-flags-vs-feature-branches" rel="noopener noreferrer"&gt;feature branches&lt;/a&gt;. For SDK-specific guides, see &lt;a href="https://rollgate.io/blog/feature-flags-react" rel="noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://rollgate.io/blog/feature-flags-golang" rel="noopener noreferrer"&gt;Go&lt;/a&gt;, or &lt;a href="https://rollgate.io/blog/feature-flags-python" rel="noopener noreferrer"&gt;Python&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gradualrollout</category>
      <category>featureflags</category>
      <category>deployment</category>
    </item>
    <item>
      <title>Feature Flags at the Edge: What Cloudflare Flagship Means for the Category</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:56:19 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/feature-flags-at-the-edge-what-cloudflare-flagship-means-for-the-category-48ld</link>
      <guid>https://forem.com/domenico_giordano_e441224/feature-flags-at-the-edge-what-cloudflare-flagship-means-for-the-category-48ld</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This was originally published on &lt;a href="https://rollgate.io/blog/cloudflare-flagship-edge-feature-flags" rel="noopener noreferrer"&gt;rollgate.io/blog/cloudflare-flagship-edge-feature-flags&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cloudflare Just Entered the Feature Flag Market
&lt;/h2&gt;

&lt;p&gt;On April 17, 2026, Cloudflare announced &lt;a href="https://blog.cloudflare.com/flagship/" rel="noopener noreferrer"&gt;Flagship&lt;/a&gt;, a native feature flag service evaluated at the edge. The pitch is simple and, technically, hard to argue with: if your code already runs inside a Cloudflare Worker, why would you pay a third-party SaaS to evaluate a boolean for you over HTTP?&lt;/p&gt;

&lt;p&gt;For teams that live on Cloudflare, Flagship is a genuinely compelling product. For everyone else — and for the category as a whole — the announcement is more interesting than the product itself. It signals that feature flags are finishing their slow transition from "specialized SaaS" to "commoditized infrastructure primitive," the same path that caching, DNS, and logging walked before them.&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt;, an independent feature flag platform, so I have an obvious bias here. But I want to write about this honestly: what Flagship gets right, where it narrows the market, and where independent platforms still fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Flagship Actually Is
&lt;/h2&gt;

&lt;p&gt;Strip out the marketing, and Flagship is a pragmatic piece of engineering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Control plane on Durable Objects.&lt;/strong&gt; Flag configuration is stored authoritatively in Durable Objects and fanned out to Workers KV for global read distribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation at the edge.&lt;/strong&gt; Flag values are evaluated inside the same Worker isolate that is already processing the request. No outbound HTTP call, no external dependency on the request path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub-millisecond evaluation.&lt;/strong&gt; Because there is no network hop, latency is measured in microseconds. Cloudflare positions this as essentially free on a per-request basis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenFeature-native.&lt;/strong&gt; Flagship is built on &lt;a href="https://openfeature.dev/" rel="noopener noreferrer"&gt;OpenFeature&lt;/a&gt;, the CNCF standard. You write evaluation code against the OpenFeature API and swap providers via configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-runtime client.&lt;/strong&gt; The provider works on Workers (with direct bindings), Node.js, Bun, Deno, and browsers — though the best performance is obviously on Workers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private beta.&lt;/strong&gt; No public pricing yet. General availability and pricing will come "as we approach GA."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The framing in the announcement leans heavily on AI-generated code and agentic workflows: autonomous systems that ship, test, and iterate without a human in the loop need kill switches, and kill switches are feature flags. That framing is smart marketing — it ties Flagship to Cloudflare's broader AI-platform story — but the underlying product works just as well for boring use cases: gradual rollouts, A/B tests, killswitches, &lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;scheduled releases&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Flagship Is Genuinely Great
&lt;/h2&gt;

&lt;p&gt;Let me be direct: if your production traffic all terminates in Workers, Flagship is probably the right choice. Here's why.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero evaluation latency
&lt;/h3&gt;

&lt;p&gt;Even a well-tuned independent SDK has to do &lt;em&gt;something&lt;/em&gt;: either call the API (30–150ms round trip depending on region) or evaluate locally from a cached ruleset (still a few microseconds of hashing and rule walking). Flagship collapses the second case into a native Workers binding. It is hard to beat "the flag value is already in memory next to your code."&lt;/p&gt;

&lt;p&gt;For most applications this difference is invisible — a &lt;a href="https://rollgate.io/blog/how-we-test-rollgate" rel="noopener noreferrer"&gt;well-designed SDK&lt;/a&gt; evaluates flags in around 1–5μs locally after the initial config fetch. But at extreme scale, or on latency-critical edge APIs, "invisible" and "zero" are not the same thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenFeature-first
&lt;/h3&gt;

&lt;p&gt;The most important line in the Flagship announcement is "built on OpenFeature." This is the feature flag category finally picking a standard. OpenFeature is to feature flags what OpenTelemetry was to observability: a common API that lets you swap vendors by changing a provider, not by rewriting evaluation code.&lt;/p&gt;

&lt;p&gt;OpenFeature being a first-class citizen in Cloudflare's product is a net positive for &lt;em&gt;every&lt;/em&gt; feature flag platform, including independent ones. It pressures the whole market toward the standard and away from proprietary SDK lock-in.&lt;/p&gt;

&lt;h3&gt;
  
  
  No data plane to operate
&lt;/h3&gt;

&lt;p&gt;For small teams running entirely on Cloudflare, Flagship removes a dependency. No third-party SDK to monitor, no availability SLA to track for a non-Cloudflare vendor, no secondary bill. One throat to choke is a real operational win, even if it comes at the cost of vendor concentration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Flagship Narrows The Market
&lt;/h2&gt;

&lt;p&gt;Flagship is a strong product for Cloudflare-native teams. For teams that are not Cloudflare-native — which, despite Cloudflare's footprint, is still the majority of production systems — the picture is more complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  It is Cloudflare-only infrastructure
&lt;/h3&gt;

&lt;p&gt;Flagship's killer feature is its proximity to your code. That killer feature only exists when your code runs in Workers. If your backend is Go on a Hetzner VPS, Python on AWS Lambda, Java on GKE, or Node on Fly.io, Flagship's client SDK becomes just another HTTP client talking to Cloudflare's edge — and suddenly the latency advantage evaporates. You are back to the same evaluation model as any other third-party flag provider.&lt;/p&gt;

&lt;p&gt;This is not a criticism of Cloudflare; it is an honest observation about the product's target. Flagship optimizes for the Cloudflare stack and does not pretend otherwise. But the feature flag problem does not end at the boundary of any one cloud, and most production systems span at least two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vendor lock-in, quietly
&lt;/h3&gt;

&lt;p&gt;Because Flagship is built on OpenFeature, swapping &lt;em&gt;providers&lt;/em&gt; is straightforward — that is the whole point of the standard. What is not straightforward is swapping &lt;em&gt;infrastructure&lt;/em&gt;. If a significant share of your feature flag logic assumes it executes inside a Worker — cheap evaluation, bindings, edge-side targeting — migrating off Cloudflare means rewriting assumptions, not just configuration.&lt;/p&gt;

&lt;p&gt;This is the same lock-in pattern as any cloud-native primitive. AWS Feature Flags locks you to AWS. Vercel's Edge Config locks you to Vercel. Cloudflare's Flagship locks you to Cloudflare. That is a reasonable trade-off — you pay in portability for deep integration — but it is a trade-off worth naming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private beta, no pricing
&lt;/h3&gt;

&lt;p&gt;As of this writing, Flagship is invite-only with no public pricing. Teams evaluating a feature flag vendor today cannot choose Flagship. That is not a dig; every product starts somewhere. But it means the immediate market impact is limited, and the real impact depends entirely on what the pricing looks like at GA.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Trend: Flags Are Becoming Infrastructure
&lt;/h2&gt;

&lt;p&gt;Zoom out from Cloudflare specifically and a pattern becomes obvious. In 2024 Vercel shipped &lt;a href="https://vercel.com/docs/edge-config" rel="noopener noreferrer"&gt;Edge Config&lt;/a&gt; as a low-latency key-value store explicitly positioned for feature flags. In 2026 Cloudflare ships Flagship. AWS has had &lt;a href="https://docs.aws.amazon.com/appconfig/" rel="noopener noreferrer"&gt;AppConfig Feature Flags&lt;/a&gt; for years, though it is rarely the first choice for teams. Netlify, Fastly, and the rest will not be far behind.&lt;/p&gt;

&lt;p&gt;The category is following the well-worn path of every "specialized SaaS" that became a commodity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Early:&lt;/strong&gt; a hard problem, solved by specialists who charge enterprise prices. (LaunchDarkly, Split.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Middle:&lt;/strong&gt; cheaper independent alternatives appear, pricing becomes transparent. (Flagsmith, ConfigCat, GrowthBook, &lt;a href="https://rollgate.io/blog/launchdarkly-alternative" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Late:&lt;/strong&gt; hyperscalers ship native versions as platform primitives, differentiated by proximity to the runtime rather than by flag logic itself. (Flagship, Edge Config, AppConfig.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Feature flags are not going away. The opposite — more people and more code will use them than ever, especially as AI-generated code pushes the industry toward more aggressive rollout and rollback strategies. What is changing is the shape of the market: the "feature flags as a $100K/year enterprise SaaS" tier is being squeezed from below by commoditized platform options, and from the side by cheaper independents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Independent Platforms Still Fit
&lt;/h2&gt;

&lt;p&gt;"Commoditized" does not mean "solved." There are real categories of team for which neither Flagship nor its equivalents will be the right answer, and they are not small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-runtime and multi-cloud teams
&lt;/h3&gt;

&lt;p&gt;A meaningful share of real production systems span at least two of: a Go/Python/Java backend, a React/Vue/Angular frontend, a mobile app (Flutter or React Native), and sometimes a .NET service. None of those are Workers. For these teams, the natural choice is a platform with &lt;a href="https://rollgate.io/docs" rel="noopener noreferrer"&gt;SDKs in every language they actually use&lt;/a&gt;, not one optimized for a specific runtime.&lt;/p&gt;

&lt;p&gt;This is the explicit positioning of most independent platforms: portability across stacks, not deep integration with one. As long as heterogeneous stacks are the norm — which, in my experience, they still are — this niche is durable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Predictable, transparent pricing
&lt;/h3&gt;

&lt;p&gt;Enterprise feature flag tools typically price per Monthly Active User (MAU), which is a metric that scales with product success rather than with flag complexity. A viral signup spike can turn your flag bill into a six-figure line item without you changing a single flag. Flat-tier or per-request pricing is much easier to reason about for teams with actual budgets (&lt;a href="https://rollgate.io/blog/feature-flags-pricing-comparison" rel="noopener noreferrer"&gt;see pricing comparison&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We do not yet know how Flagship will price. If it is priced as a Workers primitive (cheap, request-based), it will put serious downward pressure on the market. If it is priced like a product-ized SaaS, the independent-alternative case stays strong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vendor neutrality as a governance stance
&lt;/h3&gt;

&lt;p&gt;For some organizations — public sector, regulated industries, companies with explicit multi-cloud mandates — being locked into a single cloud for a primitive like feature flags is not just an engineering trade-off but a compliance or procurement one. Independent, self-hostable, or cloud-agnostic flag platforms will continue to have a captive audience here regardless of what Cloudflare, Vercel, or AWS ship.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature depth
&lt;/h3&gt;

&lt;p&gt;Feature flags start as booleans and quickly become a platform: &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;targeting rules&lt;/a&gt;, &lt;a href="https://rollgate.io/blog/ab-testing-feature-flags" rel="noopener noreferrer"&gt;A/B testing&lt;/a&gt;, audit logs, scheduled releases, approvals, environment promotion, cohort analysis. The hyperscaler-primitive versions tend to start narrow and deep-integrated; the independent platforms have been building the surrounding product surface for years. That gap will close over time, but it exists today.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means If You Are Choosing Today
&lt;/h2&gt;

&lt;p&gt;If you are evaluating feature flag platforms in 2026, the decision tree is actually clearer now than it was a year ago:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You run fully on Cloudflare Workers and value the deep integration more than portability&lt;/strong&gt; → Flagship (when it exits beta).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You run fully on Vercel&lt;/strong&gt; → Edge Config for cheap cases, a &lt;a href="https://rollgate.io/blog/launchdarkly-alternative" rel="noopener noreferrer"&gt;full-featured vendor&lt;/a&gt; for complex flag logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You run on AWS and are already using AppConfig&lt;/strong&gt; → extend AppConfig for basic cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your stack spans clouds, languages, or includes mobile&lt;/strong&gt; → an independent, multi-SDK platform is the natural fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need enterprise governance (SSO, audit, approvals) and flat pricing&lt;/strong&gt; → an independent platform with an enterprise tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have a LaunchDarkly bill you cannot justify&lt;/strong&gt; → a cheaper independent alternative will almost certainly do the job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest read is that Flagship does not kill the feature flag category; it clarifies it. Cloudflare-native teams have a strong new option. Everyone else has the same options they had yesterday — just with the quiet reassurance that the category matters enough for a $50B infrastructure company to build a native version of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Note From Someone Building In This Space
&lt;/h2&gt;

&lt;p&gt;I will be transparent: I read Cloudflare's announcement and my first reaction was a nervous one. Building an independent product in a category that a hyperscaler just entered is not a comfortable position. But the more I sat with it, the more I thought it was net-good.&lt;/p&gt;

&lt;p&gt;Cloudflare shipping Flagship is the strongest possible signal that feature flags are not a niche dev-tool curiosity but essential infrastructure. More developers will learn what they are. More teams will adopt them. The total addressable market grows. Some of those teams will fit Flagship; most will not. The interesting platforms — the ones that will still matter in five years — are the ones that pick a defensible position inside this expanding market, not the ones that try to beat Cloudflare on Cloudflare's home turf.&lt;/p&gt;

&lt;p&gt;For Rollgate, the position has been the same since I started: portable across every runtime developers actually ship to, priced flat instead of per-MAU, and opinionated about the product surface around the flag itself (targeting, rollouts, scheduling, audit). Flagship does not change that position. If anything, it sharpens it.&lt;/p&gt;

&lt;p&gt;If you are thinking about how feature flags fit into your stack in 2026, you now have more good options than you did last month. That is a good thing — even for those of us who have to compete with one of them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to see how an independent, multi-runtime feature flag platform works? &lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt; has SDKs for Node.js, Go, Python, Java, .NET, Flutter, React, Vue, Angular, Svelte, React Native, and the browser, with flat pricing and a generous free tier. &lt;a href="https://rollgate.io/register" rel="noopener noreferrer"&gt;Create a free account&lt;/a&gt; or &lt;a href="https://rollgate.io/docs" rel="noopener noreferrer"&gt;read the docs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>featureflags</category>
      <category>edgecomputing</category>
      <category>openfeature</category>
    </item>
    <item>
      <title>Feature Flags in Node.js: Express and Fastify Guide</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Wed, 22 Apr 2026 20:29:56 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/feature-flags-in-nodejs-express-and-fastify-guide-1mcd</link>
      <guid>https://forem.com/domenico_giordano_e441224/feature-flags-in-nodejs-express-and-fastify-guide-1mcd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This was originally published on &lt;a href="https://rollgate.io/blog/feature-flags-nodejs" rel="noopener noreferrer"&gt;rollgate.io/blog/feature-flags-nodejs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Feature Flags in Node.js?
&lt;/h2&gt;

&lt;p&gt;Node.js powers a huge slice of production backends — REST APIs, GraphQL gateways, background workers, BFF layers, real-time services. All of them share the same release problem: you want to ship code continuously, but you do not want every deploy to be a product change.&lt;/p&gt;

&lt;p&gt;Feature flags in Node.js decouple deployment from release. You push code to production behind a flag and decide later who sees the new behavior, when, and under what conditions. If something breaks, you flip the flag off in the dashboard — no redeploy, no rollback PR, no pager at 3am.&lt;/p&gt;

&lt;p&gt;This guide covers the practical side: how to wire feature flags into Express and Fastify applications, how to target specific users, how to roll out gradually, and the production gotchas that every team hits sooner or later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start: Feature Flags in Node.js
&lt;/h2&gt;

&lt;p&gt;Let us get a flag running end-to-end. Install the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @rollgate/sdk-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then wire it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enableStreaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// real-time updates over SSE&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New checkout flow enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Legacy checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole setup. The SDK pulls rules from the API, caches them in memory, and keeps them fresh in the background. With &lt;code&gt;enableStreaming: true&lt;/code&gt; the client keeps a Server-Sent Events connection open and applies changes within ~50ms of a flag flip. The second argument to &lt;code&gt;isEnabled&lt;/code&gt; is the default value returned if the client is not yet initialized or the flag does not exist.&lt;/p&gt;

&lt;p&gt;Evaluation is local, in-process. No network hop per flag check — the rules are already in memory, so you can evaluate thousands of flags per request without adding latency to the hot path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DIY Approach (and Its Limitations)
&lt;/h2&gt;

&lt;p&gt;Before reaching for a dedicated platform, most teams start with environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;newCheckout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEW_CHECKOUT&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;darkMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DARK_MODE&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newCheckout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, for exactly one week. Then you hit the limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every flag change requires a redeploy&lt;/strong&gt; — the whole point of flags is to avoid that&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No gradual rollouts&lt;/strong&gt; — it is all-or-nothing for every user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No targeting&lt;/strong&gt; — you cannot enable a feature for beta testers, enterprise plans, or a specific region&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No kill switch&lt;/strong&gt; — if the new code breaks, rolling back means another deploy cycle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No audit trail&lt;/strong&gt; — you do not know who flipped what, when, or why&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next evolution is usually a config file or a database table. You solve the redeploy problem but inherit a new one: keeping the config in sync across every instance of your Node.js service, and refreshing it without restarts. That is where a purpose-built feature flag platform earns its keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Rollgate with Express
&lt;/h2&gt;

&lt;p&gt;Express is still the workhorse of the Node.js backend ecosystem. Here is a clean pattern: attach the Rollgate client to the request via middleware, then evaluate flags inside route handlers with the user context from the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enableStreaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-user-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe-elements&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;legacy-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;EvalContext&lt;/code&gt; you pass as the third argument lets you evaluate a flag for a specific user without mutating client-level state. Each request gets its own targeting evaluation based on &lt;code&gt;userId&lt;/code&gt; and any attributes you forward (plan, region, role, anything your targeting rules reference).&lt;/p&gt;

&lt;p&gt;Remember to shut the client down cleanly on SIGTERM so the SSE connection and telemetry buffers drain properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using Rollgate with Fastify
&lt;/h2&gt;

&lt;p&gt;Fastify is the faster, more opinionated alternative. The pattern is the same — a plugin that decorates the request — but with Fastify's decorator API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Fastify&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Fastify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enableStreaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decorateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flags&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onRequest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-user-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/experiments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-pricing-ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;semantic-search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onClose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to watch: if you are running Fastify with &lt;code&gt;logger: true&lt;/code&gt; and want the flag value in every log line, add it to the request context with &lt;code&gt;request.log.child({ flags: [...] })&lt;/code&gt; inside the &lt;code&gt;onRequest&lt;/code&gt; hook. Observability on which flags evaluated for which request is the kind of detail that saves you hours in an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gradual Rollouts and User Targeting
&lt;/h2&gt;

&lt;p&gt;Once flags are wired, the real value kicks in: turning a feature on for 1% of traffic, watching error rates for an hour, then bumping it to 10% the next day. Rollgate handles this with sticky, deterministic bucketing — the same user always lands in the same bucket, so a user who sees the new feature at 5% keeps seeing it when you move to 50%.&lt;/p&gt;

&lt;p&gt;You do not need to change your Node.js code when you change the rollout percentage. The rules live in the dashboard; your SDK pulls the new rules and evaluates them locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout-v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;signupDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signupDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attributes you pass feed into targeting rules. A common pattern for B2B SaaS: enable a feature for all &lt;code&gt;plan = "enterprise"&lt;/code&gt; users plus 10% of &lt;code&gt;plan = "pro"&lt;/code&gt; users, with no rollout for free-tier. That is three rules in the dashboard, zero code changes.&lt;/p&gt;

&lt;p&gt;If you are using flags for experimentation rather than safe releases, pair them with event tracking. The Node SDK exposes &lt;code&gt;client.track()&lt;/code&gt; for conversion events, which plugs into A/B testing workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Considerations
&lt;/h2&gt;

&lt;p&gt;A flag system sits in the hot path of every request. That changes how you think about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching and evaluation mode&lt;/strong&gt;. The SDK evaluates locally by default — rules are cached in memory and refreshed via SSE or polling. There is no network call on each &lt;code&gt;isEnabled()&lt;/code&gt;. In a Node.js process with a hot path that evaluates flags thousands of times per second, this matters: network-dependent flag checks would wreck your P99.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resilience&lt;/strong&gt;. The SDK ships with a circuit breaker, retry-with-backoff, and a stale cache fallback. If the Rollgate API becomes unreachable, your service keeps serving flag evaluations using the last known rules — it does not hard-fail. You can subscribe to &lt;code&gt;circuit-open&lt;/code&gt; and &lt;code&gt;flags-stale&lt;/code&gt; events to surface this in your own monitoring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circuit-open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollgate.circuit.open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flags-stale&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollgate.flags.stale&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Kill switches in production&lt;/strong&gt;. Wrap risky code paths — a new payment provider, a rewritten algorithm, an external API integration — in a flag you can flip instantly. When something breaks, you want the shortest possible path from "we are paging" to "traffic is back on the old code." A flag flip takes under a second; a rollback deploy takes tens of minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Process lifecycle&lt;/strong&gt;. Always call &lt;code&gt;rollgate.close()&lt;/code&gt; on shutdown. It closes the SSE connection, flushes pending telemetry, and lets Kubernetes or your PaaS roll pods cleanly. Skipping this leaks file descriptors and loses the last batch of evaluation analytics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One client per process, not per request&lt;/strong&gt;. The SDK is thread-safe and reusable. Do not instantiate a new &lt;code&gt;RollgateClient&lt;/code&gt; per request — you will hit the API hard, leak connections, and lose the benefit of local caching. One long-lived client at app start, shut down on SIGTERM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name flags by feature, not by team.&lt;/strong&gt; &lt;code&gt;new-checkout&lt;/code&gt; ages better than &lt;code&gt;backend-team-q2-project&lt;/code&gt;. Future you will thank present you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always pass a sensible default.&lt;/strong&gt; &lt;code&gt;rollgate.isEnabled('feature', false)&lt;/code&gt; — false is usually the safe default (do not ship the new thing if we cannot decide). Explicit is better than surprising.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retire flags.&lt;/strong&gt; Once a rollout hits 100% and has been stable for a week, remove the flag from code. Zombie flags are a maintenance tax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log the evaluated value for high-stakes flags.&lt;/strong&gt; If &lt;code&gt;isEnabled('new-payment-provider')&lt;/code&gt; returned &lt;code&gt;true&lt;/code&gt; for a user whose transaction failed, you want that in the log line, not inferred from the timestamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate experimentation flags from release flags.&lt;/strong&gt; A kill switch for production should not expire when an experiment wraps up. Use different naming prefixes so they are easy to tell apart.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Feature flags in Node.js are a small change with an outsized impact. You stop shipping features and start shipping code, which means faster deploys, safer releases, and a rollback story that takes seconds instead of a pager rotation.&lt;/p&gt;

&lt;p&gt;The Rollgate Node.js SDK is open source, 2KB gzipped, and works identically in Express, Fastify, Koa, NestJS, and any other Node.js framework. Local evaluation, SSE streaming, circuit breaker, and kill switches come in the box.&lt;/p&gt;

&lt;p&gt;Read the full version with internal links to related guides on &lt;a href="https://rollgate.io/blog/feature-flags-nodejs" rel="noopener noreferrer"&gt;rollgate.io/blog/feature-flags-nodejs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>express</category>
      <category>fastify</category>
      <category>featureflags</category>
    </item>
    <item>
      <title>Feature Flags Pricing Comparison 2026: Stop Paying Per Seat</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Fri, 03 Apr 2026 12:55:17 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/feature-flags-pricing-comparison-2026-stop-paying-per-seat-212g</link>
      <guid>https://forem.com/domenico_giordano_e441224/feature-flags-pricing-comparison-2026-stop-paying-per-seat-212g</guid>
      <description>&lt;p&gt;I recently spent a weekend calculating the actual cost of feature flags across every major platform. The difference between the cheapest and most expensive option for the same workload? Over 100x. Here's the full breakdown so you don't have to do the math yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Enterprise Pricing Problem
&lt;/h2&gt;

&lt;p&gt;Feature flags pricing has become absurdly complex. The core concept is simple — a conditional check that's controlled remotely — yet the industry has converged on pricing models that charge five or six figures per year for what is fundamentally a key-value store with targeting rules.&lt;/p&gt;

&lt;p&gt;LaunchDarkly charges per Monthly Active User (MAU). Statsig charges per event. Optimizely bundles feature flags into a full experimentation platform you might not need. For a team of 10 developers with a growing product, the annual cost of feature flags can exceed what you pay for your entire cloud infrastructure.&lt;/p&gt;

&lt;p&gt;This isn't sustainable. And the market is responding — new tools with transparent, predictable pricing are gaining traction precisely because developers are tired of "contact sales" being the only way to find out what something costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags Pricing Models Explained
&lt;/h2&gt;

&lt;p&gt;There are four main pricing models in the feature flags market:&lt;/p&gt;

&lt;h3&gt;
  
  
  Per Seat (Per Developer)
&lt;/h3&gt;

&lt;p&gt;You pay for each team member who accesses the dashboard. Simple to understand, but it creates a perverse incentive: teams limit who can manage flags, which slows down the release process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who uses it&lt;/strong&gt;: GrowthBook, some Flagsmith plans&lt;/p&gt;

&lt;h3&gt;
  
  
  Per MAU (Monthly Active Users)
&lt;/h3&gt;

&lt;p&gt;You pay based on how many unique users evaluate flags in your application. This punishes success — the more users you acquire, the more you pay for feature flags, even if your flag usage stays constant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who uses it&lt;/strong&gt;: LaunchDarkly, Statsig&lt;/p&gt;

&lt;h3&gt;
  
  
  Per Config Fetch / Event
&lt;/h3&gt;

&lt;p&gt;You pay per API call or flag evaluation. More predictable than MAU, but can still surprise you if you increase polling frequency or add more clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who uses it&lt;/strong&gt;: ConfigCat (config fetches)&lt;/p&gt;

&lt;h3&gt;
  
  
  Flat Tier (Per Request Bands)
&lt;/h3&gt;

&lt;p&gt;You pick a plan based on request volume. The price is fixed within each tier, so you know exactly what you'll pay. No surprises when you add users, team members, or flags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who uses it&lt;/strong&gt;: Rollgate&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags Pricing Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Rollgate&lt;/th&gt;
&lt;th&gt;LaunchDarkly&lt;/th&gt;
&lt;th&gt;Flagsmith&lt;/th&gt;
&lt;th&gt;ConfigCat&lt;/th&gt;
&lt;th&gt;GrowthBook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free tier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500K req/mo, 3 projects, unlimited flags&lt;/td&gt;
&lt;td&gt;Limited trial&lt;/td&gt;
&lt;td&gt;50K req/mo&lt;/td&gt;
&lt;td&gt;10 flags&lt;/td&gt;
&lt;td&gt;Free (self-hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Entry plan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€39/mo (Starter)&lt;/td&gt;
&lt;td&gt;~$700/mo (est.)&lt;/td&gt;
&lt;td&gt;~$45/mo&lt;/td&gt;
&lt;td&gt;~$84/mo&lt;/td&gt;
&lt;td&gt;$99/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mid plan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€99/mo (Pro)&lt;/td&gt;
&lt;td&gt;Custom quote&lt;/td&gt;
&lt;td&gt;~$125/mo&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;$199/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€299/mo (Growth)&lt;/td&gt;
&lt;td&gt;$25K-150K/yr&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing unit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SDK requests&lt;/td&gt;
&lt;td&gt;MAU&lt;/td&gt;
&lt;td&gt;Requests + seats&lt;/td&gt;
&lt;td&gt;Config fetches&lt;/td&gt;
&lt;td&gt;Seats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Price scales with&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API traffic&lt;/td&gt;
&lt;td&gt;User growth&lt;/td&gt;
&lt;td&gt;Traffic + team size&lt;/td&gt;
&lt;td&gt;Polling frequency&lt;/td&gt;
&lt;td&gt;Team size&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: Competitor pricing changes frequently. The figures above are estimates based on publicly available information as of March 2026. Always check each vendor's pricing page for current rates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What's Included at Each Rollgate Tier
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Starter (€39-45/mo)&lt;/th&gt;
&lt;th&gt;Pro (€99-119/mo)&lt;/th&gt;
&lt;th&gt;Growth (€299-349/mo)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SDK requests/mo&lt;/td&gt;
&lt;td&gt;500K&lt;/td&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;3M&lt;/td&gt;
&lt;td&gt;12M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flag evaluations/mo&lt;/td&gt;
&lt;td&gt;5M&lt;/td&gt;
&lt;td&gt;10M&lt;/td&gt;
&lt;td&gt;30M&lt;/td&gt;
&lt;td&gt;120M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Projects&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flags&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environments&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team members&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE connections&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit log retention&lt;/td&gt;
&lt;td&gt;3 days&lt;/td&gt;
&lt;td&gt;14 days&lt;/td&gt;
&lt;td&gt;90 days&lt;/td&gt;
&lt;td&gt;1 year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled changes&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1-click rollback&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RBAC&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SLA&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;99.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The annual billing option saves roughly 15-18% on each paid plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost: A 10-Developer Team with 2M Requests/Month
&lt;/h2&gt;

&lt;p&gt;Let's make this concrete. Your team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 developers with dashboard access&lt;/li&gt;
&lt;li&gt;50,000 Monthly Active Users&lt;/li&gt;
&lt;li&gt;2 million SDK requests per month (polling every 30 seconds across web + mobile)&lt;/li&gt;
&lt;li&gt;3 environments (dev, staging, production)&lt;/li&gt;
&lt;li&gt;15 active feature flags&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rollgate: €99/month (Pro)
&lt;/h3&gt;

&lt;p&gt;The Pro plan covers 3M requests/month, 10 projects, 15 team members, and unlimited flags. All environments included. Scheduled changes and rollback included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual: €99 x 12 = €1,188/year&lt;/strong&gt; (or €1,068 with annual billing)&lt;/p&gt;

&lt;h3&gt;
  
  
  LaunchDarkly: Estimated $8,000-12,000/month
&lt;/h3&gt;

&lt;p&gt;LaunchDarkly prices by MAU. At 50K MAU, you're well past the starter tier. Enterprise contracts typically start at $25K/year for small teams and scale significantly with MAU growth. With 50K MAU, expect quotes in the $100K-150K/year range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual: $96,000-$150,000/year&lt;/strong&gt; (estimated, check their sales team for exact pricing)&lt;/p&gt;

&lt;h3&gt;
  
  
  Flagsmith: Estimated $125-250/month
&lt;/h3&gt;

&lt;p&gt;Flagsmith's cloud pricing includes per-seat and per-request components. For 10 seats and 2M requests, expect mid-tier pricing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual: ~$1,500-$3,000/year&lt;/strong&gt; (estimated)&lt;/p&gt;

&lt;h3&gt;
  
  
  ConfigCat: Estimated $150-300/month
&lt;/h3&gt;

&lt;p&gt;ConfigCat charges per config fetch. With 2M requests/month and 10 team members, you'll likely need a mid-to-high tier plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual: ~$1,800-$3,600/year&lt;/strong&gt; (estimated)&lt;/p&gt;

&lt;h3&gt;
  
  
  GrowthBook: $199/month (Pro)
&lt;/h3&gt;

&lt;p&gt;GrowthBook charges per seat. The Pro plan at $199/month covers up to 20 seats with premium features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual: $2,388/year&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Estimated Annual Cost&lt;/th&gt;
&lt;th&gt;Cost vs Rollgate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rollgate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~€1,100/year&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flagsmith&lt;/td&gt;
&lt;td&gt;~$1,500-3,000/year&lt;/td&gt;
&lt;td&gt;1.4-2.7x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ConfigCat&lt;/td&gt;
&lt;td&gt;~$1,800-3,600/year&lt;/td&gt;
&lt;td&gt;1.6-3.3x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GrowthBook&lt;/td&gt;
&lt;td&gt;~$2,400/year&lt;/td&gt;
&lt;td&gt;2.2x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LaunchDarkly&lt;/td&gt;
&lt;td&gt;~$96,000-150,000/year&lt;/td&gt;
&lt;td&gt;87-136x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;These are rough estimates for comparison purposes. Actual costs depend on your specific usage patterns, negotiated contracts, and plan selection. Rollgate's pricing is publicly listed; for other tools, check their websites or contact their sales teams.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The gap is largest with LaunchDarkly because MAU-based pricing fundamentally scales differently than request-based pricing. As your product grows from 50K to 500K users, your LaunchDarkly bill grows 10x. Your Rollgate bill stays the same — or moves one tier up if your request volume increases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Request-Based Pricing Makes More Sense
&lt;/h2&gt;

&lt;p&gt;MAU-based pricing charges you for users who might evaluate a flag once per session. Request-based pricing charges you for actual API usage. The difference matters because:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your user count grows faster than your flag usage.&lt;/strong&gt; When you go from 10K to 100K users, your feature flag evaluation patterns don't change 10x. You still have the same polling intervals, the same SDKs, the same flags. Your request volume grows, but not linearly with users — especially with local caching and SSE.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can control request volume.&lt;/strong&gt; Increase polling interval from 30s to 60s, and you cut requests in half. Enable SSE, and you eliminate most polling requests entirely. You can't control how many users sign up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's predictable.&lt;/strong&gt; A polling client at 30s intervals generates approximately 86K requests per month. Multiply by your client count and you know your tier. With MAU pricing, a viral marketing campaign can triple your bill overnight.&lt;/p&gt;

&lt;p&gt;Here's a typical integration — the SDK handles polling and caching automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;
      &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rg_client_your_key"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 30s default&lt;/span&gt;
        &lt;span class="na"&gt;enableSSE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// real-time updates, reduces polling&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FeatureFlaggedApp&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;FeatureFlaggedApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewUI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redesigned-ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxUploadSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max-upload-mb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showNewUI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewUI&lt;/span&gt; &lt;span class="na"&gt;maxUpload&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;maxUploadSize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CurrentUI&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One SDK connection, one polling interval, all flags cached locally. Whether you have 1,000 or 1,000,000 users, each client makes the same number of requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Calculate Your Feature Flags Cost
&lt;/h2&gt;

&lt;p&gt;Before you commit to a tool, estimate your actual usage:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Count your SDK clients&lt;/strong&gt;: How many servers, browser instances, or mobile apps will connect? Each one polls independently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Determine your polling interval&lt;/strong&gt;: Default is usually 30 seconds. At 30s, one client = ~86K requests/month. At 60s = ~43K. With SSE, polling is minimal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiply&lt;/strong&gt;: &lt;code&gt;clients x requests_per_client = monthly_requests&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pick the tier&lt;/strong&gt; that covers your volume.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: You have 3 API servers + 1 web frontend. With 30s polling, that's 4 x 86K = 344K requests/month. Rollgate Free (500K) covers this. You pay nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling example&lt;/strong&gt;: Your product grows to 20 API servers + 5 frontend instances. That's 25 x 86K = 2.15M requests/month. Rollgate Pro (3M) at €99/month covers this. On LaunchDarkly, the cost would depend on how many end users those 20 servers handle — potentially tens of thousands of dollars per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop Overpaying for Feature Flags
&lt;/h2&gt;

&lt;p&gt;Feature flags should be a line item, not a line of the budget. The core technology — flag evaluation, targeting rules, SDK communication — isn't complex enough to justify enterprise pricing for most teams.&lt;/p&gt;

&lt;p&gt;Rollgate offers transparent, flat-tier pricing with no surprises. The free tier (500K requests/month) is generous enough for small teams and side projects. Paid plans start at €39/month and include &lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;scheduled releases&lt;/a&gt; and instant rollback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;Check the full pricing at rollgate.io&lt;/a&gt; or &lt;a href="https://demo.rollgate.io" rel="noopener noreferrer"&gt;try the live demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For more context on choosing a feature flag tool, see our &lt;a href="https://rollgate.io/blog/launchdarkly-alternative" rel="noopener noreferrer"&gt;LaunchDarkly alternative comparison&lt;/a&gt; and &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;gradual rollout strategies&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt;, a feature flag platform for developers. If you have questions about feature flags, drop them in the comments — happy to help!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Feature Flags vs Feature Branches: When to Use Each</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Fri, 03 Apr 2026 12:55:16 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/feature-flags-vs-feature-branches-when-to-use-each-5gej</link>
      <guid>https://forem.com/domenico_giordano_e441224/feature-flags-vs-feature-branches-when-to-use-each-5gej</guid>
      <description>&lt;p&gt;I used to think feature branches were the only way to manage releases. Then I joined a team that used feature flags with trunk-based development, and I never looked back. Turns out, the "feature flags vs feature branches" debate is a false dichotomy — here's how to think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The False Dichotomy
&lt;/h2&gt;

&lt;p&gt;Developers often frame feature flags vs feature branches as an either/or choice. In reality, they solve different problems and work best together. Feature branches manage &lt;strong&gt;code integration&lt;/strong&gt;. Feature flags manage &lt;strong&gt;feature release&lt;/strong&gt;. Understanding this distinction is key to a healthy deployment workflow.&lt;/p&gt;

&lt;p&gt;Whether you call them feature flags, feature toggles, or feature switches, the core idea is the same: decouple deployment from release. And whether you use Git Flow, GitHub Flow, or trunk-based development, branches are about managing code changes. Once you internalize that these tools operate at different layers, the "feature branch vs feature toggle" debate dissolves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags vs Feature Toggles: Are They the Same?
&lt;/h2&gt;

&lt;p&gt;Before we go deeper, let's clear up a common source of confusion. You'll see "feature flags," "feature toggles," "feature switches," and "feature gates" used across blog posts, documentation, and conference talks. &lt;strong&gt;They all refer to the same concept&lt;/strong&gt;: a conditional check in your code that controls whether a piece of functionality is active.&lt;/p&gt;

&lt;p&gt;Martin Fowler popularized the term "feature toggle" in his 2010 article. The term "feature flag" gained traction in the DevOps community and is more common today, especially in SaaS tooling. Some teams use "toggle" for simple on/off switches and "flag" when targeting rules and percentage rollouts are involved, but this distinction isn't universal.&lt;/p&gt;

&lt;p&gt;Throughout this article, we use "feature flag" and "feature toggle" interchangeably. If you've been searching for "feature branch vs feature toggle," you're in the right place.&lt;/p&gt;

&lt;p&gt;For a complete introduction to the concept, see our guide on &lt;a href="https://rollgate.io/blog/what-are-feature-flags" rel="noopener noreferrer"&gt;what feature flags are and how they work&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Feature Branches?
&lt;/h2&gt;

&lt;p&gt;Feature branches are a Git workflow where each new feature is developed on a separate branch. When the feature is ready, the branch is merged into the main branch (usually &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature/new-payment-flow
&lt;span class="c"&gt;# ... work on the feature ...&lt;/span&gt;
git push origin feature/new-payment-flow
&lt;span class="c"&gt;# Create a pull request, get reviews, merge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advantages of Feature Branches
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation&lt;/strong&gt;: Changes are isolated until the branch is merged&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code review&lt;/strong&gt;: Pull requests enable structured review&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI checks&lt;/strong&gt;: Automated tests run on each branch before merging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Familiar&lt;/strong&gt;: Every developer knows Git branching&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Problems with Feature Branches
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Long-lived branches diverge&lt;/strong&gt;: The longer a branch lives, the harder the merge. Conflicts accumulate, and integration becomes painful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delayed feedback&lt;/strong&gt;: You don't know if your feature works in production until it's merged and deployed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All-or-nothing releases&lt;/strong&gt;: Merging a feature branch means releasing it. There's no way to deploy the code but hide the feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge conflicts&lt;/strong&gt;: Large branches touching many files create merge hell, especially on active codebases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hidden Cost of Long-Lived Feature Branches
&lt;/h2&gt;

&lt;p&gt;The problems above aren't theoretical. Long-lived feature branches impose real, measurable costs on engineering teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merge Conflict Escalation
&lt;/h3&gt;

&lt;p&gt;Research from DORA (DevOps Research and Assessment) consistently shows that teams practicing trunk-based development with short-lived branches ship faster and with fewer failures. The reason is compounding: every day a branch stays open, the probability of a merge conflict increases non-linearly. A branch open for 2 days might have a 10% chance of conflict. At 2 weeks, that number can exceed 80%, depending on team size and codebase activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context Switching Tax
&lt;/h3&gt;

&lt;p&gt;When a developer opens a PR for a 3-week-old branch and receives 47 review comments, the original context is gone. The developer has moved on to other work. Rebuilding mental context to address feedback on stale code is one of the most expensive activities in software development. Studies suggest context switching can cost 20-25 minutes per interruption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration Risk Compounds
&lt;/h3&gt;

&lt;p&gt;Consider a team of 6 developers, each working on a separate feature branch for 2 weeks. When all six branches merge in the same sprint, the integration surface is enormous. Features that worked in isolation break when combined. This "merge day" anti-pattern is why many teams dread release weeks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delayed Bug Discovery
&lt;/h3&gt;

&lt;p&gt;A bug introduced on a feature branch stays hidden until merge. If your branch diverged from &lt;code&gt;main&lt;/code&gt; 10 days ago, the bug has had 10 days to become entangled with other changes. Compare this to trunk-based development where the same bug would surface within hours, when it's cheap to fix and the context is fresh.&lt;/p&gt;

&lt;p&gt;The alternative is clear: keep branches short (hours to days, not weeks) and use feature flags for long-running features that span multiple merges.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Feature Flags?
&lt;/h2&gt;

&lt;p&gt;Feature flags wrap new functionality in a conditional check. The code is deployed to production but only activated when the flag is enabled. This decouples deployment from release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PaymentPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-payment-flow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;showNewPayment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NewPaymentFlow&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CurrentPaymentFlow&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the backend, the same pattern works in any language. Here's Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="s"&gt;"github.com/rollgate/sdk-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;paymentHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getUserFromContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUserID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-payment-flow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;handleNewPaymentFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;handleCurrentPaymentFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advantages of Feature Flags
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy anytime&lt;/strong&gt;: Code goes to production even if the feature isn't ready for users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradual rollout&lt;/strong&gt;: Enable for 1% of users, then 10%, then 100% — learn more in our &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;gradual rollouts guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant rollback&lt;/strong&gt;: Disable a flag in seconds, no redeployment needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Targeting&lt;/strong&gt;: Show features to specific users, teams, or segments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trunk-based development&lt;/strong&gt;: Everyone commits to &lt;code&gt;main&lt;/code&gt;, reducing merge conflicts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled releases&lt;/strong&gt;: Combine flags with time-based rules for &lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;scheduled feature releases&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Problems with Feature Flags
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code complexity&lt;/strong&gt;: Two code paths mean more logic to maintain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale flags&lt;/strong&gt;: Unused flags accumulate as technical debt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing surface&lt;/strong&gt;: You need to test both flag states&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requires discipline&lt;/strong&gt;: Flags need to be cleaned up after full rollout&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Companies Like Google and Netflix Use Trunk-Based Development
&lt;/h2&gt;

&lt;p&gt;Trunk-based development with feature flags isn't a niche practice. It's how the world's most productive engineering organizations ship software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google
&lt;/h3&gt;

&lt;p&gt;Google operates one of the largest monorepos in existence, with over 80,000 engineers committing to a single repository. Long-lived feature branches would be impossible at this scale. Instead, Google uses trunk-based development where all engineers commit to &lt;code&gt;head&lt;/code&gt;. Incomplete features are guarded by flags (internally called "experiments" or "flags") that keep them invisible until ready. This approach allows Google to make over 60,000 commits per day without breaking the build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Netflix
&lt;/h3&gt;

&lt;p&gt;Netflix deploys hundreds of times per day across its microservices architecture. Their engineering blog describes how every feature is wrapped in a flag before reaching production. This allows them to test new recommendation algorithms, UI changes, and encoding pipelines with real traffic at controlled percentages. When a new video player feature caused buffering issues in 2019, they disabled it for all users in under 30 seconds — something a rollback deployment would have taken minutes or longer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Meta (Facebook)
&lt;/h3&gt;

&lt;p&gt;Meta ships code to production twice daily using a system called Gatekeeper, their internal feature flag platform. Every new feature — from News Feed ranking changes to Messenger updates — goes through a flag-controlled rollout. Engineers can target by geography, device type, employee status, or custom segments. This system handles billions of flag evaluations per day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spotify
&lt;/h3&gt;

&lt;p&gt;Spotify uses feature flags extensively for their "controlled rollout" process. New features are first exposed to internal employees ("dogfooding"), then to a small percentage of users in a single market, then expanded globally. This layered approach catches issues at each stage before they reach the full 500+ million user base.&lt;/p&gt;

&lt;p&gt;The pattern is consistent across these organizations: short-lived branches for code management, feature flags for release management.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Feature Branches
&lt;/h2&gt;

&lt;p&gt;Feature branches are the right choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short-lived work&lt;/strong&gt; (1-3 days): Bug fixes, small features, refactors that can be reviewed and merged quickly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code review is essential&lt;/strong&gt;: You want a structured PR workflow with approvals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No production risk&lt;/strong&gt;: The change doesn't need gradual rollout or targeting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-source contributions&lt;/strong&gt;: External contributors need isolated branches for PRs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use Feature Flags
&lt;/h2&gt;

&lt;p&gt;Feature flags are the right choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gradual rollout needed&lt;/strong&gt;: You want to release to a percentage of users first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-running features&lt;/strong&gt;: Work that spans multiple sprints or weeks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production testing&lt;/strong&gt;: You need to verify behavior in production before full release&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kill switch required&lt;/strong&gt;: The feature could impact system stability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User targeting&lt;/strong&gt;: Different users should see different experiences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A/B testing&lt;/strong&gt;: You're running experiments between variants&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Best Approach: Use Both
&lt;/h2&gt;

&lt;p&gt;The most effective teams use short-lived feature branches combined with feature flags:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a short-lived branch&lt;/strong&gt; for a small piece of work (1-2 days)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrap new UI or behavior&lt;/strong&gt; behind a feature flag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge to main quickly&lt;/strong&gt; — the flag is off, so nothing changes for users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to production&lt;/strong&gt; — the code is there but hidden&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable the flag gradually&lt;/strong&gt; when the full feature is ready&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean up the flag&lt;/strong&gt; once the feature is at 100%&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives you the best of both worlds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short branches&lt;/strong&gt; avoid merge conflicts and integration pain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature flags&lt;/strong&gt; give you release control without deployment pressure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trunk-based development&lt;/strong&gt; keeps the main branch always deployable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous delivery&lt;/strong&gt; becomes natural, not scary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trunk-Based Development with Feature Flags
&lt;/h2&gt;

&lt;p&gt;Trunk-based development (TBD) is the practice of keeping branches short (hours to days) and merging to &lt;code&gt;main&lt;/code&gt; frequently. Feature flags are what make TBD safe for large features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Day 1: Merge backend API behind flag -&amp;gt; Deploy -&amp;gt; Flag off
Day 2: Merge UI component behind flag -&amp;gt; Deploy -&amp;gt; Flag off
Day 3: Merge integration tests -&amp;gt; Deploy -&amp;gt; Flag off
Day 4: Enable flag for internal team -&amp;gt; Test in production
Day 5: Rollout to 10% -&amp;gt; Monitor metrics
Day 7: Rollout to 100% -&amp;gt; Remove flag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each merge is small, easy to review, and safe to deploy. The feature grows incrementally in production without affecting users until you decide it's ready.&lt;/p&gt;

&lt;p&gt;Here's a concrete Node.js example of a flag-guarded API endpoint that you can merge to &lt;code&gt;main&lt;/code&gt; safely on Day 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rollgate&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Rollgate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_SERVER_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useNewCheckout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout-flow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useNewCheckout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handleNewCheckout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handleLegacyCheckout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Migration Guide: From Feature Branches to Feature Flags
&lt;/h2&gt;

&lt;p&gt;If your team currently relies on long-lived feature branches, migrating to a flag-based workflow doesn't have to happen overnight. Here's a practical step-by-step approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Pick One Feature as a Pilot
&lt;/h3&gt;

&lt;p&gt;Don't try to convert your entire workflow at once. Choose a medium-complexity feature that's about to start development. This gives you a real scenario to learn from without risking critical work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Set Up Your Flag Service
&lt;/h3&gt;

&lt;p&gt;You need a way to create and manage flags. You can start with environment variables or a config file, but a dedicated service like &lt;a href="https://rollgate.io/blog/launchdarkly-alternative" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt; gives you a dashboard, targeting rules, and audit logs from day one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Adopt the Branch + Flag Pattern
&lt;/h3&gt;

&lt;p&gt;Instead of a single long-lived &lt;code&gt;feature/new-dashboard&lt;/code&gt; branch, break the work into small PRs (each 1-2 days):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# PR 1: Add the flag and the empty shell&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; add-dashboard-flag
&lt;span class="c"&gt;# Create the flag in Rollgate, add the conditional check&lt;/span&gt;
&lt;span class="c"&gt;# Merge to main -- flag is OFF, no user impact&lt;/span&gt;

&lt;span class="c"&gt;# PR 2: Build the data layer&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; dashboard-data-layer
&lt;span class="c"&gt;# Add API endpoints behind the flag&lt;/span&gt;
&lt;span class="c"&gt;# Merge to main -- still invisible to users&lt;/span&gt;

&lt;span class="c"&gt;# PR 3: Build the UI&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; dashboard-ui
&lt;span class="c"&gt;# Add components behind the flag&lt;/span&gt;
&lt;span class="c"&gt;# Merge to main -- still invisible&lt;/span&gt;

&lt;span class="c"&gt;# Ready? Enable the flag for your team first, then gradually roll out.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Establish Flag Hygiene Rules
&lt;/h3&gt;

&lt;p&gt;Agree on team conventions before flags accumulate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Naming&lt;/strong&gt;: Use a consistent pattern like &lt;code&gt;feature.dashboard-v2&lt;/code&gt; or &lt;code&gt;release.new-checkout&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership&lt;/strong&gt;: Every flag has an owner responsible for cleanup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL&lt;/strong&gt;: Set an expected removal date when creating the flag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleanup sprint&lt;/strong&gt;: Dedicate time each sprint to removing fully-rolled-out flags&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Expand to the Full Team
&lt;/h3&gt;

&lt;p&gt;Once the pilot is complete, share lessons learned and repeat the pattern. Most teams report that within 2-3 sprints, the flag-based workflow feels natural and the benefits are obvious: fewer merge conflicts, faster releases, and fewer incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Feature Branches Only&lt;/th&gt;
&lt;th&gt;Feature Branches + Flags&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Branch lifetime&lt;/td&gt;
&lt;td&gt;Days to weeks&lt;/td&gt;
&lt;td&gt;Hours to days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merge conflicts&lt;/td&gt;
&lt;td&gt;Frequent&lt;/td&gt;
&lt;td&gt;Rare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release control&lt;/td&gt;
&lt;td&gt;None (merge = release)&lt;/td&gt;
&lt;td&gt;Full (gradual, targeted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback speed&lt;/td&gt;
&lt;td&gt;Minutes (redeploy)&lt;/td&gt;
&lt;td&gt;Seconds (toggle flag)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production testing&lt;/td&gt;
&lt;td&gt;Not possible&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A/B testing&lt;/td&gt;
&lt;td&gt;Not possible&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I use feature flags without a feature flag service?
&lt;/h3&gt;

&lt;p&gt;Yes. The simplest feature flag is an &lt;code&gt;if&lt;/code&gt; statement checking an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENABLE_NEW_DASHBOARD&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showNewDashboard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for basic use cases, but it has limitations. Changing a flag requires redeployment, there's no gradual rollout, no user targeting, and no audit trail. As your team grows and you manage more flags, a dedicated service pays for itself in reduced deployment risk and developer time. Most teams start with env vars and upgrade to a flag service once they hit 5-10 flags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do feature flags slow down my application?
&lt;/h3&gt;

&lt;p&gt;A well-implemented feature flag adds negligible overhead. The flag evaluation itself is a simple conditional check — microseconds at most. Modern SDKs cache flag values locally and sync in the background, so evaluations don't make network requests on the hot path.&lt;/p&gt;

&lt;p&gt;In Go, a cached flag evaluation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// This reads from local cache -- no network call&lt;/span&gt;
&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// Typical evaluation time: &amp;lt;1 microsecond&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The performance concern is valid only if your implementation makes a remote API call on every evaluation — which no production-grade SDK does.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I handle database migrations with feature flags?
&lt;/h3&gt;

&lt;p&gt;Database migrations are one of the trickier aspects of flag-based development. The key principle is: &lt;strong&gt;your database schema must support both code paths simultaneously&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's a practical approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Additive migrations only&lt;/strong&gt;: Add new columns or tables, never remove or rename existing ones while the flag is active&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual-write pattern&lt;/strong&gt;: When the flag is on, write data to both the old and new schema. When off, write only to the old schema&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backfill after full rollout&lt;/strong&gt;: Once the flag is at 100% and removed, run a migration to clean up the old schema
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Always write to the existing table&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// When the flag is on, also write to the new normalized tables&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rollgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;normalized-orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderLines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach avoids the scenario where you roll back a flag and lose access to data written in a new format. Both code paths can read from the same source during the transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Feature branches and feature flags aren't competing approaches — they're complementary tools. Use feature branches for code management and review. Use feature flags for release management and safety. Together, they enable fast, safe, continuous delivery.&lt;/p&gt;

&lt;p&gt;If you're still doing long-lived feature branches with big-bang releases, consider adding feature flags to your workflow. Start with one flag on your next feature and experience the difference. The teams at Google, Netflix, and Spotify didn't adopt this pattern because it was trendy — they adopted it because it works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;Get started with Rollgate&lt;/a&gt; — free tier, no credit card required.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt;, a feature flag platform for developers. If you have questions about feature flags, drop them in the comments — happy to help!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How I Built a Feature Flags SaaS as a Solo Developer</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Fri, 03 Apr 2026 12:55:15 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/how-i-built-a-feature-flags-saas-as-a-solo-developer-4417</link>
      <guid>https://forem.com/domenico_giordano_e441224/how-i-built-a-feature-flags-saas-as-a-solo-developer-4417</guid>
      <description>&lt;p&gt;I spent a year building a feature flags SaaS from scratch. No co-founder, no VC, just me, Go, and a Hetzner VPS. This post covers the full technical journey — the stack I chose, the features I built, the mistakes I made, and the real numbers behind a solo SaaS. If you're thinking about building your own developer tool, I hope this helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built Rollgate: From Side Project to Feature Flags SaaS
&lt;/h2&gt;

&lt;p&gt;Over the past year, I built Rollgate, a SaaS platform for feature flag management. The goal was simple: offer the same capabilities as LaunchDarkly — gradual rollouts, user targeting, A/B testing, kill switches — without the $70k/year enterprise price tag.&lt;/p&gt;

&lt;p&gt;In this article, I share the technical journey: architectural decisions, all the features I implemented, mistakes made, and the real numbers behind a SaaS built by a single developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Anyone who has worked in a development team knows the situation: you want to release a feature, but you're not sure it works for all users. Traditional deployment is a binary event — either the feature is live for everyone, or for no one.&lt;/p&gt;

&lt;p&gt;Feature flags solve this by separating deployment from release. You can deploy code to production and enable it gradually: first 1% of users, then 5%, then 25%, and so on. If something goes wrong, you turn off the flag in a second — no rollback, no hotfix, no downtime.&lt;/p&gt;

&lt;p&gt;Tools like LaunchDarkly do exactly this, but cost $25k to $150k per year. For a startup or mid-size team, that's prohibitive. I thought: can I build something equivalent and offer it at a fraction of the price?&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Tech Stack
&lt;/h2&gt;

&lt;p&gt;The first fundamental decision was the stack. I had two non-negotiable requirements: low latency (feature flags are evaluated on every request) and operational simplicity (fewer moving parts means fewer things to break).&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend: Go
&lt;/h3&gt;

&lt;p&gt;I chose Go for the backend API for three reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; — A flag evaluation must happen in microseconds. Go compiles to native binary, has no significant GC pauses, and handles thousands of concurrent connections with goroutines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple deployment&lt;/strong&gt; — A single static binary. No runtime, no dependencies. The final Docker image weighs 20MB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excellent stdlib&lt;/strong&gt; — HTTP server, JSON encoding, crypto, testing — all built-in. Fewer external dependencies means less surface area for bugs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The router is Chi, lightweight and net/http-compatible. No heavy frameworks — I prefer composition over convention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend: Next.js
&lt;/h3&gt;

&lt;p&gt;For the dashboard, I chose Next.js with React and Tailwind CSS. It provides SSR/SSG for marketing pages (SEO), API routes for backend proxying, and a smooth dev experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database: PostgreSQL + Redis
&lt;/h3&gt;

&lt;p&gt;PostgreSQL for persistence: JSONB for flag metadata, partial indexes, LISTEN/NOTIFY for cache invalidation. 29 tables, 67 indexes, 29 foreign keys.&lt;/p&gt;

&lt;p&gt;Redis as cache layer for flag evaluations. When an SDK asks "is flag X enabled for user Y?", Redis serves the answer in milliseconds, updated in real-time via pub/sub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollgate's Feature Set
&lt;/h2&gt;

&lt;p&gt;Rollgate isn't a simple toggle on/off. I implemented a complete set of features covering the entire software release lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Flags with Multiple Types
&lt;/h3&gt;

&lt;p&gt;Flags support four value types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boolean&lt;/strong&gt; — Classic on/off. Perfect for kill switches and feature toggles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String&lt;/strong&gt; — Returns text. Ideal for A/B testing ("variant-a", "variant-b") or dynamic messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number&lt;/strong&gt; — Returns a numeric value. Useful for configuring limits, percentages, or algorithm parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON&lt;/strong&gt; — Returns a structured object. For complex configurations like layouts, plan-specific feature sets, or business rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gradual Rollouts
&lt;/h3&gt;

&lt;p&gt;Percentage rollout enables a flag for a specific percentage of users. Distribution uses consistent hashing (MurmurHash3) on the user ID, so the same user always gets the same result without saving state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MurmurHash3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flagKey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;
&lt;span class="nx"&gt;isEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;rolloutPercentage&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Precision to 0.01%. You can go from 1% to 5% to 25% to 100% gradually, monitoring metrics at each step.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Targeting and Segments
&lt;/h3&gt;

&lt;p&gt;Targeting enables flags for specific user groups based on attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User attributes&lt;/strong&gt; — Email, plan, country, app version, any custom attribute&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operators&lt;/strong&gt; — equals, contains, startsWith, endsWith, regex, in, greaterThan, lessThan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusable segments&lt;/strong&gt; — Define "Beta Users" or "Enterprise Customers" once, use in all flags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combined rules&lt;/strong&gt; — AND/OR logic across multiple conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kill Switches
&lt;/h3&gt;

&lt;p&gt;The kill switch is the most critical production feature. When something goes wrong — a bug, a performance issue, a third-party integration down — you need to disable a feature instantly.&lt;/p&gt;

&lt;p&gt;In Rollgate, every flag has a toggle that takes effect in seconds via real-time SSE propagation. No deploy needed, no server access required, you don't even need to be a developer. A product manager can kill a feature from the dashboard on their phone.&lt;/p&gt;

&lt;p&gt;I've seen enterprise teams take 30-45 minutes for a traditional rollback (revert commit, CI, deploy, verify). With a kill switch, it's 2 seconds. During a production incident, those 30 minutes can cost thousands in lost revenue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled Changes
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;Scheduled changes&lt;/a&gt; let you program flag modifications for a specific date and time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coordinated launch&lt;/strong&gt; — "Enable the new feature Friday at 2 PM when marketing publishes the blog post"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporary promotions&lt;/strong&gt; — "Enable the Black Friday banner from Friday to Monday"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic progressive rollout&lt;/strong&gt; — "1% Monday, 10% Wednesday, 50% Friday, 100% next Monday"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature sunset&lt;/strong&gt; — "Disable the old API on April 1st"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each scheduled change appears in the flag's timeline with pending/executed/cancelled status. A server-side cron job checks every minute for changes to apply.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instant Rollback
&lt;/h3&gt;

&lt;p&gt;Every flag modification is saved in history with timestamp, author, and complete state snapshot. Rollback restores the exact flag state at a previous point — not just the value, but also targeting rules, percentages, and scheduled changes.&lt;/p&gt;

&lt;p&gt;It's different from a simple "undo": you can go back 5 changes, or to a specific dated version. The audit log tracks who did what and when.&lt;/p&gt;

&lt;h3&gt;
  
  
  A/B Testing
&lt;/h3&gt;

&lt;p&gt;String variant flags enable native &lt;a href="https://rollgate.io/blog/ab-testing-feature-flags" rel="noopener noreferrer"&gt;A/B testing&lt;/a&gt;. Instead of simple true/false, the flag returns a variant ("control", "variant-a", "variant-b") with configurable percentage distribution.&lt;/p&gt;

&lt;p&gt;Evaluation tracking records which variant was served to each user, with timestamp and context. This allows correlating variants with business metrics in your preferred analytics tool.&lt;/p&gt;

&lt;p&gt;I deliberately didn't build an integrated statistical engine — I prefer teams use their existing analytics tools rather than creating another data silo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Environment
&lt;/h3&gt;

&lt;p&gt;Every project has separate environments: development, staging, production (and as many as you want). Flags are independent per environment — you can have a flag active in staging but off in production.&lt;/p&gt;

&lt;p&gt;Each environment has its own API keys (server and client), targeting rules, and history. This prevents the classic "oops, I modified the production flag thinking it was staging" mistake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audit Log
&lt;/h3&gt;

&lt;p&gt;Every platform action is recorded in an immutable audit log: who created/modified/deleted a flag, what changed (before/after diff), when (precise timestamp), from which IP and user agent.&lt;/p&gt;

&lt;p&gt;Retention varies by plan: 3 days (Free), 14 days (Starter), 90 days (Pro), 1 year (Growth). For compliance and debugging, having a complete trace is essential.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhooks
&lt;/h3&gt;

&lt;p&gt;Webhooks notify external systems when a flag changes. Every create, update, or delete sends an HTTP POST to configured URLs with the full event payload.&lt;/p&gt;

&lt;p&gt;Use cases: notify Slack when a production flag changes, trigger a deploy, update monitoring, sync with project management tools. Webhooks have automatic retry with exponential backoff and delivery logging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics and Usage Tracking
&lt;/h3&gt;

&lt;p&gt;Rollgate tracks flag evaluations in real-time: how many times evaluated, unique users, true/false distribution, trends over time. Usage limits are per plan: 500K requests/month (Free), 1M (Starter), 3M (Pro), 12M (Growth).&lt;/p&gt;

&lt;h2&gt;
  
  
  13 SDKs
&lt;/h2&gt;

&lt;p&gt;I built 13 SDKs covering all major languages and frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript/TypeScript&lt;/strong&gt; — sdk-core, sdk-node, sdk-browser, sdk-react, sdk-vue, sdk-angular, sdk-svelte&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile&lt;/strong&gt; — sdk-react-native, sdk-flutter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt; — sdk-go, sdk-java, sdk-python, sdk-dotnet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All SDKs share the same features: local cache, circuit breaker, retry with backoff, event buffering, and real-time SSE updates. If the server is down, SDKs continue working with cached values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rg_client_your_key"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewFeature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;showNewFeature&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LegacyDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client SDK -&amp;gt; API Server (Go) -&amp;gt; Redis Cache -&amp;gt; PostgreSQL
              ^
          SSE Connection (real-time updates)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flag evaluation happens in ~200us. P99 latency is under one millisecond. The system supports tens of thousands of simultaneous SSE connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Race Conditions on Updates
&lt;/h3&gt;

&lt;p&gt;When multiple users modify the same flag simultaneously, I use optimistic locking with a version counter. If someone else modified the flag in the meantime, the operation returns 409 Conflict.&lt;/p&gt;

&lt;h3&gt;
  
  
  Circuit Breaker in SDKs
&lt;/h3&gt;

&lt;p&gt;If the API server is down, SDKs must not crash the host app. The circuit breaker has three states (Closed, Open, Half-Open) with exponential retry and request deduplication.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO for a SaaS App
&lt;/h3&gt;

&lt;p&gt;I separated marketing and dashboard with a domain split: rollgate.io for SEO pages (Server Components) and app.rollgate.io for the authenticated dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resilience and Monitoring
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daily backups&lt;/strong&gt; — PostgreSQL dump every night to a geographically separate server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt; — Prometheus + Grafana + Alertmanager with real-time alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health checks&lt;/strong&gt; — Automated verification of API, Web, DB, Redis every 60 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disaster recovery&lt;/strong&gt; — Automated script rebuilds the entire stack in ~20 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK resilience&lt;/strong&gt; — Local cache + circuit breaker = works even offline&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Go files (non-test)&lt;/td&gt;
&lt;td&gt;147&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript/TSX files&lt;/td&gt;
&lt;td&gt;168&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API endpoints&lt;/td&gt;
&lt;td&gt;114&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB tables&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total tests&lt;/td&gt;
&lt;td&gt;~850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Published SDKs&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codebase score&lt;/td&gt;
&lt;td&gt;7.86/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Simplicity Wins
&lt;/h3&gt;

&lt;p&gt;Kubernetes in production? No, Docker Compose is enough. Microservices? No, a modular monolith is simpler. Every added complexity has a maintenance cost that must justify its weight.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tests Save Lives
&lt;/h3&gt;

&lt;p&gt;With ~850 tests, I can refactor with confidence. CI runs everything on every PR and blocks deployment if something fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Billing Is Harder Than the Product
&lt;/h3&gt;

&lt;p&gt;Paddle for payments was surprisingly complex: webhooks, subscription lifecycle, prorating, dunning, tax handling. Use a payment provider that handles everything (Paddle, Lemon Squeezy) rather than raw Stripe.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. SEO Requires Constant Attention
&lt;/h3&gt;

&lt;p&gt;If Google doesn't index you, nobody will find you. I spent weeks on SSR, JSON-LD, sitemaps, meta tags. It never ends.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Building Solo Is Sustainable (With Limits)
&lt;/h3&gt;

&lt;p&gt;Code is the easy part — finding users is the real challenge. Marketing, support, and growth require different skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Market: Competitors and Positioning
&lt;/h2&gt;

&lt;p&gt;The feature flags market is dominated by a few players with aggressive enterprise pricing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Pricing&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Limitations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LaunchDarkly&lt;/td&gt;
&lt;td&gt;From $833/mo&lt;/td&gt;
&lt;td&gt;Market leader, enterprise-ready, vast integrations&lt;/td&gt;
&lt;td&gt;Prohibitive for startups, excessive complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flagsmith&lt;/td&gt;
&lt;td&gt;From $45/mo&lt;/td&gt;
&lt;td&gt;Free self-hosted, API-first&lt;/td&gt;
&lt;td&gt;Less polished UI, fewer SDKs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unleash&lt;/td&gt;
&lt;td&gt;From $80/mo&lt;/td&gt;
&lt;td&gt;Mature self-hosted, good docs&lt;/td&gt;
&lt;td&gt;Complex setup, dated UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostHog&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;Complete suite (analytics + flags), open-source&lt;/td&gt;
&lt;td&gt;Flags aren't the focus, overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollgate&lt;/td&gt;
&lt;td&gt;Free -&amp;gt; €45/mo&lt;/td&gt;
&lt;td&gt;Accessible pricing, 13 SDKs, 5-min setup&lt;/td&gt;
&lt;td&gt;New to market, growing community&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Choosing the Pricing Model
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rollgate.io/blog/feature-flags-pricing-comparison" rel="noopener noreferrer"&gt;Pricing&lt;/a&gt; was one of the hardest decisions. I studied every competitor and identified three approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-seat (LaunchDarkly)&lt;/strong&gt; — Scales with team size, penalizes large companies. $70k/year for 50 developers isn't unusual.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage-based (PostHog)&lt;/strong&gt; — Scales with traffic, unpredictable for budgeting. Can explode with traffic spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed tiers (Rollgate)&lt;/strong&gt; — Predictable pricing, scales by features and limits. Customers know exactly what they pay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose fixed tiers because cost predictability is essential for startups and SMBs. The four plans:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;th&gt;Annually&lt;/th&gt;
&lt;th&gt;Requests/mo&lt;/th&gt;
&lt;th&gt;Team&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;€0&lt;/td&gt;
&lt;td&gt;€0&lt;/td&gt;
&lt;td&gt;500K&lt;/td&gt;
&lt;td&gt;3 members&lt;/td&gt;
&lt;td&gt;Side projects, MVPs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;€45/mo&lt;/td&gt;
&lt;td&gt;€39/mo&lt;/td&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;5 members&lt;/td&gt;
&lt;td&gt;Early-stage startups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;€119/mo&lt;/td&gt;
&lt;td&gt;€99/mo&lt;/td&gt;
&lt;td&gt;3M&lt;/td&gt;
&lt;td&gt;15 members&lt;/td&gt;
&lt;td&gt;Growing teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;€349/mo&lt;/td&gt;
&lt;td&gt;€299/mo&lt;/td&gt;
&lt;td&gt;12M&lt;/td&gt;
&lt;td&gt;50 members&lt;/td&gt;
&lt;td&gt;Scale-ups, high traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The free tier is generous by design: 500K requests/month is enough for an app with thousands of active users. The goal is to lower the entry barrier — if a developer tries Rollgate and it works, upgrading to Starter as the project grows is natural.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Open Source?
&lt;/h2&gt;

&lt;p&gt;I evaluated the open-core model (free core, paid enterprise features), but it requires an active community to work. For a solo project, the risk is giving everything away for free without monetizing. I opted for a SaaS model with a generous free tier, with plans to release a self-hosted version under BSL (Business Source License) — the same approach used by HashiCorp and Sentry.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Rollgate is live at &lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;rollgate.io&lt;/a&gt; with a generous free tier (500K requests/month). Next up: advanced A/B testing with auto-winner, teams with SSO, self-hosted version, and complete OpenAPI spec.&lt;/p&gt;

&lt;p&gt;If you're interested in the project or have questions about building a SaaS solo, I'd love to hear from you in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt;, a feature flag platform for developers. If you have questions about feature flags, drop them in the comments — happy to help!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Best LaunchDarkly Alternative in 2026: Rollgate vs the Rest</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Fri, 03 Apr 2026 12:55:14 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/the-best-launchdarkly-alternative-in-2026-rollgate-vs-the-rest-3l02</link>
      <guid>https://forem.com/domenico_giordano_e441224/the-best-launchdarkly-alternative-in-2026-rollgate-vs-the-rest-3l02</guid>
      <description>&lt;p&gt;Let's be real: LaunchDarkly is a great product, but the pricing is wild. I was paying more for feature flags than for my entire cloud infrastructure. So I started looking at alternatives, and eventually built one. Here's an honest comparison of what's out there in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Teams Are Looking for a LaunchDarkly Alternative
&lt;/h2&gt;

&lt;p&gt;If you're searching for a LaunchDarkly alternative, you're not alone. LaunchDarkly is the category leader in feature flags, but it's also the most expensive option by a wide margin. Enterprise contracts typically range from $25,000 to $150,000 per year, and pricing scales with Monthly Active Users (MAU) — a metric that punishes success. The more users you have, the more you pay, even if your feature flag usage stays the same.&lt;/p&gt;

&lt;p&gt;For teams with 10 developers and a growing product, the math doesn't work. You're paying enterprise pricing for what is fundamentally a configuration service: a key-value store with targeting rules and an SDK.&lt;/p&gt;

&lt;p&gt;That's why more teams are evaluating alternatives to LaunchDarkly that offer the same core capabilities — &lt;a href="https://rollgate.io/blog/what-are-feature-flags" rel="noopener noreferrer"&gt;feature flags&lt;/a&gt;, targeting, rollback — without the unpredictable pricing model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Look for in a LaunchDarkly Alternative
&lt;/h2&gt;

&lt;p&gt;Not all feature flag tools are created equal. Before you switch, make sure the alternative checks these boxes:&lt;/p&gt;

&lt;h3&gt;
  
  
  Transparent, Predictable Pricing
&lt;/h3&gt;

&lt;p&gt;No "contact sales" as the only option. No MAU-based pricing that explodes when your product grows. You should know exactly what you'll pay before you sign up. For a deeper breakdown of how different vendors charge, see our &lt;a href="https://rollgate.io/blog/feature-flags-pricing-comparison" rel="noopener noreferrer"&gt;feature flags pricing comparison&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Platform SDK Support
&lt;/h3&gt;

&lt;p&gt;Your stack isn't just React. You need SDKs for your backend (Node.js, Go, Python, Java), your frontend (React, Vue, Angular, Svelte), and your mobile apps (React Native, Flutter). The SDKs should include circuit breakers, retry logic, and local caching out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled Releases
&lt;/h3&gt;

&lt;p&gt;The ability to &lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;schedule a flag change&lt;/a&gt; for a specific date and time — without writing cron jobs or staying awake at 3am. This is a feature that LaunchDarkly offers only on higher tiers, and many alternatives don't offer at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instant Rollback
&lt;/h3&gt;

&lt;p&gt;When something breaks in production, you need to revert a flag to its previous state in one click. Not "create a new flag version," not "redeploy" — one click, instant rollback to the exact previous state.&lt;/p&gt;

&lt;h3&gt;
  
  
  GDPR Compliance with EU Data Residency
&lt;/h3&gt;

&lt;p&gt;If you serve European users, your feature flag data should stay in Europe. LaunchDarkly's infrastructure is primarily US-based. For teams that need GDPR compliance without complex DPAs, an EU-hosted alternative is simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  LaunchDarkly Pricing Breakdown: Why MAU Gets Expensive
&lt;/h2&gt;

&lt;p&gt;LaunchDarkly's pricing model revolves around two axes: &lt;strong&gt;seats&lt;/strong&gt; (the developers who manage flags) and &lt;strong&gt;Monthly Active Users&lt;/strong&gt; (the end users your application evaluates flags for). This sounds reasonable until you look at how MAU scales in practice.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Base plan cost&lt;/strong&gt;: LaunchDarkly's Pro plan starts around $10,000/year. Enterprise plans start around $25,000/year and go well over $100,000/year depending on contract terms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAU tiers&lt;/strong&gt;: Each plan includes a base MAU allotment. Once you exceed it, you pay per additional MAU block. The per-MAU overage cost varies by contract, but published estimates range from $0.01 to $0.03 per MAU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAU counts every unique user&lt;/strong&gt;: Every user who triggers a flag evaluation counts, regardless of how many flags you use. If you have 100,000 active users but only 3 flags, you're still paying for 100,000 MAU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side vs server-side&lt;/strong&gt;: Client-side SDKs report MAU automatically. Server-side SDKs can too, depending on how you pass user contexts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problem is that MAU correlates with product growth, not feature flag complexity. A team with 50,000 MAU and 5 flags pays far more than a team with 1,000 MAU and 200 flags — even though the second team puts more load on the system.&lt;/p&gt;

&lt;p&gt;For startups experiencing growth, this creates budget unpredictability. You might be paying $1,000/month one quarter and $4,000/month the next, simply because your product is succeeding.&lt;/p&gt;

&lt;p&gt;Cheaper alternatives to LaunchDarkly, like Rollgate, avoid this problem entirely by pricing on SDK requests — a metric you can control and predict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flag Tools Compared: Rollgate vs LaunchDarkly vs Alternatives
&lt;/h2&gt;

&lt;p&gt;Here's a side-by-side comparison of the major feature flag platforms in 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Rollgate&lt;/th&gt;
&lt;th&gt;LaunchDarkly&lt;/th&gt;
&lt;th&gt;Flagsmith&lt;/th&gt;
&lt;th&gt;ConfigCat&lt;/th&gt;
&lt;th&gt;GrowthBook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free tier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500K req/mo&lt;/td&gt;
&lt;td&gt;Limited trial&lt;/td&gt;
&lt;td&gt;50K req/mo&lt;/td&gt;
&lt;td&gt;10 flags&lt;/td&gt;
&lt;td&gt;Unlimited (self-hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Paid plans&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;From €39/mo&lt;/td&gt;
&lt;td&gt;From ~$8,333/mo (est.)&lt;/td&gt;
&lt;td&gt;From $45/mo&lt;/td&gt;
&lt;td&gt;From $84/mo&lt;/td&gt;
&lt;td&gt;From $99/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per request (flat)&lt;/td&gt;
&lt;td&gt;Per MAU + seat&lt;/td&gt;
&lt;td&gt;Per request + seat&lt;/td&gt;
&lt;td&gt;Per config fetches&lt;/td&gt;
&lt;td&gt;Per seat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SDKs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;13 official&lt;/td&gt;
&lt;td&gt;25+&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scheduled changes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All paid plans&lt;/td&gt;
&lt;td&gt;Enterprise only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1-click rollback&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All paid plans&lt;/td&gt;
&lt;td&gt;Flag history only&lt;/td&gt;
&lt;td&gt;Audit log&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-hosted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Coming soon&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EU data residency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (Germany)&lt;/td&gt;
&lt;td&gt;US-primary&lt;/td&gt;
&lt;td&gt;Configurable&lt;/td&gt;
&lt;td&gt;EU option&lt;/td&gt;
&lt;td&gt;Self-hosted only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A/B testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (Pro+)&lt;/td&gt;
&lt;td&gt;Yes (add-on)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (core)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Real-time (SSE)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Polling only&lt;/td&gt;
&lt;td&gt;Polling only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on competitor pricing&lt;/strong&gt;: Prices change frequently. Check each vendor's website for current pricing. The estimates above are based on publicly available information as of March 2026.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Real Cost Comparison: 10K, 50K, 100K, 500K MAU
&lt;/h2&gt;

&lt;p&gt;One of the biggest reasons teams look for a LaunchDarkly alternative is sticker shock at scale. Here's a realistic cost comparison across different growth stages, assuming a team of 10 developers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MAU&lt;/th&gt;
&lt;th&gt;Rollgate (est.)&lt;/th&gt;
&lt;th&gt;LaunchDarkly (est.)&lt;/th&gt;
&lt;th&gt;Flagsmith (est.)&lt;/th&gt;
&lt;th&gt;ConfigCat (est.)&lt;/th&gt;
&lt;th&gt;GrowthBook (est.)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10K&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€39/mo&lt;/td&gt;
&lt;td&gt;~$1,000/mo&lt;/td&gt;
&lt;td&gt;~$45/mo&lt;/td&gt;
&lt;td&gt;~$84/mo&lt;/td&gt;
&lt;td&gt;~$99/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;50K&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€39-99/mo&lt;/td&gt;
&lt;td&gt;~$2,500/mo&lt;/td&gt;
&lt;td&gt;~$200/mo&lt;/td&gt;
&lt;td&gt;~$200/mo&lt;/td&gt;
&lt;td&gt;~$99/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;100K&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€99-119/mo&lt;/td&gt;
&lt;td&gt;~$4,000/mo&lt;/td&gt;
&lt;td&gt;~$400/mo&lt;/td&gt;
&lt;td&gt;~$350/mo&lt;/td&gt;
&lt;td&gt;~$99/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;500K&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€299-349/mo&lt;/td&gt;
&lt;td&gt;~$10,000+/mo&lt;/td&gt;
&lt;td&gt;~$1,200/mo&lt;/td&gt;
&lt;td&gt;~$800/mo&lt;/td&gt;
&lt;td&gt;~$200/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;How to read this table&lt;/strong&gt;: Rollgate prices are based on SDK requests, not MAU, so the correlation is approximate. A typical SPA polling every 30 seconds generates ~86K requests/user/month. With caching and SSE (which Rollgate supports), actual request volume is much lower. LaunchDarkly estimates are based on published pricing and community reports — actual costs depend on your contract.&lt;/p&gt;

&lt;p&gt;The pattern is clear: at every growth tier, LaunchDarkly costs 5-30x more than alternatives. For a team at 100K MAU, the difference between Rollgate (~€99/mo) and LaunchDarkly (~$4,000/mo) is roughly &lt;strong&gt;$46,000/year&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's not a rounding error — that's a senior developer's salary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rollgate Is the LaunchDarkly Alternative Built for Growing Teams
&lt;/h2&gt;

&lt;p&gt;Rollgate was built by developers who got tired of paying five figures for a feature flag service. Here's what makes it different.&lt;/p&gt;

&lt;h3&gt;
  
  
  12 Official SDKs with Built-in Resilience
&lt;/h3&gt;

&lt;p&gt;Every Rollgate SDK ships with circuit breakers, retry with exponential backoff, local caching, and graceful degradation. If the Rollgate API goes down, your flags keep working with cached values.&lt;/p&gt;

&lt;p&gt;Here's a React example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rg_client_your_key"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RollgateProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewDashboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;showNewDashboard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LegacyDashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on the backend with Node.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_SERVER_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enableSSE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// real-time updates&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newPricing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-pricing-model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newPricing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;calculateNewPricing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;calculateLegacyPricing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scheduled Changes as a First-Class Feature
&lt;/h3&gt;

&lt;p&gt;Unlike LaunchDarkly where scheduling is buried in enterprise plans, Rollgate makes &lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;scheduled changes&lt;/a&gt; available on every paid plan. Set &lt;code&gt;enable_at&lt;/code&gt; and &lt;code&gt;disable_at&lt;/code&gt; directly from the dashboard or API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Schedule a flag to enable at midnight UTC on launch day&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.rollgate.io/api/v1/projects/:id/flags/:flagId/schedule &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$ROLLGATE_SERVER_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"enable_at": "2026-04-01T00:00:00Z"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also schedule a maintenance window — enable at midnight, disable at 6am — all from the dashboard without writing any code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gradual Rollouts Without Complexity
&lt;/h3&gt;

&lt;p&gt;Rollgate supports &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;percentage-based gradual rollouts&lt;/a&gt; on every plan. Roll a feature out to 5% of users, monitor your metrics, then ramp to 25%, 50%, and 100%. If something goes wrong at any stage, roll back instantly to the previous percentage.&lt;/p&gt;

&lt;p&gt;LaunchDarkly supports this too, but it's part of their premium pricing. With Rollgate, gradual rollouts are available starting from the free tier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flat, Predictable Pricing
&lt;/h3&gt;

&lt;p&gt;Rollgate pricing is based on SDK requests, not MAU. You know exactly what you'll pay:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;th&gt;SDK Requests&lt;/th&gt;
&lt;th&gt;Projects&lt;/th&gt;
&lt;th&gt;Team Members&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;€0&lt;/td&gt;
&lt;td&gt;500K/mo&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;€39-45/mo&lt;/td&gt;
&lt;td&gt;1M/mo&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;€99-119/mo&lt;/td&gt;
&lt;td&gt;3M/mo&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;€299-349/mo&lt;/td&gt;
&lt;td&gt;12M/mo&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A team polling every 30 seconds generates about 86K SDK requests per month per client. Even at scale, Rollgate costs a fraction of LaunchDarkly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What LaunchDarkly Does Better
&lt;/h2&gt;

&lt;p&gt;Let's be honest — LaunchDarkly is the market leader for a reason. If you're evaluating alternatives, you should know where LaunchDarkly genuinely excels:&lt;/p&gt;

&lt;h3&gt;
  
  
  Mature Ecosystem and Integrations
&lt;/h3&gt;

&lt;p&gt;LaunchDarkly has 25+ official SDKs covering virtually every language and framework. They also have deep integrations with tools like Jira, Slack, Datadog, Terraform, and dozens more. If you need a Haskell SDK or a Terraform provider on day one, LaunchDarkly has it.&lt;/p&gt;

&lt;p&gt;Rollgate covers the 12 most-used platforms, which handles 95%+ of teams. But if your stack includes niche languages, verify SDK availability first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enterprise Features
&lt;/h3&gt;

&lt;p&gt;LaunchDarkly offers features built for large enterprise organizations: approval workflows, custom roles with granular permissions, audit log exports, SCIM provisioning, and SSO with every major identity provider. If you have a 200-person engineering org with strict compliance requirements, these features matter.&lt;/p&gt;

&lt;p&gt;Rollgate covers team management, role-based access, and audit logging — enough for most teams — but doesn't yet match LaunchDarkly's enterprise governance depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relay Proxy
&lt;/h3&gt;

&lt;p&gt;LaunchDarkly's Relay Proxy lets you run a local proxy that caches flag data, reducing latency and providing an extra layer of resilience. This is particularly valuable in high-traffic environments or architectures with strict network policies.&lt;/p&gt;

&lt;p&gt;Rollgate's SDKs handle resilience at the SDK level (circuit breakers, local cache, SSE streaming), but a dedicated relay proxy is not yet available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Statistical Experimentation
&lt;/h3&gt;

&lt;p&gt;LaunchDarkly's experimentation add-on includes statistical significance calculations, metric analysis, and guardrail metrics. If you're running high-stakes A/B tests where statistical rigor matters, LaunchDarkly's experimentation is more mature.&lt;/p&gt;

&lt;p&gt;Rollgate offers A/B testing on Pro plans and above, but the statistical analysis tooling is still evolving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bottom line&lt;/strong&gt;: If you're a Fortune 500 company with an unlimited budget and need every possible integration, LaunchDarkly is a safe choice. For everyone else, the question is whether those extras justify 10-30x the cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration from LaunchDarkly to Rollgate
&lt;/h2&gt;

&lt;p&gt;Switching from LaunchDarkly doesn't have to be painful. Here's a practical migration path:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Export Your Flags
&lt;/h3&gt;

&lt;p&gt;Audit your current LaunchDarkly flags. Most teams discover that 30-50% of their flags are stale (old kill switches, completed rollouts, abandoned experiments). Migration is a good time to clean house.&lt;/p&gt;

&lt;p&gt;For each active flag, note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flag key and type (boolean, string, multivariate)&lt;/li&gt;
&lt;li&gt;Targeting rules and segments&lt;/li&gt;
&lt;li&gt;Default values (on/off)&lt;/li&gt;
&lt;li&gt;Which environments use it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Create Flags in Rollgate
&lt;/h3&gt;

&lt;p&gt;Recreate your active flags in the Rollgate dashboard. Rollgate supports the same flag types: boolean, string, number, and JSON. Targeting rules map directly — user attributes, segments, and percentage rollouts all work the same way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Swap the SDK
&lt;/h3&gt;

&lt;p&gt;This is the core change. Here's how LaunchDarkly SDKs map to Rollgate equivalents:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;LaunchDarkly SDK&lt;/th&gt;
&lt;th&gt;Rollgate SDK&lt;/th&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;launchdarkly-js-client-sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@rollgate/sdk-browser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Browser/SPA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;launchdarkly-react-client-sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@rollgate/sdk-react&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@launchdarkly/node-server-sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@rollgate/sdk-node&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Node.js server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;launchdarkly-vue-client-sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@rollgate/sdk-vue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vue 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gopkg.in/launchdarkly/go-server-sdk.v5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;github.com/rollgate/sdk-go&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.launchdarkly:launchdarkly-java-server-sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;io.rollgate:sdk-java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;launchdarkly-server-sdk&lt;/code&gt; (Python)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rollgate-sdk-python&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Python server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;launchdarkly_flutter_client_sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rollgate_sdk_flutter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;launchdarkly-react-native-client-sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@rollgate/sdk-react-native&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 4: Update Flag Evaluation Calls
&lt;/h3&gt;

&lt;p&gt;The API surface is intentionally similar. Here's a typical before/after:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LaunchDarkly (React):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlags&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;launchdarkly-react-client-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;newCheckout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlags&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;newCheckout&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewCheckout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OldCheckout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rollgate (React):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newCheckout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;newCheckout&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewCheckout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OldCheckout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main differences: Rollgate uses &lt;code&gt;useFlag('key', defaultValue)&lt;/code&gt; instead of destructuring from &lt;code&gt;useFlags()&lt;/code&gt;. This makes default values explicit and avoids issues with undefined flags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Run Both in Parallel (Optional)
&lt;/h3&gt;

&lt;p&gt;For critical flags, you can run both LaunchDarkly and Rollgate simultaneously during a transition period. Evaluate flags from both services and log any discrepancies. Once you're confident the values match, remove the LaunchDarkly SDK.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Clean Up
&lt;/h3&gt;

&lt;p&gt;Remove LaunchDarkly SDK packages, delete environment variables, and update your CI/CD pipeline. Cancel your LaunchDarkly contract (check your billing cycle — many contracts auto-renew).&lt;/p&gt;

&lt;p&gt;Most teams complete the migration in &lt;strong&gt;1-3 days&lt;/strong&gt; for small projects and &lt;strong&gt;1-2 weeks&lt;/strong&gt; for larger codebases with multiple services.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Choose Each Alternative
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Rollgate if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want flat, predictable pricing without MAU penalties&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;Scheduled releases&lt;/a&gt; and instant rollback are core requirements&lt;/li&gt;
&lt;li&gt;You need GDPR compliance with EU-hosted infrastructure (Germany)&lt;/li&gt;
&lt;li&gt;You're a startup or mid-size team (5-50 developers) shipping fast&lt;/li&gt;
&lt;li&gt;You want resilient SDKs with circuit breakers and local caching out of the box&lt;/li&gt;
&lt;li&gt;You care about &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;gradual rollouts&lt;/a&gt; as part of your release process&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose LaunchDarkly if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You're an enterprise with budget for $25K+/year and need maximum integrations&lt;/li&gt;
&lt;li&gt;You need 25+ SDKs including niche languages (Haskell, Erlang, Lua, etc.)&lt;/li&gt;
&lt;li&gt;You need built-in experimentation with statistical significance&lt;/li&gt;
&lt;li&gt;You require enterprise governance: approval workflows, SCIM, custom roles&lt;/li&gt;
&lt;li&gt;You need the Relay Proxy for latency-sensitive, high-traffic architectures&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Flagsmith if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to self-host today and want an open-source option (MIT license)&lt;/li&gt;
&lt;li&gt;You want remote config in addition to feature flags&lt;/li&gt;
&lt;li&gt;You need an open API and want full control over your flag infrastructure&lt;/li&gt;
&lt;li&gt;You're comfortable managing your own infrastructure and upgrades&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose ConfigCat if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You have a small team with very few flags and simple targeting needs&lt;/li&gt;
&lt;li&gt;You primarily need simple boolean toggles without scheduling or rollback&lt;/li&gt;
&lt;li&gt;You want a no-frills tool that does the basics well at a reasonable price&lt;/li&gt;
&lt;li&gt;Polling-based updates (no real-time SSE) are acceptable for your use case&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose GrowthBook if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A/B testing and experimentation are your primary use case, not feature management&lt;/li&gt;
&lt;li&gt;You want to self-host and connect your own data warehouse (BigQuery, Snowflake, etc.)&lt;/li&gt;
&lt;li&gt;You need Bayesian statistical analysis built into the platform&lt;/li&gt;
&lt;li&gt;Feature flags are secondary to your experimentation workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Rollgate production-ready?
&lt;/h3&gt;

&lt;p&gt;Yes. Rollgate has been running in production since 2025. The platform handles millions of SDK requests, is hosted on EU infrastructure (Hetzner, Germany), and has built-in resilience at every layer — from the API (health checks, graceful degradation) to the SDKs (circuit breakers, retry logic, local caching). You can &lt;a href="https://demo.rollgate.io" rel="noopener noreferrer"&gt;try the live demo&lt;/a&gt; to see the dashboard and evaluate flags in real time.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does migration from LaunchDarkly take?
&lt;/h3&gt;

&lt;p&gt;For most teams, &lt;strong&gt;1-3 days&lt;/strong&gt; for a small project (one service, a few flags) and &lt;strong&gt;1-2 weeks&lt;/strong&gt; for larger codebases with multiple services and environments. The SDK APIs are intentionally similar, so the code changes are mostly find-and-replace. The longest part is usually auditing and cleaning up stale flags, which you should do anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Rollgate support Server-Side Rendering (SSR)?
&lt;/h3&gt;

&lt;p&gt;Yes. The &lt;code&gt;@rollgate/sdk-node&lt;/code&gt; package supports server-side flag evaluation, which integrates with Next.js, Nuxt, SvelteKit, and any other SSR framework. You can evaluate flags on the server and pass them to the client as initial props, avoiding layout shifts and flicker. The Node SDK supports SSE for real-time updates, so flag changes propagate without redeployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Rollgate pricing compare to LaunchDarkly for a growing startup?
&lt;/h3&gt;

&lt;p&gt;A startup with 50,000 MAU would pay approximately $2,500/month with LaunchDarkly. With Rollgate, the same team would pay €39-99/month depending on SDK request volume. That's a difference of roughly $28,000/year. See the &lt;a href="https://rollgate.io/blog/feature-flags-pricing-comparison" rel="noopener noreferrer"&gt;full pricing comparison&lt;/a&gt; for details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Rollgate with my existing analytics stack?
&lt;/h3&gt;

&lt;p&gt;Yes. Rollgate's SDKs emit events that you can forward to your existing analytics tools (Segment, Mixpanel, PostHog, etc.). Flag evaluations include context about which variant was served, making it easy to correlate feature flags with product metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Rollgate's free tier includes 500K requests per month, 3 projects, all 13 SDKs, and unlimited flags. No credit card required.&lt;/p&gt;

&lt;p&gt;If you're evaluating a LaunchDarkly alternative, &lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;create a free account&lt;/a&gt; or &lt;a href="https://demo.rollgate.io" rel="noopener noreferrer"&gt;try the live demo&lt;/a&gt; to see it in action.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt;, a feature flag platform for developers. If you have questions about feature flags, drop them in the comments — happy to help!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>What Are Feature Flags? A Complete Guide for 2026</title>
      <dc:creator>Domenico Giordano</dc:creator>
      <pubDate>Fri, 03 Apr 2026 12:55:13 +0000</pubDate>
      <link>https://forem.com/domenico_giordano_e441224/what-are-feature-flags-a-complete-guide-for-2026-4ck1</link>
      <guid>https://forem.com/domenico_giordano_e441224/what-are-feature-flags-a-complete-guide-for-2026-4ck1</guid>
      <description>&lt;p&gt;I've been using feature flags for years, and I'm still surprised how many teams ship code without them. Whether you're a solo dev or on a 50-person team, feature flags change how you think about deployments. This is the guide I wish I had when I started — practical, with real code examples in 5 languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Feature Flags?
&lt;/h2&gt;

&lt;p&gt;Feature flags (also called feature toggles or feature switches) are a software development technique that lets you enable or disable functionality in your application without deploying new code. Instead of shipping a feature directly to all users, you wrap it behind a conditional check that can be toggled on or off remotely.&lt;/p&gt;

&lt;p&gt;At its simplest, a feature flag looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;featureFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showNewCheckout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showOldCheckout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key difference from a regular &lt;code&gt;if&lt;/code&gt; statement is that the flag's value is controlled externally — through a dashboard, API, or configuration service — not hardcoded in your source code. This separation of deployment from release is what makes feature flags so powerful for modern engineering teams.&lt;/p&gt;

&lt;p&gt;Feature flags have become a core practice at companies like Netflix, GitHub, Google, and Spotify. They're not just a convenience — they're infrastructure that enables continuous delivery, experimentation, and operational safety at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags vs Feature Toggles vs Feature Switches
&lt;/h2&gt;

&lt;p&gt;If you've been researching this topic, you've probably seen all three terms used interchangeably. Here's the short answer: &lt;strong&gt;they're the same thing&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feature flag&lt;/strong&gt; is the most widely used term, especially in North America and in the context of SaaS platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature toggle&lt;/strong&gt; was popularized by Martin Fowler's influential &lt;a href="https://martinfowler.com/articles/feature-toggles.html" rel="noopener noreferrer"&gt;article on feature toggles&lt;/a&gt; and is common in European engineering circles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature switch&lt;/strong&gt; is less common but still used, particularly in mobile development and gaming.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some teams draw subtle distinctions — for example, using "toggle" for simple on/off switches and "flag" for flags with targeting rules and percentage rollouts — but in practice, the industry treats them as synonyms. Throughout this guide, we'll use "feature flag" as the primary term.&lt;/p&gt;

&lt;p&gt;What matters is not the name but the capability: decoupling code deployment from feature release so you can control what users see without redeploying your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Feature Flags?
&lt;/h2&gt;

&lt;p&gt;Feature flags solve several problems that every growing engineering team faces:&lt;/p&gt;

&lt;h3&gt;
  
  
  Ship Faster, Break Less
&lt;/h3&gt;

&lt;p&gt;Without feature flags, deploying code means releasing it to everyone. This creates pressure to batch changes into large releases, which are riskier and harder to debug. Feature flags decouple &lt;strong&gt;deployment&lt;/strong&gt; from &lt;strong&gt;release&lt;/strong&gt;. You can merge code to main, deploy it to production, and only enable it when you're ready.&lt;/p&gt;

&lt;p&gt;This is the foundation of trunk-based development — a practice where all developers commit to a single branch and use feature flags to hide work-in-progress. No more long-lived feature branches, no more merge conflicts, no more "integration hell." If you're curious about how this compares to branching strategies, check out our guide on &lt;a href="https://rollgate.io/blog/feature-flags-vs-feature-branches" rel="noopener noreferrer"&gt;feature flags vs feature branches&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gradual Rollouts
&lt;/h3&gt;

&lt;p&gt;Instead of flipping a switch for 100% of users, you can roll out a feature to 1%, then 5%, then 25%, then 100%. If something goes wrong at 5%, you turn it off — no rollback, no hotfix, no downtime.&lt;/p&gt;

&lt;p&gt;Gradual rollouts give you confidence. You can observe real-world behavior with a small audience before committing to a full release. For a deep dive on implementation strategies, see our &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;gradual rollouts guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kill Switches
&lt;/h3&gt;

&lt;p&gt;Production incidents happen. A feature flag gives you an instant kill switch. Instead of reverting a commit, waiting for CI, and redeploying, you toggle a flag and the problematic feature is disabled in seconds.&lt;/p&gt;

&lt;p&gt;The difference between a 30-second recovery and a 30-minute recovery is enormous — both for user experience and for your on-call team's stress levels.&lt;/p&gt;

&lt;h3&gt;
  
  
  A/B Testing
&lt;/h3&gt;

&lt;p&gt;Feature flags are the foundation of experimentation. Show variant A to 50% of users and variant B to the other 50%, then measure which performs better. This is how companies like Netflix, Uber, and Spotify make data-driven product decisions.&lt;/p&gt;

&lt;p&gt;With multivariate flags, you can test more than two variants simultaneously — different copy, layouts, pricing displays, or algorithms — and let data guide your decisions. Learn more about combining &lt;a href="https://rollgate.io/blog/ab-testing-feature-flags" rel="noopener noreferrer"&gt;A/B testing with feature flags&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Targeting
&lt;/h3&gt;

&lt;p&gt;Want to enable a feature only for beta testers? Or only for users on the Pro plan? Feature flags with targeting rules let you control exactly who sees what, based on user attributes like email, plan, country, or custom properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled Releases
&lt;/h3&gt;

&lt;p&gt;Some features need to go live at a specific time — a marketing campaign, a product launch, or a compliance deadline. Feature flags with scheduling let you set the exact moment a feature activates without anyone staying up at midnight to press a button. We cover this in detail in our &lt;a href="https://rollgate.io/blog/feature-flags-scheduled-releases" rel="noopener noreferrer"&gt;scheduled releases guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of Feature Flags
&lt;/h2&gt;

&lt;p&gt;Not all feature flags are the same. Understanding the different types helps you use them effectively:&lt;/p&gt;

&lt;h3&gt;
  
  
  Release Flags
&lt;/h3&gt;

&lt;p&gt;The most common type. Used to hide incomplete features in production. Short-lived — removed once the feature is fully launched. These flags enable trunk-based development by letting you merge partially complete work without exposing it to users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ops Flags
&lt;/h3&gt;

&lt;p&gt;Used for operational control. Kill switches, circuit breakers, and maintenance modes. These tend to be long-lived and are critical for system reliability. Examples include disabling a non-essential service during peak load or switching to a fallback provider when a third-party API is down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment Flags
&lt;/h3&gt;

&lt;p&gt;Used for A/B tests and multivariate experiments. They assign users to variants and track metrics. Removed after the experiment concludes and a winner is chosen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permission Flags
&lt;/h3&gt;

&lt;p&gt;Control access to features based on user attributes — plan tier, role, geography. Often long-lived, acting as a dynamic permission system. For example, showing advanced analytics only to Pro plan users, or enabling GDPR-specific data handling for EU users.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Feature Flags Work: Architecture Deep Dive
&lt;/h2&gt;

&lt;p&gt;Understanding the architecture behind feature flags helps you make better decisions about implementation. Here's how a typical feature flag system works end to end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐     ┌──────────────────┐     ┌─────────────┐
│  Dashboard   │────&amp;gt;│  Flag Service    │────&amp;gt;│  Database   │
│  (Web UI)    │     │  (API Server)    │     │  (Postgres) │
└─────────────┘     └──────────────────┘     └─────────────┘
                           │
                    ┌──────┴──────┐
                    │             │
               SSE/Polling    REST API
                    │             │
              ┌─────v─────┐ ┌────v──────┐
              │ Client SDK │ │ Server SDK│
              │ (Browser)  │ │ (Node/Go) │
              └─────┬─────┘ └────┬──────┘
                    │             │
              ┌─────v─────┐ ┌────v──────┐
              │ Your App   │ │ Your API  │
              │ (Frontend) │ │ (Backend) │
              └───────────┘ └───────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Flag Service
&lt;/h3&gt;

&lt;p&gt;At the center is the flag service — the API that stores flag definitions, targeting rules, and percentage allocations. This is where the logic lives: "Enable &lt;code&gt;new-pricing&lt;/code&gt; for 20% of users in the US who are on the Pro plan." The service evaluates rules and returns flag states for a given user context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-Side vs Server-Side Evaluation
&lt;/h3&gt;

&lt;p&gt;There are two fundamental approaches to flag evaluation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server-side evaluation&lt;/strong&gt; — Your backend SDK sends the user context to the flag service (or evaluates locally with a cached ruleset) and returns the computed flag values. This is more secure because targeting rules and flag logic never leave your server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client-side evaluation&lt;/strong&gt; — The browser SDK receives the evaluated flag values from the flag service (not the raw rules). The client knows what's enabled for the current user but doesn't see other users' configurations or your targeting logic.&lt;/p&gt;

&lt;p&gt;Most production setups use both: server-side SDKs for backend logic and API responses, and client-side SDKs for UI rendering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Polling vs Streaming
&lt;/h3&gt;

&lt;p&gt;How does your SDK know when a flag changes?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Polling&lt;/strong&gt; — The SDK periodically checks the flag service for updates (e.g., every 30 seconds). Simple to implement, but introduces latency. If you toggle a flag, it might take up to 30 seconds to propagate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming (SSE/WebSocket)&lt;/strong&gt; — The flag service pushes updates to connected SDKs in real-time via Server-Sent Events or WebSockets. Changes propagate in milliseconds. More complex to operate, but critical for kill switches where seconds matter.&lt;/p&gt;

&lt;p&gt;Most feature flag platforms support both, letting you choose based on your latency requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SDK Pattern
&lt;/h3&gt;

&lt;p&gt;Modern feature flag SDKs follow a consistent pattern regardless of language:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialize&lt;/strong&gt; with a client key and optional user context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache&lt;/strong&gt; flag values locally (in memory, localStorage, or disk)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluate&lt;/strong&gt; flags synchronously from the local cache&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync&lt;/strong&gt; with the flag service in the background (polling or streaming)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit events&lt;/strong&gt; for analytics and experimentation tracking&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means flag evaluation is fast (local cache lookup) and resilient (works even if the flag service is temporarily unavailable).&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags in Different Languages
&lt;/h2&gt;

&lt;p&gt;Feature flags work across any language and framework. Here's what the integration looks like in practice:&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript / React
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PricingPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewPricing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-pricing-page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;showNewPricing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewPricingLayout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CurrentPricingLayout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RollgateClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollgate/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROLLGATE_SERVER_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/recommendations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useNewAlgorithm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-recommendations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useNewAlgorithm&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;newRecommendationEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;currentRecommendationEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollgateClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROLLGATE_SERVER_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;user_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attributes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-search-engine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;new_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;legacy_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/rollgate/sdk-go"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rollgate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserContext&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;UserID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-User-ID"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Attributes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="s"&gt;"plan"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pro"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;serveNewDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;serveCurrentDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Java
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.rollgate.sdk.RollgateClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.rollgate.sdk.UserContext&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;RollgateClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RollgateClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ROLLGATE_SERVER_KEY"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="nc"&gt;UserContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UserContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"plan"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPlan&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEnabled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-payment-flow"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processWithNewFlow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processWithCurrentFlow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is the same everywhere: initialize a client, pass user context, and evaluate a flag. The SDK handles caching, syncing, and resilience under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flag Use Cases
&lt;/h2&gt;

&lt;p&gt;Feature flags are versatile. Here are the most common real-world scenarios where teams use them:&lt;/p&gt;

&lt;h3&gt;
  
  
  Dark Launches
&lt;/h3&gt;

&lt;p&gt;Deploy a feature to production without any user seeing it. The code is live, but the flag is off. This lets you verify that deployment works — database migrations ran, services connect, no errors in logs — before any user is exposed to the change. When you're confident, you flip the flag for a small group.&lt;/p&gt;

&lt;p&gt;Dark launches are especially valuable for major infrastructure changes. You can deploy a new payment provider integration, test it with internal transactions, and gradually shift real traffic without any public announcement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beta Programs and Early Access
&lt;/h3&gt;

&lt;p&gt;Create a segment of beta users who opt in to see new features before general availability. This gives you real feedback from motivated users without the risk of a full release. Flag targeting rules make this trivial: target users where &lt;code&gt;beta_program = true&lt;/code&gt; or where the email ends with &lt;code&gt;@yourcompany.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trunk-Based Development
&lt;/h3&gt;

&lt;p&gt;In trunk-based development, all developers commit to &lt;code&gt;main&lt;/code&gt; (or a single trunk branch). Feature flags hide incomplete work so it can be merged without breaking the application. This eliminates long-lived feature branches, reduces merge conflicts, and keeps the codebase in a continuously deployable state.&lt;/p&gt;

&lt;p&gt;The workflow: create a flag, wrap your work-in-progress code behind it, merge to main, deploy. The flag stays off until the feature is complete and tested. Read more about &lt;a href="https://rollgate.io/blog/feature-flags-vs-feature-branches" rel="noopener noreferrer"&gt;feature flags vs feature branches&lt;/a&gt; and why many teams are making this switch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Canary Releases
&lt;/h3&gt;

&lt;p&gt;A canary release routes a small percentage of traffic to the new version while the majority stays on the current version. If the canary shows elevated error rates or latency, you pull it back. Feature flags enable canary releases at the application level — no need for infrastructure-level traffic splitting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operational Kill Switches
&lt;/h3&gt;

&lt;p&gt;Every external dependency in your system should have a kill switch. If your recommendation engine depends on a third-party ML service, wrap it in a flag. When that service has an outage, disable the flag and fall back to a simpler algorithm. Your users see slightly less personalized results instead of an error page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entitlement Management
&lt;/h3&gt;

&lt;p&gt;Use feature flags as a dynamic permission layer. Free users see the basic editor; Pro users see the advanced editor with collaboration features; Enterprise users see admin controls and audit logs. When a user upgrades their plan, their flag evaluations update in real time — no code change needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compliance and Regional Features
&lt;/h3&gt;

&lt;p&gt;Some features are only legal in certain regions, or need to comply with specific regulations. Feature flags targeting by geography let you enable GDPR consent flows for EU users, disable certain data collection in California for CCPA compliance, or roll out features region by region to meet local requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flag Lifecycle
&lt;/h2&gt;

&lt;p&gt;Every feature flag should follow a defined lifecycle. Flags that linger without clear ownership become technical debt. Here's the lifecycle that high-performing teams follow:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Planning
&lt;/h3&gt;

&lt;p&gt;Before creating a flag, define its purpose, type, and expected lifetime. A release flag for a new checkout flow might have a two-week lifespan. An ops flag for a circuit breaker might be permanent. Document this upfront.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Creation
&lt;/h3&gt;

&lt;p&gt;Create the flag in your dashboard with a descriptive name, a description of its purpose, and an owner. Use a consistent naming convention across your team — &lt;code&gt;feature.checkout-v2&lt;/code&gt;, &lt;code&gt;ops.disable-recommendations&lt;/code&gt;, &lt;code&gt;experiment.pricing-page-layout&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Implementation
&lt;/h3&gt;

&lt;p&gt;Wrap the relevant code paths with the flag check. Keep the flag's scope as narrow as possible — one flag should control one feature, not five. Test both the on and off states.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rollout
&lt;/h3&gt;

&lt;p&gt;Enable the flag progressively. Internal users first, then beta testers, then 5% of production traffic, then wider. Monitor error rates, latency, and business metrics at each stage. Our &lt;a href="https://rollgate.io/blog/gradual-rollouts-guide" rel="noopener noreferrer"&gt;gradual rollouts guide&lt;/a&gt; covers specific strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Full Release
&lt;/h3&gt;

&lt;p&gt;Once you're confident, enable the flag for 100% of users. But you're not done yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Cleanup
&lt;/h3&gt;

&lt;p&gt;This is the step most teams skip — and it's the most important for long-term codebase health. Once a flag has been at 100% for a defined period (one to two weeks is common), remove it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the flag evaluation from your code&lt;/li&gt;
&lt;li&gt;Remove the &lt;code&gt;else&lt;/code&gt; branch (the old code path)&lt;/li&gt;
&lt;li&gt;Delete the flag from your dashboard&lt;/li&gt;
&lt;li&gt;Update any tests that referenced the flag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set a calendar reminder or use your flag service's stale flag detection to ensure cleanup happens. A codebase with 500 stale flags is a codebase no one wants to touch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Feature Flag Mistakes
&lt;/h2&gt;

&lt;p&gt;Feature flags are powerful, but misuse leads to real problems. Here are the mistakes we see most often:&lt;/p&gt;

&lt;h3&gt;
  
  
  Never Cleaning Up Flags
&lt;/h3&gt;

&lt;p&gt;The most common mistake by far. Teams add flags but never remove them. Six months later, no one knows if &lt;code&gt;temp-fix-checkout-2024&lt;/code&gt; is still needed. The code has two paths, both partially tested, and removing either feels risky. Prevent this by assigning an owner and an expiry date to every flag at creation time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flag Sprawl
&lt;/h3&gt;

&lt;p&gt;Creating too many flags without governance leads to combinatorial complexity. If you have 20 flags, there are potentially over a million unique combinations of flag states. Testing all of them is impossible. Keep your active flag count manageable and archive flags aggressively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Only the Happy Path
&lt;/h3&gt;

&lt;p&gt;If your code has a feature flag, it has two code paths. Many teams only test the new path (flag on) and neglect the old path (flag off). When the flag is toggled off in an emergency, the fallback is broken because no one tested it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nesting Flags
&lt;/h3&gt;

&lt;p&gt;Avoid situations where one flag's behavior depends on another flag's state. &lt;code&gt;if (flagA &amp;amp;&amp;amp; flagB)&lt;/code&gt; quickly becomes &lt;code&gt;if (flagA &amp;amp;&amp;amp; flagB &amp;amp;&amp;amp; !flagC)&lt;/code&gt;, and suddenly your application behavior is impossible to reason about. If you find yourself nesting flags, it's a sign your flag design needs simplification.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Flags for Permanent Configuration
&lt;/h3&gt;

&lt;p&gt;Feature flags are for temporary conditions. If something will never be removed — like a config value for page size or a timeout duration — use application configuration, not a feature flag. Conflating the two leads to a flag system bloated with pseudo-config values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Monitoring Flag Impact
&lt;/h3&gt;

&lt;p&gt;Toggling a flag without monitoring is flying blind. Always have dashboards or alerts that correlate flag state changes with key metrics (error rate, latency, conversion). If you enable a flag and errors spike, you need to know immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flag Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keep Flags Short-Lived
&lt;/h3&gt;

&lt;p&gt;Every feature flag is technical debt. Once a feature is fully rolled out, remove the flag and the conditional code. Stale flags clutter your codebase and confuse new team members.&lt;/p&gt;

&lt;h3&gt;
  
  
  Name Flags Descriptively
&lt;/h3&gt;

&lt;p&gt;Use clear, consistent naming. &lt;code&gt;enable-new-checkout-flow&lt;/code&gt; is better than &lt;code&gt;flag-42&lt;/code&gt; or &lt;code&gt;test&lt;/code&gt;. Include the feature area and purpose in the name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a Feature Flag Service
&lt;/h3&gt;

&lt;p&gt;Managing flags through config files or environment variables works for a handful of flags but doesn't scale. A dedicated service gives you a dashboard, audit logs, targeting rules, and real-time updates without redeployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Both Paths
&lt;/h3&gt;

&lt;p&gt;If your code has a feature flag, you have two code paths. Test both. Ensure the application works correctly whether the flag is on or off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor Flag Usage
&lt;/h3&gt;

&lt;p&gt;Track how many users are seeing each variant. Monitor error rates and performance metrics per flag state. This helps you catch issues early during rollouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Ownership and Expiry
&lt;/h3&gt;

&lt;p&gt;Every flag should have an owner (a person or team) and an expected removal date. Review stale flags weekly. If a flag has been at 100% for more than two weeks and no one can explain why it's still there, it's time to clean it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flag Tools: The Landscape
&lt;/h2&gt;

&lt;p&gt;There are several feature flag platforms available, ranging from open-source to enterprise SaaS. Here's a brief overview to help you evaluate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LaunchDarkly&lt;/strong&gt; — The established market leader with enterprise features, strong SDKs, and a mature platform. Well-suited for large organizations. Pricing starts high and scales with seat count and monthly active users (MAUs), which can get expensive quickly. See our &lt;a href="https://rollgate.io/blog/launchdarkly-alternative" rel="noopener noreferrer"&gt;LaunchDarkly alternative comparison&lt;/a&gt; for a detailed breakdown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flagsmith&lt;/strong&gt; — Open-source with a hosted option. Good flexibility, supports self-hosting. The interface can feel complex for smaller teams, and some advanced features require the enterprise tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ConfigCat&lt;/strong&gt; — Simple and affordable. Good for teams that want basic flag management without the complexity of a full experimentation platform. Limited targeting and analytics compared to other options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GrowthBook&lt;/strong&gt; — Open-source, strong focus on A/B testing and experimentation. Great if experiments are your primary use case. The feature flagging capabilities are secondary to the experimentation engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rollgate&lt;/strong&gt; — Built for teams that want a fast, developer-focused flag platform without enterprise pricing. First-party analytics, multi-environment support, and SDKs for all major languages and frameworks. Transparent pricing with a generous free tier. For a detailed pricing comparison across platforms, see our &lt;a href="https://rollgate.io/blog/feature-flags-pricing-comparison" rel="noopener noreferrer"&gt;feature flags pricing comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The right tool depends on your team size, budget, and requirements. What matters most is that you use &lt;em&gt;something&lt;/em&gt; — even a simple implementation is better than no feature flags at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Feature Flags
&lt;/h2&gt;

&lt;p&gt;If you're new to feature flags, start simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick a feature flag service&lt;/strong&gt; — Choose one that fits your stack and budget. You can &lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;try Rollgate for free&lt;/a&gt; or explore the &lt;a href="https://demo.rollgate.io" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; to see how it works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install the SDK&lt;/strong&gt; for your tech stack — most platforms have SDKs for JavaScript, React, Node.js, Python, Go, and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create your first flag&lt;/strong&gt; and wrap a small, low-risk feature — maybe a UI change or a new notification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice the cycle&lt;/strong&gt;: create flag, deploy code, enable for internal users, monitor, full rollout, clean up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Establish team conventions&lt;/strong&gt; — naming patterns, ownership rules, and cleanup cadence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The learning curve is gentle. Most teams have their first flag in production within an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Feature flags are more than just &lt;code&gt;if/else&lt;/code&gt; statements. They're a deployment strategy, a safety net, and an experimentation platform rolled into one. Whether you're a solo developer or part of a large engineering team, feature flags help you ship faster, recover from incidents in seconds, and make product decisions backed by real data.&lt;/p&gt;

&lt;p&gt;The pattern is simple — wrap code behind a conditional, control it remotely — but the impact on your delivery velocity and operational confidence is profound. Teams that adopt feature flags consistently report shorter release cycles, fewer production incidents, and more experimentation.&lt;/p&gt;

&lt;p&gt;If you're ready to get started, &lt;a href="https://app.rollgate.io/register" rel="noopener noreferrer"&gt;create a free Rollgate account&lt;/a&gt; or explore the &lt;a href="https://demo.rollgate.io" rel="noopener noreferrer"&gt;interactive demo&lt;/a&gt; to see feature flags in action.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://rollgate.io" rel="noopener noreferrer"&gt;Rollgate&lt;/a&gt;, a feature flag platform for developers. If you have questions about feature flags, drop them in the comments — happy to help!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
