d.
Blog

I was loading 48,000 ads nobody ever saw

For a month my fill rate sat in the low sixties and I read that as a demand problem. The plan was mediation: sign up with two or three more networks, wire their adapters into both games, let them bid on whatever AdMob could not fill. A week of work, maybe two.

Then I pulled the numbers properly and found I had been reading the wrong column.

The number I was watching was the wrong one

Match rate answers one question: did AdMob return an ad when I asked. Show rate answers a different one: did I ever put it on screen. I had been staring at the first.

matchedshown
rewarded, Android19,2851.3%
rewarded, iOS2,7620.1%
interstitial, Android28,00035%

Across both games, 47,970 ads were matched and then thrown away. I asked for them, AdMob filled them, and no player ever saw one.

Waste is only half of it. AdMob throttles publishers who request without showing, so the two numbers are connected: the discarded ads are why the fill rate looked bad to begin with. The clearest evidence is my US traffic, filling at 46.4%. US inventory does not run dry. I was being held back.

A timer nobody cancelled

The cause was nine lines in a file I had not opened in months. A periodic timer, thirty seconds, topping up three ad slots whenever they were empty.

Two details make this worse than a plain mistake.

The same file has three timers of this shape. The other two are cancelled properly when their screens go away. This one is created once at startup and never cancelled, so it runs for as long as the app is open.

And the condition is wrong. "The slot is empty" sounds like a reason to load. But wherever fill genuinely fails, the slot is always empty, so the app asks again every thirty seconds for the rest of the session. The failure feeds the loop that produces more failure.

The load error was discarded, which is how this survived so long. A network error on a cold start and a country with no advertisers arrive at the same callback, and the code treated them as the same thing.

Asking for less

The fix is not to stop retrying. Someone who opens the app before their connection is up still deserves an ad. It is to retry like something that expects to fail: back off, give up after a few attempts, and read the error code so a temporary outage and a permanent one are handled differently. Rewarded ads need no preload timer at all, because you know a second in advance when a player is reaching for the button.

The other half was not code. Google does not serve ads in Russia, so every request from there fills at exactly zero. In one of the two games that was 17.3% of all traffic. Taking those requests out of the arithmetic, without touching a line, moves that game from 60.5% to 73.1%.

Both games are being fixed now. The check I care about is not the fill rate, which takes weeks to recover. It is whether requests fall by half while the earnings line stays where it is. If revenue drops with them, I did not cut waste, I cut inventory. I am watching that line.