Decision 01
Building shared pieces before building more dashboards
Problem
Decision
Trade-off
Result
Case studyFrontend Architecture2022
An interface for exploring healthcare datasets: summary views showing the shape of a population, over-time views showing how it changes, and detail tables for the records behind either.
Cohort
Coverage
Utilisation
Trend over time
MonthlyInterface recreated for portfolio purposes.
The complete case study in seven parts. The sections below provide the engineering detail behind this summary.
Problem
Context
Constraints
My role
Approach
Solution
Outcome
An analytics interface for exploring healthcare datasets: summary views for the shape of a population, over-time views for how it moves, and detail tables for the specific records behind either.
This case study is deliberately about the interface system rather than the medical domain. The engineering (shared pieces, caching, state modelling) is the transferable part.
01
Take the question
What this view has to answer
02
Point at a data source
A pre-aggregated endpoint
03
Compose the pieces
Frame, legend, axes, tooltip
04
Declare the filters
The view inherits the model
05
States come free
Empty, suppressed and partial handled
06
Accessibility comes free
Naming, table view, keyboard
07
Ship
No new chart implementation exists
One dashboard is a week's work. The difficulty is the twelfth, when the product has been answering a growing list of questions about the same underlying data and each answer arrived as a request for another view.
Built the obvious way, that produces a dozen dashboards that look almost but not quite the same: twelve tooltips, twelve legends, twelve slightly different empty states, and a dozen places to fix any given bug. The inconsistency is the visible symptom; the maintenance cost is the real one.
Underneath that sits a subtler problem. The filter surface is wide enough that the same data gets sliced many ways in one session, so caching behaviour is something users feel directly, and absent data is common and meaningful, which means an empty chart is technically true and practically misleading.
How do you make adding a view cheap without making every view the same, and without the pieces becoming a framework nobody can safely change?
What is a chart made of?
What makes it fast?
How are records read?
What kept it consistent?
Clinical and analytical interpretation belonged to qualified domain experts. The data team owned the model, APIs, and server-side aggregation, while the design team owned visual direction. The transferable frontend contribution covered the interface system and its supporting data layer.
Nothing recognisable can leave the product. That applies to this case study too, which is why the visuals are abstractions and the discussion stays at the level of the interface system.
The engineering has to be explicable without a single real screen, so the architecture had to be the interesting part.
With this many dimensions, you cannot pre-configure cache keys for the combinations users will actually ask for, and two identical requests routinely arrive written differently.
Caching had to be derived from the meaning of the filters rather than configured per combination.
Healthcare analytics is full of legitimately missing numbers: filtered to nothing, too small to report, not yet available. Showing all three as an empty chart is technically true and practically misleading.
Each state is modelled explicitly and presented differently, so the interface says which kind of nothing it is showing.
An export that does not state what it was filtered to cannot be used as evidence in anything.
Export and screenshot workflows carry their filter context with them rather than producing a bare image.
The product's job is to answer new questions about the same data, so 'more dashboards' is not a phase that ends.
A growing number of views could not be allowed to become a growing number of chart implementations.
Dashboards are built from a few shared pieces rather than from separate code each time. Adding a view means configuring what already exists, which is also why fixing a tooltip or an empty state fixes it everywhere at once.
Analyst
Asking about a population
Views
SummaryOver-timeDetail
Shared pieces
FrameLegendAxesEmpty and suppressed states
Filter model
Reduced to one canonical signature
Data layer
Cached per signaturePrepared data reused
Analytics APIs
Pre-aggregated, owned by the data team
Each of these solved a real problem and cost something in return. The trade-off is stated alongside the decision, because a decision without one was not really a decision.
Decision 01
Problem
Decision
Trade-off
Result
Decision 02
Problem
Decision
Trade-off
Result
Decision 03
Problem
Decision
Trade-off
Result
Decision 04
Problem
Decision
Trade-off
Result
A component library is easy to start and hard to get right. Extract too early and you encode assumptions from the only view that exists; extract too late and you are reconciling five implementations that already drifted.
The pieces here came out of the third dashboard, not the first. By then the shape was visible in real usage rather than guessed, and the thing worth stealing from this project is that timing, not the components.
The organising idea is that a dashboard should not contain a chart implementation. What each layer owns follows from that, and so does where the tests are worth writing.
Which question is being asked, and of what
Deliberately thin. A view is a data source, a filter declaration and a composition. If it grows rendering logic, something has gone wrong upstream.
Frame, legend, axes, tooltips, empty and suppressed states
The only place a chart is actually implemented, which is what makes a single fix land everywhere.
The filter state and its canonical signature
The signature, not the raw filter object, is what the cache keys on, so requests that mean the same thing are the same request.
Pre-aggregated responses per signature
Cached and revalidated in the background, which turns most navigation into a repaint rather than a request.
With this many filter dimensions, two identical requests arrive written differently: different key order, different ways of expressing empty, keys that do not affect the result at all. A cache keyed on the raw query misses almost every time, and the user feels every miss.
?to=mar&from=jan, ?from=jan&to=mar, ?from=jan&to=mar&sort=asc all resolve into One canonical signature (Key order normalised · empties unified · irrelevant keys dropped), which is then read by Cache hit and No second request.
A shared system makes the twelfth dashboard cheap and makes any view that genuinely needs to be different harder to build.
ChoiceCompose from shared pieces, and treat a view that fights them as a signal to look at the piece rather than to add an escape hatch. Documenting their constraints properly would have made that boundary clearer than it was.
Modelling empty, partial and suppressed separately means every shared piece carries more state handling than a happy-path chart would, for a case that never appears in a demo.
ChoiceModel them anyway. Telling 'nothing matched your filters' from 'this cannot be shown' is the difference between a working filter and an apparently broken dashboard.
Material React Table is a substantial dependency with its own opinions, an upgrade cost, and styling that sometimes has to be argued with rather than written.
ChoiceAdopt it, behind a thin wrapper. Building sorting, grouping, pinning and export well is a project; building it badly is a support queue nobody was funded to staff.
Adding a dashboard became a matter of composing existing pieces with a data source, which changed the unit of work from weeks to days and kept every view consistent with the others.
The states nobody demos (no data, suppressed, partial) are handled the same way everywhere, which is where analytics interfaces usually lose their users' trust.
The first is a guess and the fifth is a reconciliation. Waiting until the third gave a shape drawn from real usage, and it is the decision from this project I would keep unchanged.
Reading existing dashboards is a workable way to learn a system and a bad way to learn its limits. Twice, someone reasonably extended a piece in a way that broke it elsewhere.
The states nobody demos are the ones that decide whether a user believes the dashboard. An empty chart that could mean three different things is worse than an error.
Users experience a cache miss as slowness, not as a technical event. Normalising what a request means turned a cache that almost never hit into one that almost always does.
05 / Contact
Available for senior frontend, product engineering, and AI product engineering opportunities.
Location
Bangalore, India · Remote