How mainframe-era techniques and native browser APIs outperform modern frameworks
The Numbers
Before the philosophy, the receipts:
Frontend (multicardz.com):
- 32KB JavaScript bundle (15% of minified React)
- <100KB total payload including HTML, CSS, fonts, everything
- 0ms Total Blocking Time
- 0 Cumulative Layout Shift
- 0.3s First Contentful Paint
- 100 Lighthouse score
Backend:
- 32ms server processing to select any result from 1M cards
- 160ms round trip (the rest is network)
- Resultset size doesn’t affect query time
This is a drag-and-drop spatial query interface with active event listeners, not a static page. The old ways got me here.
What I Built
multicardz is a spatial data organization tool. Users drag tags into zones: drop left to group rows…
How mainframe-era techniques and native browser APIs outperform modern frameworks
The Numbers
Before the philosophy, the receipts:
Frontend (multicardz.com):
- 32KB JavaScript bundle (15% of minified React)
- <100KB total payload including HTML, CSS, fonts, everything
- 0ms Total Blocking Time
- 0 Cumulative Layout Shift
- 0.3s First Contentful Paint
- 100 Lighthouse score
Backend:
- 32ms server processing to select any result from 1M cards
- 160ms round trip (the rest is network)
- Resultset size doesn’t affect query time
This is a drag-and-drop spatial query interface with active event listeners, not a static page. The old ways got me here.
What I Built
multicardz is a spatial data organization tool. Users drag tags into zones: drop left to group rows, drop top to split columns, drop center to filter. Three drags can slice a million cards into exactly what you need.
I needed this because I wear three hats: Product Manager, Software Architect, CTO. The many-to-many relationships between features, code, releases, and dependencies don’t fit in Trello’s columns or Notion’s tables. I needed multidimensional matrices.
Building it confirmed for me that performance techniques everyone’s forgotten can work better than the new hotness.
The Backend: 1972 Called
In 1972, IBM’s Model 204 database stored query results as bitmaps:
“The FOUNDSET which results is referenced by later User Language statements either at a set level (e.g., to COUNT the records) or in a loop through the individual records… held in ‘bit map’ form.”
The insight: don’t touch the database until you have to. Keep intermediate results as bitmaps. Intersect and union them in memory. Only fetch actual records when you have the final result set.
I use RoaringBitmaps—a modern compressed bitmap implementation—to do exactly this. Each tag gets a bitmap of card IDs. When users drag tags:
- Bitmap operations (AND/OR/NOT) happen in memory
- No database hit during manipulation
- Only fetch card data for the final result set
The complexity is O(M) where M is the result set size, not O(N) where N is the total cards. Querying 10 cards from 1 million takes the same 32ms as querying 10 cards from 1,000.
This isn’t novel. It’s a 50-year-old technique that got buried under ORMs and “just query the database.”
The Frontend: DATAOS
You’ve heard of HATEOAS (Hypermedia As The Engine Of Application State). This is DATAOS: DOM As The Authority On State.
The principle: the DOM already represents your UI state. Why maintain a separate state object that you constantly sync (at least you hope) with the DOM? Read state from the DOM. Write state to the DOM. Eliminate the synchronization problem entirely.
When a user drags a tag in multicardz, I don’t:
- Update a state object
- Trigger a re-render
- Diff a virtual DOM
- Patch the real DOM
I just move the element:
zone.querySelector('.tags-wrapper').appendChild(tagElement);
Where is the tag? Look at the DOM. What tags are in the filter zone? Query the DOM. The visual representation IS the data structure.
This eliminates an entire category of bugs: state synchronization errors, and an entire category of performance overhead: reconciliation.
The Bundle
32KB of vanilla JavaScript gets you:
- Native browser drag-drop API (already optimized in C++)
- Direct DOM manipulation (no diffing algorithm)
- Zero framework overhead
- Complete control over performance
React’s minimum is ~215KB. What do you get for that 183KB? A reconciliation algorithm to sync state with DOM. But if the DOM contains your state, why do you need reconciliation?
CSS handles the visual complexity. You’d be surprised how little JavaScript you need even for sophisticated animations and effects. The browser already knows how to be fast—stop fighting it.
Specific Techniques
Font subsetting: Google Fonts for the “multicardz” work mark was adding almost 500ms to FCP. I subset to just those 9 characters and vendor them:
pyftsubset font.woff2 --text="multicardz" --output-file=subset.woff2
2KB instead of 100KB+.
Script defer: The timing worked out beautifully; JavaScript becomes available almost exactly as the DOM elements finish rendering (reminds me of The Story of Mel).
<script defer src="/static/js/drag-drop.min.js"></script>
Native APIs: The drag-drop API is implemented in C++ in every browser and runs faster than anything I could write. It handles hit testing, event propagation, and cursor changes at native speed. What kind of crazy pills would make me want to reimplement this in JavaScript?
When This Applies
DATAOS works when the DOM represents user state—which is most web applications. Data displays, forms, dashboards, editors, spatial interfaces.
It doesn’t apply when the DOM is just a render target for something else:
- Games: State is physics, positions, velocities—the DOM just draws pixels
- Video: State is playback, buffering, streams—the DOM displays frames
- Canvas apps: Drawing tools, image editors—the DOM shows results, not state
If your users manipulate things that live in the DOM, the DOM can be your state. If the DOM is just a viewport into something else, it can’t.
The Thesis
The best frontend technology is ~15 years old (native DOM APIs, CSS3). The best backend technique is ~50 years old (bitmap indexes). The layers we’ve added since then—virtual DOMs, ORMs, state management libraries—often exist to solve problems those layers created. We created frameworks to manage the complexity that came from treating state as something other than what the user just changed on screen. Then they wrote frameworks to manage the complexity created by the frameworks that were written to manage the complexity of that paradigm.
Strip it all away. Read state from the DOM. Keep result sets as bitmaps. Hit the database only when you must.
100 Lighthouse. 0ms TBT. 32ms queries. The old ways work.