1. The challenge
Managing large-scale datasets with over 100,000 rows presented a severe usability bottleneck for the power users I supported, such as carbon data analysts. They needed to isolate specific subsets of data by combining multiple criteria multiple times a day.
The existing filter chips required an expensive multi-step interaction loop: opening a dropdown, selecting options, closing it, waiting for the table to reload, and repeating the exact same process for the next filter. Two filters meant two distinct menu dives and two consecutive API requests. This high interaction cost led to UI fatigue, wasted network overhead, and slowed down daily analytical workflows.
2. The strategy and selection
To resolve this efficiency bottleneck, I evaluated three distinct architectural patterns:
- Option 1: Batching button.Keep the existing dropdown menus but add an explicit “Apply filters” button to delay network calls. Rejected: this solved the network spam but did not reduce the high click count required to open and close multiple menus.
- Option 2: Advanced visual query builder.Implement a comprehensive, nested boolean drag-and-drop query UI. Rejected: over-engineered for this product. The analysts did not require deeply nested AND/OR grouping logic; they needed speed.
- Option 3: Hybrid search bar (chosen).Introduce a text-based, keyboard-driven query bar underneath the existing visual filter chips.
Why I chose option 3: it offered an advanced express lane for keyboard-proficient power users to type entire multi-filter queries in seconds, while safely preserving the familiar visual dropdown chips for casual users.
3. The implementation
I deployed an inline, text-based search bar that acts as a flexible editing canvas. The frontend architecture functions as follows.
Guided autocomplete and a small grammar
Focusing the empty bar opens a structural list of available columns (status, assignee, and so on). Typing filters this list instantly using fast local metadata. The input layout adopts a flat, highly readable syntax:
- Commas signify OR operations within a single field.
- Plus signs (+) signify AND operations between different fields.
status: to-do, in-progress + assignee: johnDecoupled parsing and validation
The text string is purely a layout mechanism. The client application instantly converts the text into a strongly typed JSON array of objects, which serves as the uniform source of truth:
[
{
"filterName": "status",
"operator": "is",
"values": ["to-do", "in-progress"]
},
{
"filterName": "assignee",
"operator": "is",
"values": ["john"]
}
]The backend API receives only this structured JSON payload, meaning it never performs heavy text parsing. Autocomplete suggests parameters locally, while expensive volumetric lookups (like thousands of assignees) are debounced to prevent character-by-character network requests.
Crucially, the core data table remains static until the user presses Enter, compiling multiple criteria modifications into a single batch request. Once executed, the standalone visual filter chips instantly sync to reflect the new state.
- Old flow
- Open Status → Select → Close (table updates) → Open Assignee → Select → Close (table updates)
- New flow
- Type query string → Press Enter (table updates exactly once)
Fault-tolerant constraints
- Typos.Typing
statuzsimply clears the suggestion list and disables submission without wiping out the user’s progress. - Invalid values.Typing an unrecognised value like
status: fooflags the specific token with a warning icon (△), blocking the API request while letting the user cleanly edit the mistake. - Syntax breakage.Forgetting a separator (for example, missing the +) treats the input as a single unfinished string fragment, safely preventing a broken query submission.
Sample tokenizer
export interface FilterToken {
filterName: string;
operator: string;
values: string[];
isValid: boolean;
errorReason?: string;
}4. The impact and results
Shifting to a keyboard-driven batch workflow yielded immediate performance and interaction dividends:
- 66% reduction in clicks.Complex queries requiring three distinct filtering criteria dropped from 6 interaction clicks down to just 2 (focusing the bar and hitting Enter).
- 50%+ reduction in API load.Multi-filter query generation dropped from multiple consecutive data requests down to exactly one server request per search sequence.
- Zero disruption to casual workflows.Because the query bar sits parallel to the existing chip menus, standard user adoption remained stable while power user processing speeds accelerated.
5. Key takeaways and lessons
What I learned
Uncoupling the textual "editing surface" from the structured JSON state was a massive win that kept backend processing incredibly clean. However, engineering the search bar to be completely fault-tolerant was a massive technical task.
Allowing users the freedom of an open text area meant my frontend validation layer had to perform heavy lifting. I had to ensure that typos or broken syntax didn't cause destructive UI behaviors like wiping out user progress or prematurely executing broken requests. Writing a resilient tokenizer that gracefully flagged inline errors (like status: foo ⚠) without rewriting or deleting the user's active workspace required meticulous input state isolation and complex verification workflows. Keeping the query grammar flat was the only thing that kept this validation loop maintainable.
What I would do differently next time
While storing state in a shared JSON object works, maintaining bi-directional synchronization between raw text substrings and visual filter chips introduced intricate string-parsing edge cases. If a user deletes a visual chip, stripping out that exact text value along with its surrounding + boundaries without breaking the remaining text string is highly complex.
Next time, I would implement an immutable pill-box tokenized input (similar to Slack or GitHub’s search bars). The moment a field or operator is selected, it would render instantly as a discrete visual UI token inside the box. This would completely eliminate raw text parsing, handle edge-case characters (like commas inside actual text values) effortlessly, and naturally combine the chips and the input bar into a single, cohesive component.