How do I optimise my marketing budget for next year?

A visual walk-through of Marketing Mix Modeling (MMM) with Google's Meridian — how a CMO's hardest budget question gets answered with data.

Every CMO eventually faces the same deceptively simple question from the CFO: "If I give you the same budget next year, can you make it work harder — and where should the money go?" It sounds like a spreadsheet exercise. It isn't. Answering it well means untangling which channels actually caused sales versus which merely took credit for them.

That used to be the job of last-click attribution and multi-touch attribution (MTA). But with third-party cookies fading, mobile identifiers restricted (Apple's ATT, Google's Privacy Sandbox), and walled gardens reporting their own homework, user-level tracking no longer sees the whole picture. This is exactly why Marketing Mix Modeling (MMM) — a privacy-safe, aggregate approach that predates cookies — is having a major revival.

Below, I answer the budget question end-to-end for a fictional brand, using Google Meridian, Google's open-source Bayesian MMM library. The numbers are illustrative, but the method is exactly what I'd run in production.

tldr;

By modelling two years of weekly data, our fictional brand discovers that Meta and programmatic display are saturated (extra dollars barely move the needle) while TikTok and YouTube are underfunded (every extra dollar returns well above average). Reallocating ~20% of the media budget — without spending a cent more — is projected to lift incremental revenue by +11%.

The scenario

Meet "Kirana" — a fictional D2C brand

Kirana Co. is a made-up direct-to-consumer skincare brand selling online across Southeast Asia. It spends about US$6M a year on media across eight channels, from brand-building TV down to lower-funnel search. The CMO wants next year's plan to be defensible to the board.

$6M
Annual media budget
8
Channels to balance
104
Weeks of history (2 yrs)
The approach

Six steps, one loop

MMM isn't a one-off report — it's a loop you refresh as new data arrives. Here's the whole journey before we zoom into each step:

flowchart LR
  A["1 · Frame the
decision"] --> B["2 · Acquire &
shape data"] B --> C["3 · Model the
two forces"] C --> D["4 · Configure in
Meridian"] D --> E["5 · Validate
& trust"] E --> F["6 · Read results
& optimise"] F -.->|"refresh quarterly
with new data"| B F ==> G(["Budget
decision"])
The MMM workflow — a repeating loop, not a single deliverable.
Step 1

Frame the decision before touching data

The most common way MMM projects fail is skipping this step. Before any modelling, we pin down three things:

  • The KPI: what are we optimising? For Kirana, it's weekly online revenue.
  • The decision: how to split a fixed ~$6M across eight channels for the next four quarters.
  • The constraints: real-world guardrails — e.g. "don't cut brand/TV below 15%", "TikTok can't more than double", contract minimums on out-of-home.

A model that ignores constraints produces answers no one can execute. Naming them now makes the final recommendation actionable.

Step 2

Acquire & shape the data

MMM works on aggregate, time-series data — no personal data, no cookies, no user IDs. That's the privacy superpower. We assemble two years of weekly rows combining three ingredients:

Ingredient Examples Why it matters
Media Spend & impressions per channel (TV, YouTube, Meta, TikTok, Search, Display, OOH, Affiliates) The levers we control and want to value
The outcome Weekly online revenue (the KPI) What we're trying to explain and grow
Controls Price/discounts, seasonality & holidays, competitor activity, macro trend So credit isn't wrongly given to ads during a sale or festive spike

Meridian can also model at the geo level (e.g. per country or region) rather than one national line. More geographies means far more signal for the model to learn from — the statistical equivalent of running many parallel experiments. Kirana feeds in six markets.

Rule of thumb

Aim for 2–3 years of weekly data and enough spend variation across channels. If a channel's budget never changes, the model can't learn what happens when it does.

Step 3

The two forces every MMM must capture

Advertising doesn't convert like a vending machine. Two real-world effects make the spend-to-sales relationship non-linear, and modelling them is what separates a credible MMM from a naïve regression.

flowchart LR
  S["Media spend
this week"] --> AD["Adstock
(carryover)
a TV ad seen today
still drives sales
next week"] AD --> SAT["Saturation
(diminishing returns)
the 2nd million spent
does less than
the 1st"] SAT --> INC["Incremental
revenue"]
Every channel's contribution is spend → adstock → saturation → incremental revenue.

Adstock — advertising has a memory

A brand campaign you see on Monday can still nudge a purchase the following week. Adstock captures this carryover, decaying over time. TV and YouTube have long memories; a search ad's effect is almost immediate.

Saturation — dollars hit a ceiling

The first $100k on a channel reaches the most receptive audience. The next $100k reaches slightly less receptive people, and so on — returns flatten. Saturation (modelled with an S-shaped/Hill curve) is the concept behind budget optimisation: the goal is to spend on each channel only up to the point where the next dollar still pays off.

Step 4

Configure the model in Meridian

Meridian is Bayesian, which matters for one very practical reason: it lets us start from what we already believe (a prior) and let the data update it. For a marketer, the priors are just structured common sense — "we're fairly sure search returns more than display" — expressed as numbers.

Here's the essence of the setup (lightly simplified):

# 1 · Tell Meridian which columns are media, KPI, and controls
spec = ModelSpec(
    kpi_type="revenue",
    media=["tv", "youtube", "meta", "tiktok",
           "search", "display", "ooh", "affiliates"],
    controls=["price_index", "holiday", "competitor_spend"],
)

Naming the levers (media), the goal (revenue), and the noise to control for.

# 2 · Encode prior beliefs about ROI — and calibrate with real experiments
priors = PriorDistribution(
    roi_m="lognormal",        # ROI can't be negative
    # A recent TikTok lift test measured ~3.4x ROI — anchor the prior to it
    tiktok_roi_mean=3.4,
)

mmm = Meridian(input_data=data, model_spec=spec, prior=priors)
mmm.sample_posterior()   # the model learns from the data

The single highest-leverage move in MMM: calibrating priors with incrementality experiments (lift tests, geo tests). It grounds the model in measured truth, not just correlation.

Step 5

Validate — can we trust it?

A model that hasn't been checked is just an opinion with decimal places. Before presenting anything, we ask:

  1. Does it fit reality? Overlay the model's predicted revenue on actual revenue. A close fit on held-out weeks (data the model never saw) is the real test — not just the weeks it trained on.
  2. Are the results stable and sensible? Because it's Bayesian, Meridian gives every number a range, not a single figure. "TikTok ROI is 3.4x ± 0.5" is honest; a lone "3.4x" hides the uncertainty.
  3. Do the priors and the data agree? If the data violently disagrees with a prior, that's a signal to investigate — often a data-quality issue.

The honesty principle

MMM is directional, not surgical. It's excellent for "shift budget from A to B" decisions and poor for "prove this one ad drove exactly $4,213". Presenting ranges keeps the conversation credible.

Step 6

Read the results

Now the payoff. Meridian decomposes revenue into a baseline (sales that would happen with no media — brand equity, organic, repeat) and each channel's incremental contribution. For Kirana, media drives ~45% of revenue; the rest is baseline.

But the number that drives the decision isn't average ROI — it's marginal ROI (mROI): what the next dollar returns. A channel can have great average ROI yet be saturated at the margin.

Channel Spend share Avg ROI Marginal ROI Verdict
Search (non-brand) 10% 4.2x 2.1x Efficient, near optimal
TikTok 8% 3.5x 3.1x Underfunded — grow
YouTube 12% 2.8x 2.4x Headroom — grow
TV 22% 1.9x 1.3x Hold (long carryover)
Meta 26% 2.1x 0.9x Saturated — trim
Display 12% 1.1x 0.5x Over-invested — cut
OOH 10% 1.0x 0.8x Trim to contract floor

Illustrative figures for the fictional brand. Note how Meta has a healthy average ROI (2.1x) but a poor marginal ROI (0.9x) — the tell-tale sign of saturation.

The optimisation

Turn insight into a budget

Here's the elegant part. Meridian builds a response curve for every channel (spend → revenue, with saturation baked in). The optimiser then reshuffles the budget to equalise marginal ROI across channels — the mathematical definition of an optimal mix — while respecting the constraints from Step 1.

flowchart TD
  RC["Response curve
per channel"] --> OPT{{"Optimiser:
move $1 to wherever
it earns the most,
repeat"}} CON["Constraints
(floors, caps,
fixed budget)"] --> OPT OPT --> ALLOC["Recommended
allocation"] ALLOC --> CHK{"Marginal ROI
equal across
channels?"} CHK -->|"no"| OPT CHK -->|"yes"| DONE(["Optimal budget"])
The optimiser keeps moving dollars until the last dollar on every channel earns the same return.
# Ask Meridian for the best split of the SAME total budget
optimizer = BudgetOptimizer(mmm)
result = optimizer.optimize(
    fixed_budget=True,          # same $6M, just allocated smarter
    constraints=guardrails,       # floors & caps from Step 1
)
result.plot_response_curves()     # current vs. recommended spend per channel

One call turns the model into a board-ready recommendation.

Before vs. after — same $6M, smarter split

Channel Current Recommended Change
TikTok 8% 18% +10
YouTube 12% 17% +5
Search (non-brand) 10% 13% +3
OOH 10% 9% −1
TV 22% 18% −4
Display 12% 6% −6
Meta 26% 19% −7
Total 100% 100% $6M
+11%
Projected incremental revenue
$0
Extra budget required
~$1.2M
Reallocated to higher-return channels
The answer

So — how do we optimise next year's budget?

For Kirana, the recommendation to the board is concrete and defensible:

  1. Shift ~$1.2M out of saturated Meta and low-return display into TikTok, YouTube, and non-brand search — the channels with the strongest marginal returns.
  2. Hold TV despite its lower average ROI: its long adstock keeps the baseline healthy, and cutting it too far would quietly erode future organic sales.
  3. Expect ~+11% incremental revenue from the same spend — with the model's uncertainty range shown, not hidden.

And if the CFO instead asks "what if I give you 20% more?", the same response curves answer that too: they show exactly where the next dollar should go before returns flatten — turning budget-setting into a scenario conversation rather than a negotiation.

Doing it responsibly

MMM is one leg of a three-legged stool

MMM is powerful but shouldn't be run in isolation. Best-in-class measurement triangulates three methods, each covering the others' blind spots:

flowchart TD
  MMM["MMM
the big-picture,
privacy-safe view
of every channel"] --> U(["Unified,
trusted
measurement"]) EXP["Incrementality
experiments

ground-truth ROI
to calibrate MMM"] --> U ATT["Attribution /
analytics

fast, granular,
day-to-day tactics"] --> U
MMM for strategy, experiments for ground truth, attribution for tactics — used together.

Refresh the model quarterly, keep running lift tests to re-calibrate, and treat every output as a range. That discipline is what turns a clever model into a trusted decision tool.

What this case study demonstrates

Translating a hard MarTech topic into a decision a CMO can act on: framing the business question, understanding modern measurement (MMM, incrementality, attribution) in a post-cookie world, working hands-on with Google Meridian, and — most importantly — communicating it clearly to non-technical stakeholders. That bridge between deep technical measurement and executive decision-making is exactly the value I bring to customer success and MarTech teams.

← Back to Projects Talk to me about MMM