3 runs and gone: rebuilding a game's retention loop

The moment my heart sank, every fix I shipped after, and the thing I still haven't cracked.

I looked at the numbers a couple of days after launch and my heart sank a little. People were playing growordie.io, they just weren't staying. The average session was about three runs. A few minutes, and then gone. Nobody came back the next day. And the thing that stung most: it wasn't that the game was bad. It was that I hadn't given anyone a single reason to come back.

Some quick context so the rest makes sense. growordie is a snake .io game, you grow on your own, one touch kills you, and your length in metres is the only score. I launched it on Reddit, in r/WebGames and r/iogames, and about 50 organic players trickled in over the first 48 hours. That's tiny, I know. But it was enough to see the shape of the problem clearly, and honestly it's the most I've learned about product from any two days of my life.

This is me writing down the whole thing while it's fresh: the number that made my stomach drop, everything I built in response and why, the stuff that flopped, and the part I still can't figure out. It's a product story, not an engineering one. If you want the netcode, that's over in how growordie works. This one is about the harder problem: getting anyone to come back at all.

00 · The diagnosis: a funnel with no floor

What confused me at first is that, by one reading, the game was working. Roughly half the people who landed on it played three or more runs, and for a game with no tutorial, no account, and a two-word premise, that's a genuinely nice hook. People got it. They died, shrugged, went again a couple of times. The moment-to-moment was fun. I almost let that reassure me.

But then I sat with it and realised the good news was the bad news. If people happily play three rounds and then vanish, the game isn't the problem. The two things wrapped around the game are:

So here's how I'd put it to myself, harshly, that evening: the game hooks the people who walk in, but I'd built no reason for any of them to come back, and no way for a player to become a source of players. A funnel with no floor and no branches. Here's the shape of it: the top is what I had, the bottom is what I spent the next few days trying to build.

BEFORE · a funnel with no floor newplayer run 1 run 2 run 3 gone (session over) ~50% reached 3+ runs, then the screen said nothing. AFTER · loops that pull you back one more run near-miss re-venge today'sboard streak skins 🎬share + new player

the shape of the problem· leak → loops

The top is a funnel that only drains: a player runs a few times and falls off an edge with nothing to catch them. The bottom is what the rest of this post is: five return loops that give the end of a run a reason to become the start of the next one, plus one outbound loop that turns a death into a new player. Same game. Completely different topology.

I shipped everything below over a handful of evenings, straight to production, mostly in the order you'll read it. For each one I'll try to give you the honest version: what I saw that bugged me, the little bit of psychology I was betting on, what I actually built, and whether it's too early to know if it worked. Spoiler: for a lot of them, at 50 players, it is.

Fix 01 · return loop

The near-miss: reframing the death screen

The moment you die is the most attention you'll ever have from a player, they just failed, they're deciding what to do next, and for about a second the screen has them completely. And I was spending that second telling them their rank. Think about how that feels: in a game where the all-time record belongs to someone who's played for hours, a new player reads "you're #4,120" and hears "you're nobody here." I'd taken the best second I get and used it to make people feel small. Once I saw it that way I couldn't unsee it.

The hunch · the near-miss

The thing that makes you go "ugh, one more" isn't winning, it's almost. A near-miss lights up the same reward wiring as a win but leaves the goal sitting there unclaimed, and an unclaimed goal is the thing your brain refuses to let go of. It's the whole engine behind slot machines and every "you were 2 points off your best." So the frame had to be you vs. your own best self, never you vs. the universe.

So I made the server hand the death screen your personal record from before the run, and rewrote the screen to talk to that instead of the leaderboard. Three moods, each with one job:

I also slipped in a quiet run counter, "Run #3 this session", because the second you put a number on the repetition, it stops being invisible and starts feeling like a little streak you don't want to break. None of this is new content. It's the same death and the same number, just reframed into a story about you closing a gap instead of you being ranked below strangers.

Effect: honestly, too early to isolate at ~50 players, but this is the idea the whole death-screen rebuild (Fix 06) leans on, and it cost me almost nothing to try. It's the cheapest lever on the list.

Fix 02 · return loop

A leaderboard you can actually win: TODAY

An all-time leaderboard is basically a monument to people who aren't you. Great if you're in the top 0.1%, quietly demoralising for everyone else, and (the part that actually mattered for my problem) it never changes fast enough to be a reason to come back tomorrow. I wanted a board a brand-new player could realistically top in their first session, and one that wipes clean often enough to hand out fresh hope.

The hunch · reachable, and it resets

A goal only pulls on you if it feels like you could actually hit it. A daily board makes #1 look reachable on day one, and because it resets every 24 hours, yesterday's bad run is erased and today is a clean slate. "The board resets at midnight" is one of the oldest reasons-to-return in games for a reason: it quietly turns a permanent ranking into a renewable one.

So I added a top-10-of-the-last-24-hours board with a little two-tab toggle: TODAY / ALL-TIME. (It rides its own lightweight message so it never touches the binary game snapshot, and it's cached off the hot path, the one place the engineering leaked into the product decision.) The detail I'm happiest with is the default: newcomers land on TODAY, returning players land on ALL-TIME. Someone who's never won anything sees a hill they can climb; a veteran sees the mountain they're already on. Same widget, two different promises depending on who's looking at it.

Effect: too early to measure next-day return at this size, and I won't pretend otherwise, but "come back before the board resets" is the first thing in the whole game that gives tomorrow a job.

Fix 03 · return loop

The vendetta: hunt the player who killed you

This is the one I got a little excited about. When another player ends your run, something happens that a lonely death into a wall never does: you're not just bummed, you're mad at a specific someone. I noticed it in myself playing my own game, I'd die to some "viper" and immediately want back in to get them. And I was letting that feeling evaporate on a screen that shrugged "slain by viper" and offered a generic Play again. What a waste.

The hunch · the grudge

A grudge might be the strongest "one more run" generator there is. It turns a loss into an open loop with a name on it, and named, personal, unfinished business is the kind of thing people will re-queue for instantly, over and over. The beautiful part: revenge is a goal the player writes for themselves. You can't hand someone a better motivation than the one they invented.

So now, if another wyrm kills you, the death screen lights up a red 🔪 HUNT viper button. Respawn through it and you carry a vendetta into the next life: while your target is alive you get a pulsing red lock-on (or a directional arrow at the screen edge when they're out of view) and a little "HUNTING viper" bar. Land the kill and you get a private ⚔️ REVENGE celebration, shown only to you, never broadcast, because the whole point is that it's personal. It's almost entirely client-side; the only thing the server had to learn was a name flag. Tiny to build. And of everything here, it's the hook that comes closest to setting itself.

Effect: too early to measure, but if you made me bet my own money on one fix in this whole post, it's this one. It takes the most infuriating way to die and turns it into the most obvious reason to hit respawn.

Fix 04 · return loop

The daily streak: a habit you can see

Everything up to here works inside a session. But my actual problem was people not coming back the next day, and none of it reached across that gap. A streak does. It's the difference between a fun little toy and something you open by reflex, and yes, I'm fully aware of how shameless the mechanic is. I added it anyway.

The hunch · you don't want to lose it

The moment a streak exists, it becomes a thing you can lose, and we all work much harder to avoid losing something we already have than to gain something we don't. A visible "🔥 7 day streak" is a tiny pile of sunk cost you protect with a daily visit. It's the Duolingo trick, and it works precisely because it's almost embarrassing how well it works.

I kept the build deliberately humble, pure client-side, in localStorage. It remembers the last calendar day you played and counts consecutive days. Come back the next day and a "🔥 X day streak!" toast greets you on the menu; skip a day and it quietly resets to 1, no scolding. Once you're two or more days deep, a little streak line shows on the death screen too. No server, no accounts, nothing to sync, just a small number that stings a bit to reset.

Effect: too early to measure, and it's the most obvious one to be honest about, a streak needs weeks of returning players before it even shows up in a curve. I planted it now so it has something to compound on later.

Fix 05 · return loop · monetisation foundation

Lifetime metres → skins: the long arc

The near-miss and the grudge are short loops, they yank you into the very next run. But I also wanted a long one: something that survives a whole session ending, a slow-filling bar that makes every run count toward something, even the runs where I died in four seconds like an idiot.

The hunch · nothing is wasted

We keep going toward a goal we've already put something into. So if every metre you've ever travelled piles onto a permanent lifetime total, no run is ever wasted, even a terrible one nudged the bar forward. Visible progress toward a named reward is one of the stickiest mechanics in games, and cosmetics are also the cleanest path to eventually making money: you're selling identity, not power. Nobody feels cheated.

So every run's length now adds, forever, to a lifetime total on your account, and crossing fixed thresholds unlocks cosmetic snake skins:

ember

10 m lifetime

you came back at least a few times

frost

50 m lifetime

you're a regular

gold

200 m lifetime

you're invested

void

1 km lifetime

you're one of us, and it cycles its hue forever

aurora soon

? a taste of where the shop could go

not a real tier yet, just proof the ladder has room to keep climbing

the skin ladder· 10 m → 1 km

Four tiers, auto-equipped at the highest you've reached. ember is molten, frost shimmers cold, gold catches a sweeping specular highlight, and void is the prize: a cosmic body whose rainbow scrolls along its length and cycles hue forever. The aurora at the bottom is a teaser, not a real tier, just a hint of where a cosmetics shop could go.

Your active skin is always the highest tier you've reached, auto-equipped so there's no menu to fuss with. A little progress bar on the death screen and the menu shows exactly how far you are from the next one, "lifetime 34m · next: gold in 166m", so the gap is always sitting right there in your eyeline. And I'll be honest about the second motive: this is the foundation the business would eventually sit on. Once someone cares how their snake looks, a cosmetic shop is a natural, non-pay-to-win next step. I'm not selling anything yet. I'm just building the reason someone might one day want to buy.

Effect: too early to measure long-term return, this is a months-scale bet by design, but it quietly doubles as the groundwork for ever making money, so it earns its keep twice.

Fix 06 · the rebuild

Burying twenty lines to surface one decision

And then I walked straight into a trap, and it's such a classic one that I'm a little embarrassed. Every fix above added something to the death screen. A motivation line. A session counter. A "YOU BECAME A WYRM OF" label. The big number. A row of stat chips. A challenge button. A HUNT button. A skins bar. And underneath all of it, a twenty-row Hall of Fame, a wall of names, almost none of them yours, sitting exactly where your eye lands the instant you die.

I'd built a genuinely good set of levers and then stacked them on top of each other until they cancelled out. I opened the screen one night to test the HUNT button and just… couldn't find it for a second. The one button that actually mattered, Play again, was drowning in the middle of a spreadsheet. I'd done it to myself.

The death screen has exactly one job: get you to press the button. Everything on it that wasn't that button was competing with it.

So I threw most of it out and rebuilt around a single question (what do you do next?), cutting or demoting anything that didn't help answer it. Here's the before and after, side by side:

BEFORE the replay prompt, buried YOU DIED So close, 68% of your best. One more? Run #3 this session · session best 9.8m YOU BECAME A WYRM OF 6.7m 2 kills 0:48 slain by viper ⚔️ challenge a friend to beat 6.7m Play again (space) HALL OF FAME · ALL TIME 1seraphax241.0m 2nyx228.6m 3krayt210.3m 4vorr198.1m 5basilisk187.4m 6sarkan176.0m 7venra165.9m 8skarn154.2m 9mordax148.7m 10quill139.1m 11threx131.8m 12ophis124.5m 13wraith118.0m 14coil112.4m and 6 more AFTER one screen, one decision YOU DIED 9.8m best 6.7m 2 kills · 0:48 · slain by viper lifetime 34m next: gold 🔪 HUNT viper Play again (space) ⚔️ challenge 🎬 share this run · #212 all-time · top 8% ▸ room to breathe the wall moved to the menu

the death screen, rebuilt· from spreadsheet to decision

Left, the eye lands on a leaderboard and the replay button is one row in a stack. Right, the whole screen is a single decision: a progress bar toward your record crowning the number, one line of context, and a contextual primary button, HUNT if a player killed you, Play again if you killed yourself. Challenge and share drop to a discreet row; the Hall of Fame wall moved to the menu where browsing belongs; the standing collapses to one clickable line. The button hierarchy now changes with how you died, because the right next action isn't the same every time.

This is the fix I'm proudest of, and the funny thing is it added zero new features, it only removed and reordered. The lesson landed hard enough that I want to say it plainly: a screen with ten good things on it is usually worse than a screen with two. Every element you add quietly borrows attention from the one that matters most. I don't think I could have seen that without first building the cluttered version and wincing at it.

Effect: too early to see in a retention curve, but this is the screen whose entire reason to exist is runs-per-session, so it's the first number I'll be watching the moment there's enough traffic to trust it.

Fix 07 · the outbound loop

Share it or it didn't happen

Everything up to here fights the first problem, keeping a player. But there was a second, bigger one I'd been avoiding: acquisition was a flat line, and no amount of retention polish fixes that on its own. A player I retain perfectly but who never brings a friend is still a dead end, just a slower one. At some point the product itself has to make new players, or it never really goes anywhere.

The hunch · borrow the player's trust

My own marketing channel is cold, an unknown brand shouting into the void (more on how badly that went in a minute). But a player's channel is warm. When a real person drops a clip on their own TikTok or into a group chat, it shows up with trust and reach I could never buy. The best distribution I'll ever have isn't mine to own, it's thousands of players each posting from an account their friends already follow. So the whole job is: make sharing effortless at the exact moment a run is actually worth sharing.

So now when you die, your browser quietly does something that would normally be a whole video-editing chore: it re-renders the best ~10 seconds of your run as a vertical 720×1280 MP4, sized for TikTok / Reels / Shorts. A little highlight log scores the run (kills, nearby explosions, the death itself) and grabs the most intense window rather than just the boring tail, and it ends on your death, in slow motion, because the wipeout is the money shot. Then it pops the native share sheet and copies a caption with hashtags to your clipboard in the same tap (TikTok and Instagram ignore pre-filled share text, so you have to sneak it onto the clipboard). One button, and I couldn't resist the label: 🎬 SHARE IT OR IT DIDN'T HAPPEN.

💀you die 🎬auto-clip10s vertical 📤you shareTikTok · chat 👀friend sees itwarm reach they play+1 player every player is a channel client-side render · $0 server cost

the outbound loop· die → clip → share → player → …

The whole cycle is client-side: the clip is rendered in the player's own browser from state we already buffer for the kill-cam, so it costs the server exactly nothing and scales for free with every death. Structurally this is stronger than us posting from a cold brand account, because each clip goes out from a real person's feed to people who already trust them. It's the one fix that attacks acquisition instead of retention.

Now the honest bit, because this is the one it'd be easiest to oversell: the loop exists, but it isn't spinning yet. Building the machine is not the same as the machine running. A share button is not virality, it's a prerequisite for virality, and those are very different things. Do players actually tap it? Are the clips good enough to stop a thumb mid-scroll? Does a stopped thumb turn into someone who plays? At 50 players I genuinely don't know yet. I built the road. I haven't seen the traffic.

Fix 08 · seeding the loop

The clip factory (a note on cold-start)

A share loop has a nasty chicken-and-egg problem: it only spins once enough players are sharing, and you don't have enough players precisely because it isn't spinning yet. So to prime the pump, to at least have something on TikTok when the first curious person searches the name, I built a little pipeline that cranks out marketing clips with no human in the loop.

It's a standalone Playwright driver that loops over fresh, database-verified snake names, records a headless ~180-second run per name driven by a "star" autopilot bot that plays and chases boosts, logs the intense moments, then cuts the best 1–3 non-overlapping windows of each run into captioned TikToks in both 9:16 and 16:9. It's resume-safe and politely backs off if the live server is busy. I let it run, and it produced 81 clips without me touching a video editor once. That part felt like magic.

Two things I want to be really clear about, because it'd be easy to blur them. First: these are marketing assets, not players. The star bot is a content generator, full stop, I'd never count it as a user, and it never once touches the ~50 human numbers I've been quoting. Second: 81 clips are worth exactly as much as the distribution behind them, which is a perfect segue into the part I got wrong.

09 · What we got wrong

This wouldn't be an honest post-mortem if I only told you about the stuff I'm proud of. So here are the two that actually stung.

I got the launch banned from r/incremental_games

Early on I posted to a subreddit that seemed adjacent, and just wasn't. growordie is a twitchy action .io game, not an incremental, and the community read the post as exactly what it was: off-topic self-promotion. Removed. The lesson was cheap to learn and I won't forget it: "kind of related" is not "relevant." Firing your launch into a community you haven't actually understood spends goodwill you can't get back. Fit beats reach, every time.

My first TikTok push was a cold account posting silent clips

My first swing at video was, in hindsight, the exact mistake the share loop exists to avoid, a brand-new account with zero followers, posting clips that were also, embarrassingly, muted. On TikTok, sound isn't a nice-to-have, it's half the product. Cold account plus no audio is a recipe for zero reach, and zero reach is roughly what I got. It's the clearest possible evidence for why the version that lasts has to run through players' accounts, not mine.

Both mistakes have the same root, and it's a little uncomfortable to admit: I reached for the channel I controlled instead of the one that would actually work, because the one I controlled was easier and felt safer. The fix, both times, was the same, stop broadcasting from a cold corner and go to where the trust already lives.

10 · What we still haven't cracked

Here's the part I'd love to skip, so I'll say it straight instead: I built the retention machine, and I still haven't found the acquisition spark.

The return loops (near-miss, grudge, daily board, streak, skins) are all live, and I really do believe in the psychology behind each of them. But at ~50 organic players over 48 hours, I can't prove a single one in a retention curve. The sample is too small and too new, and I'd rather tell you that honestly than dress up a handful of sessions as a trend. I've marked every effect "too early to measure" because that's the truth. Ask me again at a few thousand players and I'll have real curves instead of good intentions.

And the bigger, humbler truth is that acquisition is still the unsolved problem. Retention decides what a visitor is worth; it doesn't create visitors. The share loop is my bet on fixing that from inside the game, but a loop that isn't spinning is a hypothesis, not a win. The thing I'm actively hunting right now is the spark, that first push of real, warm distribution that gets enough players sharing for the loop to start turning on its own. I have the machine built. I'm still looking for the match to light it.

Retention turned out to be a design problem, and design problems bend if you think hard enough. Acquisition is a distribution problem, and distribution does not care how clever my death screen is.

If you take one thing from all of this, let it be the order, the order I'd recommend, and, half by luck, the order I ended up following: fix the floor before you fill the funnel. Pouring acquisition into a game that loses everyone after three runs just makes the leak louder. I built the loops first so that whenever the spark finally comes, the players it brings will have a reason to stay, and a clip worth sharing on their way back out.

If you've read this far: thank you, genuinely. This is the first game I've ever shipped, and I'd honestly love to hear where your session ended and why, the death that made you close the tab, or the one that made you go again. That's the feedback I can't get from 50 data points. Come play a few runs and tell me.

Every illustration on this page is inline SVG animated with SMIL and CSS, no JavaScript, no canvas, no external assets. Same instinct as the game: do the cheap thing that looks expensive.

Building growordie in the open