The senior engineer said it plainly: don't touch the pricing system unless you absolutely have to.
That's the kind of warning that travels through teams by osmosis. No one documents it. No one puts it in a ticket. It just exists — a shared understanding that this part of the codebase has weight, that things will break in non-obvious places, and that the blast radius is wide enough to take revenue with it.
We absolutely had to touch it.
What Was Happening
BrowserStack runs pricing across a lot of products — Live, App Live, Automate, Percy, and more. Each product has plans. Plans have prices, feature sets, trial modes, geo-specific variants. The business wants to move fast on monetisation: launch new tiers, experiment with feature fencing, run geo-differentiated pricing, test promotional variants.
None of that was easy. Because the pricing system had no concept of sub-versions.
A plan was a plan. If you wanted to render a plan differently for a different audience segment — say, show a trial user a different CTA, or fence a feature behind a pro tier — you needed code changes. New plan definitions, new rendering logic, new QA cycles. Every product did this separately. The same problem, solved the same slow way, over and over.
The growth team had a backlog of monetisation ideas they couldn't execute because engineering lead time was too high. Not because the ideas were complex. Because the infrastructure to try them didn't exist.
The Actual Problem
Here's what the existing system was doing: plan rendering was tightly coupled to plan definitions. The code that decided how to display a pricing page was the same code that knew what the plan was. No separation.
Want to show a "Most Popular" badge on a plan for a specific campaign? Code change. Want to collapse a pricing table for geo X? Code change. Want to launch a Pro tier that looks like the existing plan but gates different features? New plan definition, new rendering logic, full QA cycle — across every affected product.
Each change took a full dev and QA cycle. That cycle was taking weeks. The business was asking for changes that should take days.
The gap between what the business needed and what the system could deliver wasn't a velocity problem. It was an architecture problem.
The Constraints
I couldn't just rebuild the pricing system. That's not a real option when you have active revenue flowing through it — across multiple products, multiple teams, multiple customer segments. The blast radius of a full rewrite is too high, and the confidence that you've captured every edge case is never high enough to justify it.
The system also had cross-team dependencies I didn't fully control. External product engineering teams touched it. The growth team's experiments ran through it. Any change had to be backward-compatible. Any breakage would show up in revenue.
I also had to ship something that the next person could operate. Not just me. A configuration-driven solution that another engineer could use six months from now without needing to trace through the rendering logic to understand what it was doing.
What I Considered
The obvious answer was to build plan variants — just create new plan records for each sub-version. Product X trial plan, product X trial plan geo variant A, product X trial plan campaign variant B. Schema stays the same, just more rows.
The problem is that this explodes the plan count and doesn't actually solve the coupling problem. The rendering engine still needs to know which plan is which, and you end up with implicit logic scattered across the codebase. It also pushes the management problem to whoever has to maintain the plan catalog over time.
The other option was feature flags. Just use a flag to conditionally show or hide UI elements. Fast to build, already familiar. But feature flags don't compose well when you're trying to control rendering behavior as a function of who the user is and what plan they're on. You end up with flag combinations that are hard to reason about, and you haven't actually modelled the sub-version concept — you've just hardcoded it a different way.
What I kept coming back to: the rendering engine needed to be separated from the plan definition, and sub-version behavior needed to be expressed as configuration, not code.
The Decision
I did a full deep dive into the existing pricing system before proposing anything. I wanted to understand it from first principles — not to fix it, but to understand what it was actually doing and why.
What came out of that: a plan sub-version framework where the rendering layer is controlled through configuration. A plan can have sub-versions. A sub-version is a config object that tells the rendering engine how to display that plan for a specific context — trial vs paid, geo variant, feature-fenced tier, promotional pricing.
The rendering engine reads the config. The plan definition doesn't change. Adding a new sub-version is a config update, not a code change.
I divided the delivery into phases. Not because I wanted to slow things down, but because this was a high blast-radius change and I needed confidence at each step before expanding scope. Phase 1 covered the core framework and the first real use case. Subsequent phases rolled it out to more products as the foundation proved stable.
Built self-healing mechanisms into it — validation on the config layer that catches bad states before they reach rendering. The goal was to minimise the ongoing QA burden, not just the initial build.
What We Gave Up
Configuration-driven systems have a failure mode: they move complexity from code into config. The logic doesn't disappear — it just lives somewhere else, and if that somewhere else doesn't have good tooling, you've just traded one problem for another.
We're operating with the assumption that the teams owning the config understand what they're configuring. That's mostly true now, but it's a risk to manage over time. The config schema needs to stay documented and the abstractions need to remain intuitive.
There's also a testing surface question. Testing a config-driven rendering engine means testing the interaction between config and rendering, not just the rendering logic in isolation. Test coverage had to expand to account for that.
And the phased delivery meant the first launch was narrower than what the business wanted. That required active communication — setting expectations early that the framework wasn't going to unlock everything on day one, but that each subsequent use case would be materially faster to ship.
What Actually Happened
The Price Increase rollout (OS-8217) was the first real test. Dev and QA cycle time came down 50% compared to the traditional method. That's not a marginal improvement — that's a structural shift in how fast the team can move on pricing changes.
The framework is now in production across multiple products. Device feature fencing — every new fenced plan runs on sub-version. Pro plan launches. Pricing changes across Percy, Code Quality, and App Live, each requiring materially less dev and QA effort than the equivalent change on the old system.
The framework is processing around 40K weekly requests. Server response time dropped 20% — not because the new system is faster in isolation, but because the previous coupling meant render logic was doing more work than it needed to.
Launch time for new pricing configurations is down 40%. The growth team is no longer blocked on engineering lead time for the class of changes the framework covers.
The initiative got picked up as an Engineering OKR — scalability and repeatability of the pricing framework as an org-level goal. That happened because the first version proved the concept, and other teams saw what it unlocked.
Ashish Virmani, who owned the growth side of the pricing surface, recognized the project specifically for "optimized launch time for plans with new pricing for SS customers" — the self-serve segment where pricing iteration speed has direct revenue impact.
The team gave me the Black Belt Award — specifically for "outstanding innovation and impact." This wasn't a refactor; it was a capability the business didn't have before.
What Changed
Before this, the implicit assumption in the team was that pricing changes were expensive by nature. High risk, slow to ship, needs careful coordination. That assumption was partly correct — the existing system made them expensive. But the cost was not inherent to the problem.
The shift is that monetisation decisions can now run faster than engineering capacity used to allow. When the growth team comes with a new pricing variant, the question is no longer "how long will it take to build?" It's "is the config right?"
That's a different kind of problem. It's the kind of problem you want to have.
The part I keep thinking about: the system that existed wasn't designed by people who didn't care. It was designed for a different set of constraints — probably before BrowserStack had 10 products, before geo-pricing was a real need, before the growth team was running this many monetisation experiments. It made sense when it was built. It just hadn't been updated to match what the business actually needed now.
Most legacy system problems aren't mistakes. They're solutions that outlived their assumptions.
The job is to notice when that's happened — and to do something about it before the cost of staying quiet becomes higher than the cost of the rewrite.
Recognition
