August 29, 2008.
Recently I have been working on a desktop application where searching is a core feature, and made an important step towards search-field nirvana. Lets take a brief moment to consider the hierarchy of search fields.
The lowest form of the search field is one we are extremely familiar with: a textfield and a button labeled Search or perhaps Go! if the author was feeling particularly adventurous when he created it.
This implementation fails in a couple of ways. First, it requires you to use both the keyboard and the mouse to search, which slows you down. Second, it doesn't provide the user with any feedback about the quality of their search.
A quarter step up is the same search field, except it innovatively allows you to hit
return instead of using the mouse.
Stepping our game up a notch, we can give our textfield a history of some sort. This lets users easily return to previous searches, and also helps them remember what their searches were so they can refine them into something that is better--although they still have a hard time improving their searches since you're not giving them quick feedback.
Stepping our game up a notch, we throw the button away and instead display search results as the user types in real time. This is an important step up, as it allows users to see results as they work and quickly refine their query in response to the returned results.
There is one tiny flaw that comes out in this model, however. When the search takes longer than the time to type a letter, then this type of searching can result in a sticky or laggy user interface where typed letters don't appear instantiously.
The lagginess can be distracting enough to provoke thoughts like "What if I just made them press
enter instead? That would still be easy..." and has even caused some poor souls to revert to the lamentable A Box, A Button strategy.
Well friends, this is not the end.
There are two solutions to the lag problem. The first, which we won't consider, is to make the searching faster. The second is to delay the search until the search has become manageable or the user slows their typing.
The second is what I believe to be the key to making unsticky and helpful search boxes.
To help clarify, lets imagine we're conducting a search of a database with these characteristics (this pattern is relatively realistic if you are using a full text fuzzy matching search in a situation that defies indexing--ala
LIKE in SQLite--but even if these numbers were quite different, the discussed technique will still improve UI responsiveness).
|Number of Characters||Seconds to Search|
If you naively searched as you went, it would pause for 0.5 seconds after you typed the first character, 0.3 after the second (for a total of 0.8 seconds), 0.2 after typing the third (total 1.0 seconds) and so on.
Instead consider this strategy: after typing one character we would wait for 0.5 seconds before performing a search. If a second character was typed during that time span, we would invalidate the first search and begin a timer for the second search (delayed for 0.3 seconds). And so on.
In this scenario it would take 0.6 seconds (0.3 wait + 0.3 for search) to perform a 2 character search (compared to 0.8 in the naive implementation), but only 0.2 to perform a 4 character search (1.1 in naive), and 0.1 for a search greater than four characters (>=1.15 in naive). However, if the user slows down then the searches will go through and display guiding data to help the user refine their search. Also, once results become sufficiently quick we may revert back to the non-delayed search-as-we-go- strategy to ensure that the UI guides users to the upmost extent possible.
We can actually improve on this a bit, because it isn't necessary to wait for a full 0.5 seconds before performing a 1 character search, we simply need to wait a reasonable amount of time (a bit more time than it would take to type another character). If that amount of time was 0.2 seconds, then the time to search in the worst case (a one character search) improves from 1.0 seconds (in first non-naive implementation) to 0.7 seconds, while retaining the excellent performance in the normal (3+ characters) cases.
Another similar approach to averting lag, which isn't practical in all environments (especially in the browser), is to perform searches in a separate thread and update the results as the searches complete. This approach can theoretically have the best of both worlds (return data as soon as possible, with no user interface lag), but is the least trivial of the solutions to implement.
As to its 'least trivial' status, consider this scenario: when you type one character a search thread is launched (which will take 0.5 seconds to complete), then in 0.1 seconds you type a second character (which will complete in 0.3 seconds, or 0.1 seconds before the first search thread will complete). Then you would display the results from the two character search first, and 0.1 seconds later you would be notified that the one character search has completed. Presumably you might solve this by rejecting updates that began before the currently display result began--which is just some additional record keeping--but other issues arise.
One of those issues is that, depending on the backend upon which you are searching, you might cause all of the searches to perform more slowly by simultaniously performing several queries (up to a maximum of N simultaneous queries, where N is the length of the search term). You might avoid that by terminating all ongoing searches when a new search is created, but in the end its been my experience that using the strategy detailed above (non-threaded delayed onset searching) you can create an extremely compelling user interface experience without the complexity of moving the search to a separate thread.
I'd be more than curious to hear your thoughts and improvements.