People usually call an app intuitive when it does what they expected. They click something and the right thing happens. The status on screen matches what's actually happening. If something breaks, they find out early enough to do something about it.
That sounds like frontend work, but it rarely is. The whole system has to agree about what is happening and what should happen next.
One experiment, multiple lifecycles
I ran into this while working on self-serve A/B testing at LinkedIn. The flow looked simple: choose two campaigns, decide what you want to measure, set the budget and duration, then create the test.
Underneath were three things moving on their own. The test owned its duration, budget, criteria, and overall status. Campaign A and Campaign B each owned an objective, audience, placement, schedule, creatives, and delivery state.
Before launch, each campaign had to be valid on its own and the pair had to work together. The whole point of the experiment was to change one thing while keeping the rest comparable. Two campaigns could both be healthy and still make a bad test.
Validation crossed product boundaries. The A/B flow could check its own setup, but campaign health came from the campaign system. Too often, customers made it to launch only for the test to fail without clearly explaining what went wrong or how to fix it.
Launch is only a snapshot
Creating the test only proved that everything lined up at that moment.
After launch, an advertiser could change the test's duration, budget, or criteria. A creative could be rejected, a campaign could be paused, or its schedule could change somewhere else in Campaign Manager. Those changes didn't need to happen inside the A/B testing flow to affect the experiment.
So we kept asking the same questions while the test was live. Are both campaigns still valid? Are they still comparable? Can the test continue as configured?
The A/B flow owned the experiment rules, not campaign validation. We loaded both campaigns through the campaign system, then combined its validation results with our own checks for the test. The interface still needed honest loading, stale, ready, and invalid states without freezing the whole page.
The backend checked the important rules again before creating or updating the test. Once it was running, a lifecycle service listened for campaign events, fetched the latest state, and updated the test when something changed. The interface read that state through the experiment API and showed the advertiser what needed attention.
Events aren't always tidy. Updates can arrive late, out of order, or more than once. Processing them had to be safe, and the latest state mattered more than whichever message showed up last.
Make every change explainable
A budget change might be safe to sync. A rejected creative needed attention. A schedule change could mean the test was no longer able to keep running as configured.
On screen, we tried to make those differences obvious. We put issues close to the choice that caused them, blocked only the next invalid step, and kept the rest of the setup intact. If a campaign changed somewhere else, the testing UI still had to explain what changed and what the advertiser could do next.
Some campaign changes could break a live test. We blocked those actions in the regular campaign flow and sent the advertiser back to the A/B testing flow, where the change could be checked against both campaigns and the test before anything changed.
The result has to earn trust
Keeping these lifecycles together wasn't backend housekeeping. A stale campaign state could keep spend running longer than intended. A missed creative rejection or schedule change could leave the two sides out of balance. By the time the test ended and generated results, the comparison might no longer be useful.
Most advertisers will never see the event handling or validation rules behind this flow. They'll just know whether the product makes sense or it doesn't.
I like working in that gap.