Bat Labs · Frontend Developer · New Delhi · 2020–2021

I joined WowTalent as a junior. The app taught me the layers under the UI.

A legacy Flutter codebase, a feed that had to stay smooth while video played, and a database that needed to stop being MongoDB. I arrived curious. I left having owned the playback pipeline and the move to PostgreSQL on AWS.

100K+downloads
20K+daily active users

The codebase fought back

WowTalent was already on phones when I started at Bat Labs. I was a junior developer, and the Flutter app had grown into something hard to change. The advantage I had was curiosity. A janky scroll was not a ticket to close. It was a reason to learn what was actually running when a frame missed its deadline.

That habit set the year. I kept going one layer down from the screen: the list that built the feed, the thread that decoded video, and the database the feed was reading from.

The feed and the frame budget

The feed is the product. People live in the scroll. Optimising it was not a single widget change. Every cell could pull in images and video, and all of that work wanted the same thread that had to lay out the list, paint it, and answer the finger still dragging.

On a mid-range Android phone, that budget is small. If decode or a heavy build runs inside it, the scroll stutters. The rule I worked toward was strict. The UI isolate spends its frame on layout, paint, and gestures. Media is prepared somewhere else. The feed draws from what is already ready.

Playback was where the rule broke

Video made the problem impossible to ignore. Under load, decoding and buffering ran on the UI thread. The main isolate blocked. Frames dropped. On mid-range Android the playback stuttered, and a blocked main thread is also how an app picks up ANRs.

I moved decoding and buffering onto Dart isolates, and I reworked the buffering strategy around that split. Isolates do not share memory. The UI isolate cannot reach into a decoder and pull frames out. Work crosses as messages: the screen asks for media, a background isolate decodes and fills a buffer, and only what the current frame needs comes back. The buffer exists so a slow decode on a cheap phone does not stall the next frame. The main isolate stays free to scroll the feed.

That split, plus the buffering change, is what cut the stutter. It is also what let the app reach its download and daily-active numbers without the crash and ANR rate climbing with them.

Then the data was in the wrong shape

The client work sat on MongoDB. A feed is a relational question: which posts belong in this scroll, which media belongs to each post, and how that lines up for the person holding the phone. Documents can store all of that inside one blob. They get awkward as soon as the app asks for part of the blob, in order, for a list that never sits still.

I owned the migration to PostgreSQL on AWS. The job was to give each kind of thing its own table and a real relationship to the others, move the data, and point the app at Postgres without stopping the releases that were already in flight. The feed and the playback pipeline were changing in the same months, so the cutover had to land underneath work that was still shipping.

None of it ships if the code is scary to touch

A junior can move a decoder off the UI thread and still be afraid to merge it. The legacy codebase was the reason. I rebuilt the hard parts around SOLID and clear patterns, brought in TDD and automated tests, and set up CI/CD so a release stopped being a gamble. The isolate split and the database migration both landed inside that net. Tests made the playback change reviewable. The pipeline made the migration something we could ship on a Tuesday.

What I walked out with

The app reached 100K+ downloads and 20K+ daily active users. The number I keep is the other one: the crash and ANR rate did not climb with them. A year earlier I had been a junior who wanted to understand why a screen janked. The year taught me where that jank actually lives. Sometimes in the isolate that is decoding. Sometimes in a document store being asked a relational question.

Back to selected work