Johannes Binder
Software engineer in Karlsruhe, Germany
I work as a backend and cloud engineer. Outside of work, I build products I want to use myself, and I stay with them until the details feel right: how long a transition takes, where a number sits, what happens when the connection drops halfway through.
I’m interested in the fundamentals of computer science and in understanding how systems behave beneath the abstractions. I’m also exploring AI: how it changes the way I build software, how to make it genuinely useful in products, and the tooling and workflows that are still missing around it.
Projects

Pomodoro LedgerLive
Find out how long things really take.
A focus timer that writes every round down next to what you planned. After a week your estimates get honest, and you can see where the hours actually went.
I built it for myself first, so most changes start with something that annoyed me. The rest of the time goes into the parts you only notice when they’re missing.


A timer that floats. Pop the clock out and it stays on top of whatever you’re working on.


Digits that roll. Add five minutes and only the digits that change move.
Keyboard first. N adds a task, ⌘K finds any action, Space starts and pauses.


Weekly recap. Each new week opens with the last one on a single sheet: focus time, your best day, top labels and what you finished.
Type a task as one line: #app picks the label, “every weekday” makes it repeat and 3c plans three rounds.
Others join with a link and see each other’s rounds, never their tasks. Once everyone accepts a sync, the room runs on one clock.
Your day as stages. Drag across it to zoom into a stretch, point at a round to see its task.
During your best hours, the list suggests the task that deserves them. Move it up and it’s next.
Presets for the whole timer, or change the face, ring and background one at a time. The timer keeps running while you do.
Each break brings one small nudge: rest your eyes, stretch, drink some water. The first minute belongs to Keep going, then the nudge takes its place.
Rain that never loops: soundscapes are generated in the browser while you listen. Layer rain, ocean, fire and brown noise, and save your own mixes.
Most of the work goes into things you’ll barely notice, so the app can stay out of your way.
What I learned
Every extra signal costs attentionMost of what made the timer better was taking things away.
You look at a timer for half a second, so everything on the screen competes for that half second. I kept adding helpful things, a nudge here, a tint there, and each one made sense on its own. Together they made the app louder than the work.
The rule I ended up with is one signal per idea. If the best-time strip already says when to work, the rows don’t get tinted too. If a break nudge is showing, “Keep going” waits its turn in the same spot. A clock face shows the time and nothing else, and a ring means one thing.
Some costs only show up in use. A fixed 0–24 hour axis looked consistent and made every real day unreadable on a phone. A picker that scrolled itself after each pick felt laggy before I could say why. Now I judge a new piece next to everything else on the screen, at phone size, before it ships.
No central database, just a tiny server per roomDurable Objects are serverless functions with a name and a database of their own. That changed how I think about state.
A normal serverless function forgets everything after each request, and any copy of it can answer. A Durable Object is different in three ways: it has a name, like a room code or an email address, there is only ever one copy of it in the world, and it has its own small SQLite database that stays when the code goes to sleep. Every request for that name lands on that one copy, which handles them one at a time.
So Pomodoro Ledger has no central database. Every room is an object, and so is every person’s ledger: lots of small databases, each sitting next to the code that uses it. All of a room’s messages pass through its own object in order, so people joining, reacting and finishing rounds can’t collide, and I never needed a lock. The question that shaped the design was “what is the one thing these people share?”, and each answer became an object.
Your devices keep everything locally and sync with your ledger’s object: the newest change wins, and a delete leaves a marker so it reaches every device. Rooms send how much time is left, never when a timer ends, because devices disagree about the clock, and they only put everyone on one timer once the whole room has said yes.
It’s cheap, too. An object is only billed while it’s awake, so idle rooms sleep, answer pings without waking up, and close ten minutes after the last person leaves. Even a busy hour in a room costs a tiny fraction of a cent.

ParlaIn testing
Learn a language by reading a story written for you.
An iOS app with a personal AI teacher. Every day it writes the next chapter at your level, explains the grammar you run into, and corrects you when you talk to the characters. No streaks, no points.
Tap any word in tonight’s chapter to see what it means, and keep the ones you want to remember.
Say Lola es cansada and the teacher asks whether being tired is what she’s like or how she is tonight, then explains está.
One story at every level, from “Es viernes.” at A1 to rephrasing an earnings-call answer at C1+, with the help switching to match.
What I learned
Measure which model each job needsThe biggest model everywhere made learners wait. A bench picks one per task now.
Parla started with the biggest model on every request. The telemetry showed what that cost learners: a review exercise timed out on 7 of 11 requests, a turn in the story took 39 seconds at the median, and one chapter took four and a half minutes.
So I built a bench. It sends made-up learners’ requests through an in-process copy of the server with only the model behind one route swapped, scores the answers against gold answers, has blind judges grade the language, and recommends a model per route by comparing it with today’s on the same samples. Quality ranks first, then speed. Nothing changes until I copy its line into the config.
An AI judge is an instrument, so calibrate itBefore trusting a score, I check how often the judge gets it wrong.
Most of Parla’s checks end with one model grading another model’s text. That only works if the grader is right, so the judges get treated like any other measuring tool.
They never see which model wrote a text, and none grades its own model’s output when another judge can. Before a run, both judges read texts known to be correct and sentences with planted mistakes. A correct text that gets flagged, or a planted mistake both of them miss, goes back to whoever wrote the case. Reports show where the judges agree, with Cohen’s κ next to plain agreement, because agreement looks great when nearly everything passes.

WaymarkLive
A private map of where you’ve been, shared only with friends.
Mark the cities you’ve been to and the ones you want to see, and the countries and regions fill in as you go. Friends share their maps with you by invitation.
Mark a city as visited and the map flies there, flashes the new country in and adds it to your count.
Switch to a friend’s map to see the countries you’ve both been to and the ones only they know.
Every country you’ve been to turns into a flag in your collection, sorted by region.
The same map on a phone, with a friend’s view one tap away.
What I learned
Without a server, the rules are the backendFirestore’s security rules are the only thing between a friend and your map.
Waymark runs on Firebase with no server of its own. That makes the security rules the whole backend: they decide who can read a map and who can accept an invitation. So they get tested like a backend, against the emulator, in CI.
Rules allow or deny whole documents, not fields, and that shaped the data. A place lives in two documents, the city itself and its private details like the year and your notes, so a friend can see one without the other. Accepting an invitation creates the matching grant in the same atomic write, and the person accepting can’t widen what they were given.
Sharing is a choice on both sidesPrivacy decided the data model, not the other way round.
Friends share by mutual invitation, and the person accepting picks what to share back: visited places, plans and personal details are separate choices, and sharing nothing is allowed. Access can be revoked at any time and ends at once.
An invitation link works once, expires after a day and needs a sign-in, and no travel data ever goes into a URL.
Writing
All postsThoughts, things I found interesting, and lessons I learned along the way.
- Make no mistakesIf soon anyone can build the software they need, why would we still need developers? Some thoughts on ambiguity, prototypes and pruning.
- Random, but only oncePython evaluates a default argument once, when the function is defined. With a random number as the default, every job a process created landed on the same worker.
- Async in, sync outFeeding an async request body into a synchronous uploader, without buffering it, took me further into asyncio than I expected: a wakeup socket, two kinds of Future and one rule about event loops.






