Feature Flags and Trunk-Based Development
Deploy is not release. Here is how trunk-based development and feature flags let teams merge continuously, ship daily, and keep risk small.
By Innovation T Team
Most teams that struggle to ship are not slow because their engineers are slow. They are slow because their branches live for weeks, their merges are terrifying, and every release is a big-bang event that someone has to babysit at 2 a.m. Trunk-based development and feature flags attack that problem from two sides: keep the code integrated continuously, and separate the moment you deploy from the moment you turn a feature on.
Done well, this combination is the quiet engine behind teams that release many times a day with a calm on-call rotation. Done carelessly, it turns your codebase into a maze of stale toggles nobody dares delete. This guide covers both sides honestly.
Deploy is not release
The single most useful idea here is that deploying code and releasing a feature are two different events, and you should be able to do one without the other.
In the old model they are welded together. Code reaches production, and at that instant users see the new behavior. That coupling is what makes releases scary, because the only way to test in a real environment is to expose everyone at once, and the only way to undo a bad release is to redeploy.
Break the two apart and the fear drains away. Code ships to production dark, wrapped in a flag that is off. It sits there, exercised by your test suite and health checks, touching nothing a user can see. When you are ready, you flip the flag for one percent of traffic, watch your metrics, then ramp to ten, fifty, and a hundred. If something looks wrong, you flip it back in seconds without a deploy. Release becomes a runtime decision made by a human watching dashboards, not a mechanical side effect of a push.
What trunk-based development actually means
Trunk-based development is a branching model where everyone commits to a single shared branch, the trunk, at least once a day. Branches, if they exist at all, live for hours and are merged the same day.
The point is not to be dogmatic about branches. The point is to keep the distance between any developer's work and the shared mainline as short as possible, because that distance is where integration pain compounds.
Contrast this with long-lived feature branches. A branch that survives for two weeks silently drifts from trunk. By the time you merge, you are reconciling the accumulated divergence of the whole team, and the bigger the merge, the higher the chance something breaks. Trunk-based development refuses to let that debt accumulate. You integrate small, you integrate often, and every integration is boring.
The obvious objection is: how do I merge code for a feature that is only half finished without breaking production? That is exactly the gap feature flags fill.
Where feature flags fit
A feature flag is a conditional in your code that decides at runtime whether a piece of behavior is active. At its simplest it is one line: if the flag is on, run the new path; otherwise run the old one. That tiny indirection is what lets you merge incomplete work to trunk safely, because the new path is dark until you choose to light it up.
Not all flags are the same, and treating them as one thing is a common mistake. They have different lifespans and different owners.
The four kinds of flags
- Release flags hide work in progress so it can be merged before it is finished. They are short lived by design and should be deleted the moment a feature is fully rolled out. This is the flag that makes trunk-based development possible.
- Operational flags are kill switches for risky subsystems: a heavy recommendation engine, a flaky third-party integration, a new caching layer. They are long lived on purpose, because you want the ability to shed load or disable a dependency during an incident without a deploy.
- Experiment flags power A/B tests. They route cohorts of users to different variants and stay in place for the life of the experiment, then get cleaned up when a winner is chosen.
- Permission flags gate features by plan, role, or account, for example an enterprise-only export. These are effectively permanent and belong closer to your entitlements logic than your delivery pipeline.
Confusing these categories is where flag systems go wrong. A release flag that is treated like a permission flag never gets deleted, and six months later nobody remembers whether it is safe to remove.
A workflow that holds up in 2026
Here is a concrete loop we use and recommend for teams moving to continuous delivery. It assumes a solid test suite and a real flagging service rather than scattered environment variables.
- Cut the work into thin vertical slices. Break the feature into pieces small enough to merge in a day, each one a complete path through the stack even if it only serves internal users at first.
- Wrap the new path in a release flag, defaulted off. The very first commit introduces the flag. Everything after it modifies code that is already dark in production.
- Merge to trunk daily behind the flag. Your changes reach production continuously, exercised by CI and integration tests, but invisible to users because the flag is off.
- Turn the flag on internally first. Enable it for your own team and staff accounts. Dogfood the real thing in the real environment before any customer sees it.
- Ramp with a percentage rollout. Move to one percent of traffic, then a wider cohort, watching error rates, latency, and the business metric the feature is supposed to move.
- Watch guardrail metrics at each step. Tie the rollout to alerts. If error budgets burn or a key metric regresses, you have a signal to pause or roll back before most users are affected.
- Roll back by flipping, not deploying. If something breaks, turn the flag off. The fix is instant and blameless, and you debug at leisure with the code still safely in trunk.
- Retire the flag once fully rolled out. After a feature is at a hundred percent and stable, delete the flag and the dead branch of code in the same sprint. This step is not optional.
That last step is where most teams fail, so it deserves its own discipline.
Flag hygiene: paying down flag debt
Every release flag is a small debt. Each one adds a branch in your code, a state your tests must cover, and a line of cognitive load for the next person reading the file. A handful is fine. Several hundred stale toggles is a second, invisible codebase that nobody understands and everybody is afraid to touch.
Keep the debt low with a few habits:
- Give every release flag an expiry date and an owner at creation time. A flag with no owner is a flag no one will ever remove.
- Track flag age and treat anything past its expiry as a bug in your backlog, not a nice-to-have cleanup.
- Delete the code, not just the toggle. When you remove a flag, delete the losing branch so the winning path becomes the only path.
- Cap the number of active release flags per service. A hard limit forces cleanup before new work piles on.
- Audit permission and operational flags separately, since those are meant to live long and should not be swept up in release-flag cleanup.
In our experience, the teams that stay fast are not the ones that add flags fastest. They are the ones that remove them just as fast.
Tradeoffs and failure modes
This model is not free, and pretending otherwise sets teams up to be burned.
- Testing combinations explodes. With many flags, the number of possible states grows quickly. You cannot test every combination, so test the paths that matter: current production state, and each flag's on and off values in isolation.
- Flag evaluation must be fast and fail safe. Your app reads flags on the hot path, so a slow or unavailable flag service cannot be allowed to take down requests. Cache flag values locally and define a sensible default for when the service is unreachable.
- Flags are a security surface. A permission flag that gates a paid feature is an access-control decision. Evaluate it on the server, never trust a flag value sent from the client, and log changes to sensitive flags.
- Trunk-based development demands real tests. The whole model rests on a trunk that is always releasable. Without fast, trustworthy automated tests and continuous integration, merging to trunk daily just spreads breakage faster.
These are the same disciplines that show up whenever you decompose a system for independent delivery. If you are also weighing how far to split your architecture, our guide on going from monolith to microservices covers the organizational signals that make independent deployment worth the cost, and the flag mechanics here are how you ship those services safely once you have them.
Flags also ride along your API boundaries, so the contracts you expose matter. When a flagged change alters a response shape or endpoint behavior, the principles in our piece on designing APIs developers love keep you from shipping a breaking change behind an innocent-looking toggle.
How Innovation T can help
Trunk-based development and feature flags are less a tool purchase and more a change in how a team works: small merges, continuous integration, progressive rollout, and ruthless cleanup. Getting there usually means tightening the test suite, hardening the delivery pipeline, and picking a flagging approach that fits your scale rather than the biggest name on the market.
At Innovation T, that is exactly the kind of engineering work we do. We help teams move from long-lived branches and dreaded releases to a calm, continuous flow: setting up trunk-based workflows, wiring feature flags into a CI and CD pipeline, defining rollout and rollback playbooks, and building the flag hygiene that keeps the codebase clean a year later. Explore our software and cloud engineering services, or get in touch to talk through how your team ships today and where the friction really lives.
Ready to build with Innovation T?
Whether it is security, growth or engineering, our team can help you ship it well.