Honest Growth
← All detections

Detection · frequency_saturation

Detection: Frequency saturation with declining CTR

Key: frequency_saturation Severity: Medium–High Confidence: 75–90%

What this detection looks for

We flag an ad set as frequency-saturated when all of these are true:

  1. The ad set is active
  2. It has at least 14 days of observed metrics in the audit date range
  3. Average frequency in the last 7 days is at or above 5.0
  4. Click-through rate in the last 7 days is at least 20% lower than the prior 7 days

The signal is the combination of high frequency and declining CTR. Frequency alone is not enough — some audiences sustain high frequency without performance loss. CTR decline alone is not enough either — that can come from creative fatigue across a broad audience rather than saturation in a narrow one.

Why this matters

When the same users see your ad five or more times in a week and clicks are dropping, you have run out of new audience to reach. Continuing to spend into a saturated audience produces three consequences:

  • Cost per click rises because the auction must bid more aggressively to win impressions you have already won.
  • Conversion rate among those clicks drops because the remaining clickers are weaker fits.
  • Brand safety risk grows — the people most likely to engage with a 10th-impression ad are not always the people you want associated with the brand.

The fix is to expand the audience or refresh the creative. Lowering the budget without doing either will only delay the same problem.

How we calculate confidence

Condition Confidence
Recent frequency ≥ 6.0 AND CTR drop ≥ 30% week-over-week 90%
Recent frequency 5.0–6.0 AND CTR drop 20–30% 75%
Any condition above not met We don't surface the finding

How we calculate the estimated monthly cost

We take the share of last-week spend that has become inefficient — defined as the proportional CTR drop, capped at 50% — and project it to a 30-day month.

inefficient_share = min(ctr_drop_pct, 0.50) monthly_waste = (recent_week_spend × inefficient_share) × (30 / 7)

The cap exists because we are measuring an upper bound on inefficiency. A 60% CTR drop does not mean 60% of spend is wasted — it means a meaningful share of it is, and we are deliberately conservative in what we surface as recoverable.

What would change our mind

This finding can be a false positive in a small number of cases:

  • A short, time-bound promotion. During a Black Friday or product launch promo, frequency intentionally rises and CTR is expected to decline as the urgency window closes. If the campaign is winding down on schedule, the finding is informational rather than actionable.
  • Retargeting ad sets target small high-intent audiences. A retargeting ad set may sustain frequency 7+ profitably because the audience converts at high rates even on the 8th impression. If ROAS is steady or improving, the finding does not apply.
  • The creative was just refreshed within the window. A creative refresh in the prior 7-day window will produce a higher CTR baseline that the recent week cannot match. Check the creative refresh date before acting.

How to fix it

  1. Look at your audience size lower/upper bounds for the ad set. If you are targeting fewer than ~500K people, expand the audience.
  2. Refresh the creative — see also creative_concentration_risk. A single new creative variation often resets frequency to under 2.0.
  3. Consider duplicating the ad set with a fresh audience (lookalike, broader interest stack, or larger lookalike percentage). Pause the old ad set once the duplicate is converting.
  4. Avoid reducing budget without changing audience or creative. You will slow the bleed but not stop it.

What we look at to make this detection

  • effective_status on the ad set
  • Daily frequency from the ad-set-level Insights API
  • Daily impressions and clicks summed to compute click-through rate per 7-day window
  • Daily spend to compute the recoverable amount
  • Each window's average frequency is the average of daily frequency values (not a recomputed reach/impressions ratio), since Meta reports frequency at the day level and recomputing across days requires the full unique-user set, which is not available

Source

This methodology page is generated from apps/api/app/services/detections/frequency_saturation.py. The detection code is open for inspection. We do not have hidden rules.

See it run on a real account.

The sample audit shows this and 14 other detections fired against a synthetic but realistic $30K/month account.