Oluwafemi

Case study

Two Steps

Walking routes that never repeat.

2026 · iOS and Android · Rewritten from a step counter

Download Two Steps on the App StoreGet Two Steps on Google Play
Two Steps on three phones: setting a step goal, a suggested loop, and a finished walk

Two Steps used to be a step counter. It counted your steps and put your total on a leaderboard next to your friends'. That was the whole app, and the leaderboard was the only reason to open it.

This year I started walking properly, with a step target most days. I don't go out much otherwise, so I walk the same few routes over and over, and they wear out fast.

Going somewhere new is the obvious fix, but doing it by hand means two things I don't enjoy. I have to work out where to turn next while I'm already walking, and once I've hit my number I have to find my way home. Both of those turn a walk into something I have to manage.

On one of those walks it occurred to me that the app could do all of it. I tell it how many steps I want. It gives me a loop that adds up to roughly that many, made mostly of streets I haven't walked before, ending where it started. That's version 2.0, and it's what the rest of this is about.

Setting the number

There is one control on the first screen. You pick how many steps you want, and the app shows you what that means in distance and time, so 8,000 steps reads as about 6 km and about 75 minutes.

You can also press and hold anywhere on the map to set a destination instead. That's for when you're actually going somewhere and just want the walk there to be worth taking. Everything below works the same way for both.

Setting a goal of 8,000 steps, shown as about 6 km and about 75 minutes
A suggested loop called Tower Bridge walk: 7,409 steps, 5.6 km, 1h 20m, 99% new ground
One number, and what it works out to. The map behind it is where you are now.

Making a route

When you press the button, the phone sends three things to a small server: your goal, a random seed, and a list of the ground you've already walked.

The server doesn't know anything about streets. It works in plain geometry. For a loop it takes a circle around you, pushes the centre off in a random direction, and drops three via points around that circle at roughly even spacing, each nudged a bit so no two loops come out the same shape. The circle is drawn about a quarter smaller than the maths suggests, because a route that's meant to be round gets stretched by the street grid it has to follow.

Those via points aren't a route. They're a claim about where a route should pass. Google's Routes API takes them and returns a real walking path along real pavements, which is the part I have no business trying to work out myself.

That happens seven times with different via points, so seven real walks come back. Two numbers decide which are worth showing you: how much of each one is ground you've never covered, and how close it lands to the number you asked for. New ground is worth up to 70 points and hitting the goal is worth up to 30, so a fresh route that comes in a little long still beats a familiar one that lands exactly on target.

Then the near-duplicates go. Any route sharing more than 80% of its ground with one already picked is the same walk with a different wobble, so it's dropped. What's left is up to five options that are actually different from each other, which is worth more than five that only look like a choice.

Remembering the ground

The app remembers where you've been as squares. The world is cut into a grid of roughly 30-metre cells, and finishing a walk stamps every cell its line passed through, sampled every 20 metres so long straight stretches don't skip any.

The “new ground” percentage on a route is just the share of its cells that aren't in that set yet. It's cheap to work out and small enough to send: the phone ships up to 20,000 cell ids with every request and the server does set arithmetic on them.

Twenty thousand cells is around 600 km of street. Past that, the oldest ones drop off the end, which is the behaviour I want. A road I walked last week is worth avoiding. One I walked eight months ago isn't.

Naming the walks

Every route that survives gets a name and one line about it. That comes from a model, in a single call, and it's the only thing the model does. It's given the distance, the time, the new-ground percentage and the name of any park the route was bent through. It never sees the geometry and it never chooses the route. The scoring has already done that.

It also gets four and a half seconds. If there's no key, or it times out, or it hands back the wrong number of entries, the app falls back to names built from two local word lists, which is where names like “Crooked ramble” come from. The rule is that the model can improve a walk but can never hold one up.

A walk in progress: 139 of 6,095 steps, 104 m round the loop, 4.6 km to go
An onboarding screen explaining that the map turns with you and arrows show which way the loop runs
Walking one. The map turns with you, the road ahead stays lit, and the arrows are planted on the ground rather than drawn in front of you.

Walking it

A loop doesn't have a direction. The line runs one way in the data, but you can walk it either way and you decide which by setting off. This turned out to be the fiddliest part of the whole app. If it guesses wrong, everything downstream is wrong with it: the distance remaining counts up instead of down, and the arrows point back the way you came.

So it doesn't guess. For the first 30 metres it watches where you actually go, compares that against both readings of the line, and takes whichever one you're matching. After 150 metres the question is settled and stops being asked. There's also a turn-around button that flips it by hand and locks it there, for when you change your mind halfway round.

Once the direction is known, the map orients to it. It turns as you turn, the road ahead stays lit and the ground behind it dims. The arrows are planted on the route every 70 metres rather than floating ahead of you, so you walk past them the way you'd walk past a sign, and there's never one right under your feet.

The Trails screen: 145,532 steps, a bar chart of the week, 18 walks, 109 km, 95 km of streets covered
A saved walk called The Back of the Park: 8,315 steps, 6.2 km, 1h 19m, 92% new ground
Finished walks. Green is the route that was suggested, orange is where I actually went.

After the walk

Ending a walk saves it and stamps its cells, so the next route steers around it.

Each saved walk keeps two lines: the route that was suggested and the one you actually walked. They're rarely the same, and the difference is usually the interesting part: a crossing that wasn't there, or a shortcut that was.

Deleting a walk removes the record but keeps the ground. I did walk those streets, and forgetting that would start handing them back to me. Wiping both is a separate thing in settings, for when you actually want to start over.

Counting steps

The number on the front is the phone's own count for the day, not just what the app recorded while a walk was running. Those are different numbers and it matters which one you show.

On iOS that's straightforward. The motion coprocessor keeps a rolling history, so it can be asked for everything since midnight. Android has no equivalent: its pedometer starts counting when you subscribe and knows nothing about the hours before that. So there the day's total comes from Health Connect, and if that isn't available the app shows what it recorded itself and says that's what it is.

A walk in progress also shows up outside the app: a Live Activity on iOS, a home screen widget on Android. The Live Activity feeds itself from the motion coprocessor between updates, so a locked phone or a reloaded bundle doesn't cost it anything.

How it's built

Expo, React Native
One codebase, iOS and Android
expo-router, Restyle
Navigation and the design system
zustand, MMKV
State, written to disk synchronously
Cloudflare Workers, Hono
One endpoint: generate a route
Google Routes API
Turns via points into real pavement
Google Places
Finds a park worth bending through
AI SDK, Gemini
Names the walks. Never picks them
zod
One contract shared by phone and worker
TanStack Start
twosteps.app

Where it is now

I've been using it since I finished it, and the part I didn't expect is how much of my own area I hadn't seen. Shortcuts between streets I'd been walking around for months. Whole roads a few minutes from my door that I had no reason to turn onto. And the general fact that almost everything connects back home if you keep going.

It's at twosteps.app.

Download Two Steps on the App StoreGet Two Steps on Google Play

Screenshots are from the iOS app. Map data is Google's; walking paths come from the Routes API.