The interval
Can this test see the thing you are hoping for?
Start at the end, because the arithmetic below decides whether the rest of the post is worth running. Three controls: how big the audience is, how much of it you are willing not to market to, and how big the effect really is.
↳ Leave the effect at +1.2% and drag the audience all the way to 5M. The interval still spans zero — that lift is not measurable at any audience a brand actually has.
The design floor is the lift this arrangement is built to catch four times out of five. It is a property of the arithmetic, not of the campaign, and you can compute it before spending a cent — which is the entire reason to compute it first.
A large share of the tests people actually run cannot detect the effect they are hoping for, and instead produce a number, with a decimal point, that is noise. You can know that before you spend a cent — which is the entire reason to compute it first.
The problem
Last click makes your best channel the one that would have happened anyway
Follow one shopper. She has a cart from Tuesday, an email in her inbox, and every intention of buying. On Thursday she types the brand name into a search box, clicks the ad at the top because it is at the top, and completes the order. Last click hands that channel the entire purchase. Nothing about the ad changed her mind; it was standing between her and a checkout she was already walking toward.
Every order from someone who added to cart four days ago and was always going to come back, counted as revenue the ad produced.
The same audience, the same window, compared against people just like them who were never shown the ad at all.
- Attribution divides credit, never causation. Every model in the family takes the set of conversions that happened and splits them among the touchpoints that preceded them. The denominator is fixed before the model runs.
- Multi-touch does not fix the selection. First-touch, linear, time-decay and data-driven models redistribute credit among touchpoints that all share the same problem — the people who saw them were the people most likely to convert.
- There is no counterfactual in the data. These models are fit on observational logs. Nowhere in that log is a comparable person who was not exposed, so no amount of modelling can recover the difference.
- The feedback loop is self-sealing. Brand search reports nine times return, gets more budget, and reports nine times on the new budget too — because the extra spend bought more clicks from people who were already going to buy.
Be fair to attribution. It is genuinely useful for diagnosis and pacing: which creative is getting clicks today, whether spend is tracking to plan, where a sudden drop in traffic came from. Those are real jobs. It is just not evidence that money caused revenue, and it should never be the number in a sentence containing the word “drove”.
The holdout
Hold out in your platform, not in theirs
A holdout is the counterfactual you buy by not spending. The mechanism is one line of arithmetic: hash the profile id together with the experiment id, take it modulo ten thousand, and compare against the holdout size. Nothing is stored, so nothing can be lost, and the assignment can be re-derived from first principles by an analyst months later who was not in the room.
- Deterministic beats random-and-stored. A hash of two strings is stable across runs, across restarts, and across a warehouse migration. A stored random assignment is a table someone can truncate.
- Salting by experiment keeps tests independent. The same profile in the holdout for one test is not systematically in the holdout for the next, because the hash input changed.
- The sync engine subtracts it, not the audience definition. Membership is compiled, then the holdout is removed on the way out. Someone editing the audience SQL cannot re-include the control group, and a second overlapping audience cannot leak treatment in through a side door.
- The experiment record is the contract. Audience, destination, holdout size, start, planned end, target effect size and owner are written once, before the first sync, and the analysis is read against them.
The overlap trap deserves its own paragraph, because it is silent. Two experiments run on populations that intersect — a prospecting test on one audience and an email test on a lifecycle audience sharing forty percent of its members — and neither knows about the other. Each treats the other’s treatment as noise, which inflates variance and hides a real effect. Worse, if the overlap is uneven between arms, one test reads the other’s lift as its own. The fix is boring: a registry of running experiments per brand, and a check at creation that refuses or flags an intersection above a threshold.
Power
Decide the sample size before you look
The uncomfortable arithmetic goes first, not last. At a three percent baseline conversion rate, detecting a five percent relative lift — three percent against 3.15 percent — needs roughly two hundred thousand profiles per arm. Most audiences on most brands are not that big, which is exactly what the interactive at the top of this post is showing you.
- The target effect size is a design input, not a result. Compute it from the audience you have, and if the answer is “we could only detect a thirty percent lift”, say so before spending.
- Peeking is not significance. Checking daily until the number goes green is a different test than the one you designed, and its real false-positive rate is several times the one you think you are running at.
- Fix the end date and the metric up front. Both live in the experiment record. An analysis that cannot choose its own window or its own metric has no room to drift.
- Underpowered is worse than not running. A null from an underpowered test gets read as “it does not work” and a false positive gets read as proof. Both move budget.
The readout
Incremental revenue, incremental cost per acquisition, and an interval
The query is unglamorous, which is the point. Assign every profile in the compiled audience to an arm with the same hash the sync engine used, join conversions in the window, and compare per-profile means. No attribution model appears anywhere.
Two things to say out loud. A result whose interval includes zero is a result — it says the effect, if any, is smaller than this test could see, and it should be reported in exactly those words rather than quietly not reported. And a negative point estimate on a retargeting audience is the single most common honest finding in this whole discipline; it usually means you were paying to reach people already on their way to checkout, and the ads mostly bought attention you already had.
The governance tie-back is short. A holdout is a group of real people you deliberately chose not to market to, for a quarter, to learn something. That is a decision with a subject, an owner and a date, and it belongs in the same append-only audit log as every sync and every deletion — the holdout size and experiment id are already fields on post 4’s audit record.
Teaching-grade reference implementation, not a production customer data platform. It reproduces the ideas and the streaming/warehouse integration shape; bring your own data and destination credentials. Destination adapters run against a local mock by default. MIT-licensed. View the repo →
What you have now
- Collection you control at the edge. A first-party server-side collector, server-stamped time, and events keyed so a device's history stays ordered — not a pixel whose behaviour a browser vendor decides for you.
- Identities that can be un-merged. Profiles derived from a link graph with provenance on every link, so the merge that was wrong is a removable observation rather than permanent damage.
- Features that do not leak the future. One feature definition, two paths, computed as of the start of the label window — the difference between an offline number you brag about and an online lift that exists.
- Consent enforced by structure, deletion that fans out. Consent as a join key with no code path around it, and a tombstone that reaches your warehouse, your graph, your audiences and every destination you ever synced to.
- Syncs that send deltas and report their own match rate. Diffs rather than full replaces, idempotent batches with checkpoints, dead-lettered rejects, and a freshness target that catches the expired token before the month does.
- A number that survives “compared to what?”. A deterministic holdout the sync engine enforces, a power calculation done first, and a readout with an interval on it.
That is the platform, and it is honest to say the parts shown here are the easy parts. A stream consumer, a component solver, a diff engine, a hash-based holdout — these are solved problems with known shapes, and a competent team can build all of them in a quarter. The hard part in any real company is none of that. It is the organisational agreement about what a customer is: whether a household counts as one, whether an agency’s login counts as your marketer, whether a returned order still makes someone a buyer, whether last quarter’s consent still applies. Every one of those is a product decision wearing an engineering costume, and no amount of correct code resolves them. Build the pipeline so that when the answers change, you can change them in one place — and so that the audit log can always say who decided, and when.
Explain it back
Reveal a model answer
The sentence reports a point estimate as if it were the finding. It is not — the interval contains zero, and it contains −4%, so the test is equally consistent with the audience losing money. “1.2%” is the centre of a range whose honest summary is “we could not detect an effect”. The mechanism is that the test was underpowered for the effect size that matters: at a retargeting-scale audience and a low baseline conversion rate, the smallest lift this design could resolve was several times larger than anything plausible.
The readout should have said: no detectable effect, 95% interval −4% to +6%, smallest detectable lift 9%, so effects below that are invisible to this test.
Next: do not scale on this. Either pool across a longer window or several brands to buy power, or accept that a channel this size cannot be measured at five percent resolution and test something coarser — hold out the entire audience for a month and measure the total. The bonus consequence is political rather than statistical. Once “1.2% lift” is in a channel, it gets pasted into a deck, and it becomes a number nobody can retract without seeming to attack the team that produced it. The interval is what stops that, which is why it belongs in the sentence and not in a footnote.
Every stage in this platform is a bet that two sessions were the same person. Re-read the map with all six posts behind you and the chain reads differently — every hop is now a place you know how to lose someone, and how not to.
Back to the map →