Skip to content
AKRAmit Kumar Raikwar
All work
Product2026Live

Finalist

Scrims and tournaments that run on a clock

Overview

A scrim org runs on four things: a Discord server, one sheet for the slot list, a second sheet for the points table, and a captain's DMs where the room password ends up. That holds for about a month. Then a password gets screenshotted into a public channel, or twelve teams turn up for eleven slots because nobody backfilled the waitlist. Finalist replaces the whole arrangement with one system covering registration, slots, room details, results and leaderboards.

On paper this is a CRUD app with brackets attached. In practice most of the hard problems turned out to be scheduling problems, because an organizer sets the times once and then goes to play in the lobby. Registration opens, slots fill, lineups get filtered and the room reveals while nobody is watching the dashboard, so the clock had to be built as a subsystem rather than added as a cron job later.

Role
Design and engineering lead, built with my team: three web surfaces, the API, the Discord bot and the data model.
Year
2026
Stack
Next.jsTypeScriptPostgreSQLPrismaRedisBullMQdiscord.jsZodDocker

Engineering

The decisions that mattered.

  • 01

    Nothing waits for a click

    Registration opening, registration closing, the pre-match filter and the room reveal are all driven by the clock rather than by a host pressing a button. A lifecycle worker advances every due edge once a minute, so a scrim night runs itself while the organizer is in the lobby.

  • 02

    Schedules are stored as offsets

    Registration windows are held as minutes before start, 360 to open and 30 to close, instead of as absolute times, and every recurring preset carries its own timezone. Move the start time and the whole evening moves with it. Node runs under TZ=UTC with conversion at the edges, so a daily preset does not drift when a timezone changes offset.

  • 03

    The minute tick holds a lock

    Two worker replicas can both see the same transition come due. Redis locks around each edge make the pre-match filter fire exactly once. That pass removes lineups and promotes waiting teams into the slots it frees, so a second run would unregister teams that had only just been slotted in.

  • 04

    Room credentials are an authorization check

    The Discord announcement carries a public reveal button. Anyone can press it, authorization runs server-side on every press, and the credentials come back privately to the captain of a team holding a slot. Passwords are marked secret by default and stay masked until that captain reveals them.

  • 05

    Bracket configuration is validated when you save it

    Six formats, from battle-royale points tables to double elimination with a bracket reset. Group and lobby arrangements that cannot rotate evenly are rejected at save time. A tournament that fails validation on match day, with 64 captains already in a voice channel, is not recoverable.

  • 06

    Postgres, because everything here is a relationship

    A slot belongs to one team, a lineup to one slot, points to one confirmed match, a standings row to one stage. Prisma and Postgres, with points frozen at the moment a result is recorded and standings recomputed when a referee reopens a match.

Design decisions

Why it looks and behaves the way it does.

I designed this as well as built it. These are the interface calls I made and the reasoning behind each one.

01

The slot grid is the interface

Everything a host does on scrim night happens on one board of numbered slots: drag a waiting team into a gap, reorder, double-click a row to see the lineup it is bringing. It stays editable until the scrim starts and streams other admins' changes live, because two people work the board at once on a busy night.

02

Every automated removal carries its reason

When the filter drops a team the log records why: no in-game name, lineup under the minimum, promoted into a freed slot, captain withdrew. A captain who lost a slot at fifteen minutes to start gets an answer, and the host does not have to reconstruct one.

03

Two audiences, two apps, one session

Players get play.finalist.live and organizers get app.finalist.live, on a shared session so nobody signs in twice. One combined dashboard would have meant every player screen carrying controls they can never press.

04

Reports route attention and never apply a penalty

Player reports group into one case per target and category, ordered by a priority score, so ten people reporting the same thing produce one case with ten filings. Nothing bans automatically, and there is no moderation tier above the organizer. If it happened in your scrim, you judge it.

Outcomes

What shipped.

  • Live across a player app, an organizer dashboard, public docs and a Discord bot, on one session
  • A scrim night runs unattended from registration opening through to the room reveal
  • Six tournament formats, with stage setup validated at save time rather than on match day
  • No wallet and no entry-fee field anywhere in the data model