Senior Frontend Engineer8 years · React · TypeScript · TRM Labs, Paytm · remote · open to relocation · available immediately

I make broken interfaces fast, accessible and global.

I build data-heavy and AI product interfaces in React and TypeScript, with a focus on performance and accessibility.

From my production work at TRM Labs

Case studies

The context, constraints and trade-offs behind the demos.

Writing

Short technical write-ups from the work behind the demos.

How I make a team faster

The work that doesn’t show up in a screenshot: systems that make everyone else’s PRs safer.

  1. 01

    Standards other teams build on

    Proposed a Radix + Tailwind design system with accessibility built into the components. All new UI at TRM is built on it.

  2. 02

    Contracts across team boundaries

    Generated OpenAPI types in a pre-commit hook and CI, so an incompatible API change that the type checks can detect fails in the backend PR instead of in production.

  3. 03

    Guardrails that scale review

    Typed translation keys and CI checks that catch missing translations before merge, with agent rules so AI-written PRs follow the same process.

  4. 04

    Tooling for an AI-native team

    Split a 3,000-line agent instruction file into scoped skills, built a parallel git-worktree skill, and wrote agents the team used daily.

  5. 05

    Prototype before building

    Built working prototypes with AI agents and tested them with internal investigators before committing to a design, then proposed one assistant instead of two.

  6. 06

    Confidence at speed

    Pushed for regression coverage as AI-assisted PR volume grew, and led a 20+ PR performance push in small, reviewable, revertable steps.

Interactive lab · six things I fix for a living

Break it, then watch me fix it.

This page is a working demo. Six problems I fixed in production are re-created here and measured live in your browser.

Break the whole site and let me fix it, or use each card on its own below. The problems are synthetic workloads; the measurements are real, and each card explains how it measures.

Loading the demos…

  • Accessibility: healthy
  • Translations: healthy
  • Sidebar animation: healthy
  • Bundle size: healthy
  • Network requests: healthy
  • Graph plotting: healthy
fix 01 · performanceHealthy

A graph that freezes while plotting

The broken version finds each incoming element’s index by scanning everything already plotted (findIndex inside the loop): quadratic work that blocks the main thread. The fix memoizes each element’s index in a Map as it is plotted, so every lookup is O(1): linear work, identical output.

Before · measured—not measured yet
After · measured—not measured yet
How this is measured

Measured live on this device. Both versions get the same seeded records (about 6% repeat an earlier id) and produce the same plot; the time covers the plotting function only, not drawing. The record count comes from a quick calibration so the broken version takes about 1–2 s here, with a floor of 1,600. One run, so background activity in the browser can move the number.

The TRM Labs note below is a past production result, not measured here.

In production at TRM Labs: graph plot matching on 1,600 elements went from 15.5 s → 14 ms after a 20+ PR push I led.

fix 02 · renderingHealthy

A sidebar that stutters

Opening the sidebar resizes the graph. Broken: heavy work runs during the animation. Fixed: the work waits until the transition ends, then runs in small idle chunks.

Before · dropped frames—not measured yet
After · dropped frames—not measured yet
How this is measured

Measured live on this device. The sidebar runs its 0.5 s transition while animation frames are recorded for 0.65 s; both versions do the same 350 ms of work. Broken runs it in 70 ms blocks during the animation; fixed waits for the transition, then runs it in idle-time slices, which can finish after the window closes: the work is moved, not removed. Dropped frames are an estimate from frame timing (gaps longer than about 1.5 of this display’s frames), not a paint log.

The TRM Labs note below is a past production result, not measured here.

At TRM Labs: deferring sidebar work while the graph resized removed visible jank.

fix 03 · loadingHealthy

Every route, shipped up front

Broken: all route code loads before anything works. Fixed: only the core loads; each route’s code loads the first time you open it.

core.js—
admin.js—
graph.js—
reports.js—
search.js—
settings.js—
Before · JS needed to start—not measured yet
After · JS needed to start—not measured yet
How this is measured

Measured live. The demo loads its own code-split chunks with dynamic import(); sizes are each chunk’s decoded size from Resource Timing. Broken loads the core and every route before anything works; fixed loads the core only. On a repeat run the chunks are already in memory: sizes are still shown, and the result says nothing was downloaded.

The TRM Labs note below is a past production result, not measured here.

At TRM Labs: route-level code splitting cut the initial bundle by ~85%. This demo’s chunks are sized to mirror that.

fix 04 · networkHealthy

Requests nobody asked for

Broken: every panel fetches on load, a late banner shoves the layout, and a status check polls 30 times a second. Fixed: panels fetch as you scroll near them, space is reserved, and polling backs off.

  • Loading panel 1
  • Loading panel 2
  • Loading panel 3
  • Loading panel 4
  • Loading panel 5
  • Loading panel 6
  • Loading panel 7
  • Loading panel 8
  • Loading panel 9
  • Loading panel 10
  • Loading panel 11
  • Loading panel 12
  • Loading panel 13
  • Loading panel 14
  • Loading panel 15
  • Loading panel 16
  • Loading panel 17
  • Loading panel 18
  • Loading panel 19
  • Loading panel 20
  • Loading panel 21
  • Loading panel 22
  • Loading panel 23
  • Loading panel 24
status polling: 0 req/spanels fetched: 0/24
Before · requests in 6 s—not measured yet
After · requests in 6 s—not measured yet
How this is measured

Measured live. Counts this demo’s own requests over 6 s: entity panels and a status check. Broken fetches all 24 panels at once and polls status about 30 times a second (it stops after 20 s); fixed fetches panels as they scroll near view and polls every 20 s. Layout shift appears only in browsers that report it (Chromium).

The TRM Labs note below is a past production result, not measured here.

At TRM Labs: a p95 25 s request moved off the entity page’s initial load, and polling went from 30 req/s → ~3 req/min.

fix 05 · accessibilityHealthy

A panel only mouse users can use

Broken: every control works with a mouse and nothing else. Clickable divs, no focus ring, an unlabeled icon, faint text. Fixed: real buttons and menus, labels, visible focus, readable contrast, and a sortable table you can drive from the keyboard.

AddressTypeRisk
0x9f…a21ExchangeLow
0x3c…7e0MixerHigh
0x71…b4dOTC deskMedium
0xa4…19cBridgeHigh

WS rows · AD headers · arrow keys cells · Enter sorts

4 addresses linked to a flagged entity.

In the broken state this demo panel is intentionally inaccessible. The Fix button below always works.

Before · demo checks—not measured yet
After · demo checks—not measured yet
How this is measured

Measured live, on this panel only: how many of its interactive elements a keyboard user can reach, how many lack an accessible name, and the contrast of its body text. Three automated checks to illustrate the fix, not a WCAG audit; real conformance needs screen-reader and manual testing.

The TRM Labs note below is a past production result, not measured here.

At TRM Labs: an accessible Radix + Tailwind design system (all new UI is built on it) and keyboard tables for a government contract.

fix 06 · internationalizationHealthy

A product that only speaks English

Broken: strings are hard-coded, so switching language shows raw keys. Fixed: typed keys, and translations that load the first time a language is picked, then come from cache.

en · bundled with the page

Investigation overview

6 addresses linked to a flagged entity

Open graphAlertsUpdated just now
// generated from en.json, checked in CI
type I18nKey = 'alerts' | 'open' | 'sub' | 'title' | 'updated';
Before · missing strings—not measured yet
After · missing strings—not measured yet
How this is measured

Measured live. Counts strings missing in the chosen language: broken renders raw keys, so all 5 are missing; fixed loads that language’s file the first time it is picked and reuses it after. A request is counted only when the browser actually downloaded the file.

The TRM Labs note below is a past production result, not measured here.

Led frontend internationalization at TRM Labs: typed keys, lazy-loaded translations, and pre-commit and CI checks that flag missing translation keys before merge.

Your session’s results

Filled in from real measurements as you break and fix things above.

Not startedPR #581 · Let Subham fix it
Before and after measurements from this session, with production results
FixBefore (your device)After (your device)In production
Plot matching——15.5 s → 14 ms
Sidebar animation——Jank removed
JS on load——~85% smaller
Requests in 6 s——30 req/s → ~3 req/min polling
Demo accessibility checks——Design system for all new UI
Missing translations——Typed keys + CI checks

Where I’ve been fixing things

  1. Nov 2023 – Oct 2026

    TRM Labs

    Frontend Engineer

    TRM Forensics, a graph-based blockchain investigation product used by government agencies and financial institutions.

  2. Mar 2022 – Oct 2023

    Paytm

    Senior Software Engineer

    Paytm Payments and Paytm Money.

  3. Oct 2020 – Mar 2022

    Enterprise Minds

    Senior Software Engineer, Consulting

    Frontend modernization for product companies.

  4. Apr 2018 – Oct 2020

    Infosys

    Senior Systems Engineer

    Mobile and frontend delivery for enterprise clients.

Hiring for a senior, staff or lead frontend role?

Bhubaneswar, India · remote · open to relocation · available immediately