Build if your needs are genuinely simple and will stay simple: a few boolean switches, one service reading them, engineers doing the flipping, no percentage rollouts. Buy the moment you need consistent bucketing across services, attribute based targeting, an audit trail, or a UI that someone who is not an engineer will use. Most teams cross that line sooner than they expect.
Most vendor "build vs buy" posts are a rigged trial where buy wins. This one is written by people who built one in-house, including the parts we got wrong.
Should you build your own feature flag system?
Yes, if your flags are on/off switches read by a single codebase, changed by engineers, and never need to split traffic. A config value or a small flags table with a short cache is a legitimate v1. Build it, use it, and buy something the day it stops being enough.
The honest v1 is about two hundred lines of code. A table with key, enabled, updated_at. A repository that reads all rows and caches them for 60 seconds. A helper called flag('checkout.new_flow'). An admin page behind your existing auth, or none at all if you are fine running an update statement. That will serve five flags for a year, and if you deploy daily, even a config file boolean flips in minutes.
Do not let anyone, us included, talk you out of that. Paying a platform bill to manage five booleans only engineers touch is a bad trade. The real question is what happens when the requirements grow, which they reliably do.
What actually breaks, and when
In-house flag systems rarely fail all at once. They fail one requirement at a time, and each looks small in isolation. Here is the usual sequence, in the order teams hit it.
| The new requirement | What your system now needs |
|---|---|
| A second service reads the same flag | One source of truth, identical evaluation in two languages |
| "Roll this out to 10% of users" | Stable hashing, so the same 10% stays the same 10% everywhere |
| A flag check lands on a hot path | In-process evaluation, background sync, safe defaults when stale |
| "Pro plan, US only, excluding the beta blocklist" | A rules engine, with versioning and rule preview |
| An incident traced back to a flag flip | An audit log and role based access, retrofitted |
| A PM wants to flip it herself | A real UI, permissions, separate environments |
| 40 flags, and nobody knows which are dead | Ownership, staleness reports, someone who owns cleanup |
| "Did variant B actually convert better?" | Sticky assignment, exposure events, stats that resist peeking |
Every row is a week or three of work, and none of them ship product. Ask where your team will be in nine months. Row one, build. Row four, you are about to write a rules engine you never budgeted for.
What is the hardest part of building feature flags in-house?
Consistent bucketing and local evaluation. A percentage rollout has to put the same user in the same bucket on every service, every page load, and every deploy, without making a network call on the request path. That is a distributed systems problem, and it is where homegrown systems quietly break first, usually without anyone noticing.
Consistent bucketing is the bug everyone ships
The naive percentage rollout uses rand(0, 99) < 10. It looks right in a test and it is completely broken: the same user gets the new checkout on one page load and the old one on the next. Bug reports become unreproducible.
So you switch to a stable hash. The second bug is subtler and far more common: hashing only the user ID. Then every flag buckets users identically, and the same unlucky 10% are the guinea pigs for every rollout you ever run. Your beta cohort and your churn cohort slowly become the same people. Hash the flag key with the user ID and each flag gets its own stable assignment:
// Deterministic bucket in 0..99 for one user and one flag.
// Including flagKey in the hash means a user who lands in the
// unlucky bucket for one flag does not land there for every flag.
function bucket(flagKey, userId) {
const digest = sha1(`${flagKey}:${userId}`); // stable across processes, deploys, languages
const slice = parseInt(digest.slice(0, 8), 16); // first 32 bits
return Math.floor((slice / 0x100000000) * 100); // 0..99, no modulo bias
}
// A 10% rollout. Same user, same answer, every time, in every service.
const enabled = bucket('checkout.new_flow', user.id) < 10;
The division avoids modulo bias (2^32 does not divide evenly by 100). This snippet still lacks a salt, so you can re-randomize a rollout without reusing the last cohort, a bucketing key for anonymous visitors, and sticky assignment that survives a login. It also has to be identical in every language you ship. If Go and Node disagree about the tenth bucket, your gradual rollout is silently inconsistent and you hear about it from a support ticket.
The request path, and the fail-open problem
The next thing teams build is an HTTP call inside flag(). It works fine in dev. In production it adds latency to every flag check and makes the flag service a hard dependency of your whole application. The system whose job is to protect you from outages can now cause one.
The correct design is the boring one: pull the whole ruleset into memory on boot, keep it fresh by polling or streaming updates, and evaluate locally, in-process, in microseconds. When the config is stale or was never fetched, serve a safe default rather than throwing or blocking. That is the difference between a kill switch you can trust during an incident and one that fails exactly when you reach for it, and building it well means handling cold starts, propagation delay, and serverless functions with 40 milliseconds to live.
Targeting turns into a rules engine
"10% of users" is arithmetic. "Pro plan, in the US, signed up after March, excluding the beta blocklist" is a rules engine, with operators, types, precedence, and a serialization format. Then someone wants to preview which users a rule matches. Then someone edits a rule and breaks production, so you need versioning and a diff. Build this and you are maintaining feature toggle management software as a side project alongside your actual product.
The parts nobody puts in the estimate
Audit and access. Something breaks, and the first question in the incident channel is "who turned that on?" A table with no audit log answers "nobody knows." Adding one later means backfilling history you no longer have, plus role based access so an intern cannot flip the billing flag.
Flag lifecycle. Every flag is a branch in your code, and dead ones never announce themselves. Without an owner, a review date, and reporting on what has not been evaluated in 90 days, you accumulate permanent conditional logic nobody dares delete. That discipline is what makes flags beat long-lived branches, covered in feature flags vs feature branches.
The maintenance tax. The build is a sprint. The ownership is forever. Someone is on call for it. Someone writes the SDK for the second language, adds the third environment, builds the UI the PMs keep asking for, then supports the mobile client that cannot poll every 30 seconds. None of it is hard, all of it is real, none of it was in the ticket.
How long does it take to build a feature flag system?
A weekend for a flags table and a cache. Realistically four to eight weeks of senior engineering time for the version people actually want: consistent bucketing, local evaluation with safe defaults, a targeting layer, an audit trail, environments, and a UI a PM can use. That is the build. The upkeep does not end.
Here is the arithmetic, an illustrative estimate rather than research. Plug in your own numbers. Take a fully loaded senior engineer at, say, $3,000 to $5,000 per week: six weeks of build is roughly $18,000 to $30,000. Then add ownership, where even a half day a month on upkeep and incidents is tens of thousands more over three years, assuming nothing goes wrong. Compare that to a subscription at a few hundred dollars a month, and put the opportunity cost on the scale too.
The build looks cheap because its cost is spread thin and paid in a currency nobody invoices. It only becomes visible once someone totals it, the same way most teams only discover what their cloud and SaaS spend actually adds up to after somebody finally adds up the invoices. Run the numbers and the answer will sometimes still be "build." Just make it a decision, not a default.
When should you buy a feature flag tool instead of building one?
Buy when flags stop being engineer only booleans. The trigger points are specific: percentage rollouts across more than one service, attribute based targeting, non-engineers flipping flags, an audit trail for security review, or A/B tests you need to defend statistically. Any one of those makes buying cheaper than building within a quarter.
The checklist, ready to paste into Slack:
| Build in-house if | Buy a platform if |
|---|---|
| You have fewer than about 10 flags, and no plan for more | Flags are becoming a normal part of how you ship |
| One codebase, one language reads them | Two or more services must agree on the same evaluation |
| Flags are on/off only, no percentages, no targeting | You need percentage rollouts or attribute based targeting |
| Only engineers flip them | PMs, support, or marketing need to flip them safely |
| You deploy often enough that a config change is fast enough | You need to kill a feature in seconds during an incident |
| Nobody will ever ask "who changed this?" | Security review, SOC 2, or a postmortem needs an audit log |
| You have no experimentation ambitions | You want real A/B tests with sticky assignment and honest stats |
| A platform team already owns shared infrastructure and wants this | Your engineers are your scarcest resource |
Six on the left means build the small thing and get back to work. Six on the right means buy. A rough split means build the simple version now, but keep evaluation behind a thin wrapper so you can swap in a platform later without touching call sites. That wrapper costs nothing today and turns a future migration into a config change.
There is a third option: self-hosting an open source tool like Unleash or Flagsmith. It removes the build cost while keeping full control of your data, and it is the right call under residency requirements. It also means running tier zero infrastructure, which we worked through in open source feature flags vs managed platforms. If you are evaluating vendors, our rundown comparing the main feature flag tools puts them side by side. Read pricing models as carefully as feature lists: per-seat pricing punishes the behavior you actually want, which is giving everyone access.
Where we fit, honestly
Toggled is a managed feature flags platform with A/B testing built in rather than bolted on, so bucketing, exposure tracking, and the statistics come from the same assignment your flags already use. Planned pricing is flat per team, with unlimited seats and flags, because charging per seat to give your PM a button is a strange way to run a business.
We are in early access, not launched, and that should factor into your decision. If your flag needs are five booleans in a config file, keep them there and come back when row four of that first table lands in your sprint planning. If you are staring down consistent bucketing across three services and a rules engine you never wanted to write, that is where paying someone else to have already solved it stops looking like a purchase.