11 keystrokes that made my jQuery selector run 10x faster
ASP.NET, CSS, JavaScript, Performance, jQuery By Dave Ward on June 9th, 2009As an ASP.NET developer working on the client-side, one problem you’ll encounter is how to reference the HTML elements that ASP.NET web controls generate. All too often, you find yourself wasting time trying to reference TextBox1, when the element is actually rendered as ctl00_panel1_wizard1_TextBox1.
Much has been written about this, including a post of my own, so I won’t go into detail about many of the workarounds. Instead, I want to take a closer look at the performance drawbacks of one popular solution: the [attribute$=value] selector.
By specifying id as the attribute in this selector, you can avoid ASP.NET’s ClientID issues completely. No matter what the framework prefixes your rendered elements with, they still “end with” the ID you specify at design time. This makes the “ends with” selector a convenient alternative to injecting a control’s ClientID property via angle-brackets.
However, are we trading performance for this convenience? If so, how much?
When Craig Shoemaker asked that question while interviewing me for an upcoming episode of Polymorphic Podcast, I realized I didn’t know the answer as clearly as I’d like. So, I decided to do a bit of benchmarking.
In this post, I’ll share the results of that benchmarking, and show you one way to significantly improve the performance of this convenient selector.
The test scenario
One difficulty when analyzing selector performance is that they all perform well on small test pages. Most performance issues aren’t readily apparent until a page grows in complexity and contains many elements. This can easily leave you overly confident in techniques that survive a simple proof of concept, but don’t scale well to practical usage.
Rather than construct a complex demonstration page from scratch to test against, I decided to use an existing page. With over 160 comments at the time of writing (and testing), the Highslide JS .NET project page is an ideal candidate. Its 1,000+ DOM elements are well suited to expose poorly performing selectors.
Enclosing each comment on the page, there’s a div like this one:
<div id="div-comment-35496" class="comment"> <!-- Comment content here --> </div>
If you imagine that the “div-“ prefix is “ctl00_”, these IDs are a great substitute for the ClientIDs that ASP.NET controls render within naming containers.
So for purposes of testing, I attempted to select the element div-comment-35496 on that page, assuming that I knew its ID of comment-35496 at design time and that the “div-“ prefix was added at run time. This is identical to the process of selecting the div rendered by an ASP.NET Panel control within a Content Page, for example.
Test methodology
To benchmark each selector, I used the following JavaScript:
var iterations = 100; var totalTime = 0; // Repeat the test the specified number of iterations. for (i = 0; i < iterations; i++) { // Record the starting time, in UTC milliseconds. var start = new Date().getTime(); // Execute the selector. The result does not need // to be used or assigned to determine how long // the selector itself takes to run. $('[id$=comment-35496]'); // Record the ending time, in UTC milliseconds. var end = new Date().getTime(); // Determine how many milliseconds elapsed and // increment the test's totalTime counter. totalTime += (end - start); } // Report the average time taken by one iteration. alert(totalTime / iterations);
This simply executes the selector 100 times, recording how many milliseconds each run takes, and then determines the average.
I had originally recorded and displayed an array of each individual time, but found there was very little variation and stopped tracking each execution. The average is good enough for these purposes.
For each selector, I ran the test five times and then averaged the results, resulting in an average across 500 separate executions in semi-isolated batches. Then, I repeated the process for each major browser.
CERN probably won’t be flying me out to work on the LHC with this level of rigor, but it’s accurate enough for measuring the relative performance change between different selectors.
Update: Just to reiterate that last point, these results will not be precise. However, they will be imprecise in a consistent manner that’s suitable for relative comparison.
Also, if you decide to run your own tests like these, you should use this improved version of the testing method.
The baseline
jQuery’s #id selector leverages browsers’ native getElementById routine. Though slower than calling getElementById directly, this is a very fast way to reference our div as a jQuery object:
$('#div-comment-35496')
As you may expect, this browser assisted selector is fastest. In fact, every major browser consistently performed this in less than one millisecond in my testing. Most in less than a third of a millisecond.
To safely use this selector in ASP.NET, we have to manually inject the a control’s ClientID property. Otherwise, any naming container added, removed, or renamed would break our client-side code.
You’ve probably seen that accomplished like this:
$('#<%= comment-35496.ClientID %>')
It looks messy and requires a bit more effort, but it’s fast.
ASP.NET 4.0’s ClientIDMode property promises to eliminate this inconvenience in the long-term, but we’re stuck with it for now. For that matter, with many projects still using ASP.NET 1.x and 2.0, slow to adopt the latest version, we may be stuck with the problem for quite some time to come.
Convenience
To avoid the messy work of manually injecting ASP.NET’s rendered ClientIDs, you may have seen the suggestion that this is an easier way:
$('[id$=comment-35496]')
Indeed, this will successfully select the element that we’re after. Since its ID is div-comment-35496, searching for an element whose ID “ends with” comment-35496 works as desired.
Eliminating the angle-brackets is aesthetically pleasing and it’s less up-front work, but what about performance?
jQuery’s selector engine implements “ends with” by performing this test on every element in question, where value is the attribute you’ve specified and check is the string that you’re searching for:
value.substr(value.length - check.length) === check
That’s not so bad if you’re only doing it once, but doing it for every element on the page is a different story. Remember our test page has thousands of elements.
What’s worse, jQuery has no way to know that only one element should match the pattern. So, it must continue iterating through to the end of the page, even after locating the single element that we’re actually interested in.
How bad is it? Over the course of several hundred executions on the test page, I obtained these average speeds for a single $= selector execution in each browser:

The Chrome numbers are unsurprising, given its speedy V8 JavaScript engine. IE8 and Firefox 3.5 perform admirably too. These newer browsers all executed the selector very nearly as quickly as document.getElementById.
However, IE7 and Firefox 3.0 are substantially slower when executing the “ends with” selector. Remembering the non-trivial difference that Firebug made in this interesting post about JSON parsing speeds, I also tested with and without Firebug enabled in Firefox 3.0 (which turned out to be a significant factor here too).
Even though we’re only talking about milliseconds, the penalty in older browsers is too large to ignore — especially when the vast majority of users are still on IE6, IE7, or Firefox 3.0, not the new generation of faster browsers.
Convenience optimized
Remembering that the #id selector is quick because it leverages the native speed of getElementById, we can help jQuery execute the $= selector more efficiently.
If we modify the selector to descend from an HTML tag before performing the “ends with” search, jQuery can use getElementsByTagName to pre-filter the set of elements to search within. Since getElementsByTagName is a native browser routine, it is much faster than jQuery’s interpreted selector engine.
For example, since we know the element we’re after is a div, we could optimize the previous $= selector like this:
$('div[id$=comment-35457]')
The benefit is substantial:

The optimized selector runs more than twice as quickly in IE7 and Firefox 3.0!
Along the same lines, jQuery’s Sizzle engine is able to select CSS classes faster than it can perform substring searches within arbitrary attributes. So, the selector can be further optimized by descending from both an HTML tag and a CSS class.
Since the particular div we’re testing against has a CSS class of comment, let’s try this selector:
$('div.comment[id$=comment-35457]')
The results?

IE7 doesn’t improve much, but Firefox 3.0 shows another excellent increase in its performance. The reason that Firefox shines here is that it implements a native getElementsByClassName routine that IE7 doesn’t (though IE8 does).
While slower than the getElementById powered #id selector, these optimizations have given us a 3-10x speed increase while referencing exactly the same element as the original $= selector. That’s a pretty good return on the investment of typing a measly eleven characters (div.comment) in front of the selector!
A mountain out of a molehill?
This post may seem like a lot of effort to spend on a seemingly tiny performance differential. In fact, I almost considered leaving this one on the shelf, because it’s often difficult to sell the importance of milliseconds.
But, you know what? Milliseconds count! A performance difference of a few dozen milliseconds is a perceptible delay. A few hundred will alienate your users.
Research has consistently shown a strong correlation between fast sites and higher conversion rates, more user actions per visit, and user satisfaction. Compounded across the thousands or millions of times a particular function in your application will be used, it is absolutely worthwhile to invest a few minutes in order to save a few milliseconds.
Conclusion
I hope you’ve found this information useful. Without hard data, it’s difficult to decide which optimizations are premature and which are worthwhile. Especially since your page is likely to grow more complex over time, I think the data clearly shows that selectors which don’t directly target an ID should always descend from an HTML tag when possible.
Perhaps the most important takeaway is that you must keep in mind how easy it is to write one succinct line of jQuery code that results in a non-trivial loop. The real danger lies in putting a selector like $= within a loop yourself, unaware that what appears to be a simple loop is actually a relatively sluggish nested loop.
The other lesson here is that if speed is crucial, you should inject ClientIDs and use the #id selector (as in the baseline shown above). Even the most optimized “ends with” selector in this post still runs at least one order of magnitude slower than the direct #id selector.
Instead of simply posting that div.class[id$=id] is faster than [id$=id], I wanted to explain the sequence of events that led me to that determination. Armed with knowledge of how I optimized the “ends with” selector, I hope that you have a few new optimization tricks up your sleeve now.
Similar posts
What do you think? Your comments are welcome.
I appreciate all of your comments, questions, and other feedback, but please try to stay on topic. If you have a question unrelated to this post, I recommend posting on the ASP.NET forums instead.
If you're replying to an existing comment, please use the threading feature. To do this, click the "Reply to this" link underneath the comment you're replying to.
9 Mentions Elsewhere
- DotNetShoutout
- Reflective Perspective - Chris Alcock » The Morning Brew #365
- DotNetBurner - jQuery
- New and Notable 330 : Sam Gentile's Blog (if (DeveloperTask == Communication && OS == Windows)
- 9eFish
- Complex Optimization Guide Part 10. Ajax Optimization | odd creations blog
- jQuery, xhtml, CSS, ASP.NET, Flash, Flex, Red5 | dattaproffs.se » Blog Archive » Använd jQuerys EndsWith för att slippa ha din javascript kod i html dokumentet när ASP.NET ändrar ClientID
- Accessing controls inside a Repeater from Javascript « rioting bits
- 11 keystrokes that made my jQuery selector run 10x faster | Encosia


I did some profiling last month because I was wondering the same sort of thing… (there’s some funny parallels in our work, I swear…) But in this case, I’m pretty certain your “Convenience optimized” solution is wrong because the third most popular browser (as of May 2009 as far as marketshare.hitslink.com/browser-market-share.aspx can tell) is MSIE6 which doesn’t have .getElementsByClassName so searching by element type and css class is substantially slower than just searching by element type. Thus, for the unfortunate 17% of users that apparently can’t let MSIE6 just die already, the slightly less optimal form of div[id$=aspnethatesus] is actually the optimal choice. (Turn on compatibility mode in MSIE8 to see how its JS engine runs without .getElementByClassName)
On a related note < %= aspnethatesus.ClientID %> isn’t quite perfectly fast, it’s just that more of its work is being done on the server where javascript can’t measure. Above and beyond that, every time you use it you have to be in the context of your page — which is to say, you’re either not client-caching your static javascript files because your javascript is inline or you’re probably passing a lot of strings into their functions whenever you want to do anything at all which would be the technical reason we prefer to write div[id$=aspnethatesus].
I’ve found a good middle ground here is something like this at the end of the page:
Then, you’re free to reference TextBox1 with $(‘#’ + ClientIDs.TextBox1) in JavaScript includes off-page.
I’m doing something similar – so “What he said!”
JMiller: I suspect you typed a little faster than you thought.
Yes, the tags will get interpreted on the server (once per page load), but it will get written to the client page EXACTLY as if you wrote $(‘#ctl00_aspnethatesus’) and will work EXACTLY the same. It has to, the client page will never see the tags, only the substituted value.
You do bring up a valid point that cannot be in static js files. But it is little effort or affect to assign the to a js variable in the page proper, and referring to that variable object in the static js file.
IE7 doesn’t implement getElementsByClassName either, which is why it didn’t show much improvement when adding the CSS filter, compared to the marked improvement in Firefox. It didn’t run slower in IE7 either though. I haven’t tested at all in IE6, but the same ought to be true there too.
It would be interesting to see some numbers on that (and any other variations of the variables).
Wouldn’t it also be profitable to add “:first” to the end of the selector, i.e. div.class[id$=theId]:first, to avoid the problem you mentioned wherein the browsers don’t know that they only need to grab one, instead of checking them all? Obviously such a thought is useless without benchmarks, but it seems plausible…
That’s an excellent idea. I don’t know how :first is implemented in jQuery, but if it’s sane at all then it should be much faster for elements early on in the page.
I’d love to see some benchmarks on that.
“Wouldn’t it also be profitable to add “:first” to the end of the selector, i.e. div.class[id$=theId]:first”
You could also use array indexing to access the element:
div.class[id$=theId][0];
$(‘selector’)[0] does guarantee that you’re only working against the first result, but it doesn’t prevent jQuery from continuing to search the document after it locates that first result.
I agree with Domenic that adding :first should speed things up. Most pages are structured so that navigation etc. is higher in the Dom hierarchy. Assuming that this is not where you want to select elements, you can surround part of your page with a div with and id (e.g “content”). Your jQuery select can now be written like this:
$(‘div.comment[id$=comment-35457]:first’, $(‘”content”‘))
again reducing the amount of elements jQuery has to search through. Saving $(‘”content”‘) can be saved in a variable if you have to execute multiple selects.
Isn’t this a classical Golden Hammer antipattern? Why use $(‘#’+whatever) when you can actually use document.getElementById if you actually care about speed?
Also, provided that you indeed want to search for many elements in the same DOM with the ends-with selector on id, caching the ids of the elements would be a better optimisation to start with. Maybe even use a string cache of comma separated ids and run regex on it.
However, in the light of the new JSON native object in both IE and FF, wouldn’t it be even better if someone would just add jQuery as a browser standard and then implement it natively? THAT would be an optimisation!
If you’re doing something very simple, like getting or setting the value of a textbox, getElementById is the right thing to do. Overusing frameworks when not necessary is definitely a problem.
Generally, if I’ve got jQuery on the page and I’m selecting elements, I’m going to be performing jQuery (or jQuery plugin) specific tasks that getElementById doesn’t help with though.
But is not the advantage of using $(’#’+whatever) in jQuery is because getElementById does not work or work the same across all browsers/all versions?
Most likely, the control we’re looking for is inside another div (#header, #footer, #comments, etc) on the page, so can’t we limit the scope to that div only and then query its children for specific element?
Something like:
$(‘div#comments’).children(‘[id$=comment-34457]‘);
or shorter (and faster perhaps?)
$(‘div#comments[id$=comment-35457]‘);
I haven’t tried any of those, though :)
I saw someone mention this above, but they didn’t clarify. If you have a large DOM tree, you can use the ‘context’ parameter in your $() call to reduce the scope of the search. This can HEAVILY reduce look-up times (varying of course).
Example: $(‘div.comment[id$=comment-34457]:first’, $(‘#comments’));
http://docs.jquery.com/Core/jQuery#expressioncontext
Interesting article. I’m just beginning to use jquery in my applications, so I don’t really have many attribute selectors, but it’s good to know the downfalls and how to fix it before I find myself actually using it a lot. I never think about issues like this, so it’s good that someone else is :).
@De & @Domenic: A lot of the performance sink in jQuery selectors is just parsing the string that is the selector, with MSIE generally having the worst parsing performance. So sub-selecting usually doesn’t have a positive effect and is actually detrimental to performance for MSIE users (unless you’re sub-selecting to a CSS class in MSIE6 in which case the reduction of scanned DOM elements is likely to give a perf boost), and, surprisingly, :first also takes a performance hit.
Sub-selecting with a .find versus just selecting the subset from the get-go was about 1:4 perf in MSIE8, 1:1 or 1:2 in FF3, and too fast to be relevant in Chrome.
:first put a ~10% perf hit in FF3 and a 500% perf hit in MSIE8 (though I should note that MSIE8′s baseline was substantially faster than FF3 so the net delta isn’t as huge as it sounds). That said, my DOM was pretty simple so it’s not like there were many other elements beyond the first one to select — point isn’t that :first is bad, just that there’s a cost associated with it so don’t habitually use it because of what it “should” be doing.
Short-cutting :first out by going for [0] should get the first matched element, but won’t speed up the selector (excepting when :first’s perf hit doesn’t get made up in possible scan+select avoidance w/nod @cancel).
Here’s a quick single-selector test if you’re curious…
function jqueryTest() {
var selector = $(“#jqueryInput”).val();
var d1 = new Date();
for (i = 0; i < 500; i++) { $(selector); }
var d2 = new Date();
alert(d2.getTime() – d1.getTime());
}
@MHarr: I don’t doubt that response.write aspnethatesus.ClientID will do exactly what it says; I’m just saying that a metric that’s measuring JS performance won’t measure the server-side performance hit of a render-time inline response.write which would be included in overall page performance vs. user attention span.
@Dave: Ooh, I like that object format. I had briefly souped all of the IDs to variables which made splitting out the static JS really dodgy looking & behaving. I may give that object format a try in the future, excepting that
@Siderite is reasonably correct about the “golden hammer” and I’m more likely to stick with the div[id$=WhatICalledIt] than try to lace through ClientIDs.WhatICalledIt = resp.write(WhatICalledIt.ClientID); #+ClientIDs.WhatICalledIt because all of the milliseconds that I save my users won’t amount to the actual seconds that I’ve saved myself.
After all, jQuery is supposed to let me “Write Less, Do More” and mucking about with the precise ClientIDs isn’t writing less.
I use class name’s instead of id’s, it’s much easier than any approach i’ve seen above.
Using clientid’s, or writing more complicated selectors is silly in my opinion.
@Phillip Haydon,
This post is about optimization the speed of your code, not about making your code easy-to-use/read.
Your test code is counting both the time it takes to create 100 new Date objects and also the time it takes to call getTime() 200 times. These times might be negligible compared to the selector time, but it’s possible that these may effect your results. Is there a reason why you didn’t do something like the following?
var iterations = 100;
var totalTime = 0;
var start = new Date().getTime();
for (i = 0; i < iterations; i++) {
$(‘[id$=comment-35496]‘);
}
var end = new Date().getTime();
totalTime += (end – start);
alert(totalTime / iterations);
The reason it ended up that way is because my original testing logged every iteration, pushed it into an array, and then displayed the entire array. After I was satisfied that there was no significant variation between the individual iterations, I moved to just tracking the simple average.
If new Date() is particularly slow, you’re right that it might skew the absolute accuracy a bit, but it shouldn’t affect the relative comparison between the same browser/version.
The things that really worries me about this discussion is the fact that nobody (as far as I can see) has mentioned the perils of selecting elements with an ID that ends in some value.
I suppose that it’s down to the developer to have an understanding of the range of ID values that are used in a page, but it’s feasible (particularly woth dynamically generated content) that there are several IDs that end in the same string and an attempt to select one would result in the other(s) being selected too.
i.e.
$(’div#comments[id$=comment-35496]‘);
would select not only…
…but also…
So, as you can see, the original code was not only non-performat, but potentially incorrect too.
The technique described is, however, an interesting way of getting round ASP.NETs ID renaming behaviour, and I’m sure that it will be useful in other ways too.
P.S. I do seem to remember reading an article about coercing ASP.NET into producing predictable IDs and, If I can find it, I’ll update this post.
RTFM Bob, then you might discover how to format code sections :-(
i.e.
$(’div#comments[id$=comment-35496]‘);
would select not only…
…but also…
Grrr, enclosed the code in the suggest ‘pre’ element, and it still didn’t post correctly – so I’ll paraphrase…
i.e.
$(’div#comments[id$=comment-35496]‘);
…would select not only elements with an ID of “div-comment-35496″, but also those with an ID of “div-comment-135496″
Bad example but good point. It’s probably okay for most pages that aren’t flooded with arbitrarily-ID’d HTML though.
I tend to write my functions that take the either exact Client ID or jQuery object as parameters; I really don’t care about having those long ugly IDs in my client code, especially if they can be written via server code.
Yes – very bad case of fat thumbs and cut and paste-itis. Ooops!
I profusely use as best of selectors as possible – esp. when using jquery due to its power. It’s more time (in milliseconds) to grab the CORRECT (more accurate) objects, and iterate over a smaller set than to grab the ‘dragnet’ and iterate over 100′s. :)
i too have tested it and know that my older apps are much slower using a ‘dragnet’ style as compared to my newer ones.
thanks for the technical write-up tho, as it confirms as well as validates my brain and I’m sure yours and many others as to the true benefits of using good css selectors in using the power of jquery.
thanks once again!
Thanks for the article and all those discussions/comments. They make me learn lots of stuffs. I guess limiting the search to as narrower domain as possible is the best way to high performance dom computation, and this surely depends on the way I assign ids and css classes.
I praise both $(‘element#myID’) and &(‘element.myClass’). But one question in my mind is: doesn’t the count of number of ‘div’ elements having class ‘myClass’ puts more effort in the search than searching just for id ‘myID’ of element ‘div’?
You’re correct. If the entire ID is known, that is always the best way to select a single element.
The idea behind narrowing by class in this example is to filter down the list before applying the relatively slow “ends with” selector. Selecting by class isn’t lightening fast, but it’s still faster than “ends with”.
thank you wery much
Performing the timing and differencing inside the loop is potentially VERY inaccurate for a number of different reasons, but in short I strongly recommend that you move your timing code outside the loop and perform your benchmarks again.
Yeah, the numbers definitely aren’t absolutely accurate, but they should be an accurate measure of relative difference.
Actually the 15ms PC clock interval results in measurement errors of +/-7.5ms, so the smaller the time difference you’re measuring, the larger the (relative) error, which is why you should time the outside of the loop (so that the measurement error is small in comparison). As I said before, it’s a basic flaw in your experimental technique, hence your stats are likely to be highly inaccurate and unsuitable for drawing conclusions from.
See http://ejohn.org/blog/accuracy-of-javascript-time/ for in-depth coverage of this topic. I’ll leave you with this particularly relevant quote from that article:
When I was still recording every iteration’s duration, there definitely was a <15ms resolution. John is awesome, but I question how accurate that specific Windows testing was, running on XP in a VM.
Even if you believe there is a full 7.5ms margin of error, the improvements were greater than that margin of error.
The main point of John’s article has nothing to do with his particular setup…
But anyway, if you look at your code what you’re doing is incurring a potential measurement error *every* time that you grab the time inside the loop, then dividing the accumulated time (with all the accumulated errors) by the number of iterations.
What I’m suggesting instead is very simple, that you simply need to measure the time once before, once after, then divide by the number of iterations, which incurs only a total possible max error of +/-0.15ms [2x7.5 / 100] instead of +/-7.5ms [100x7.5 / 100] with your method – i.e. a factor of 50x smaller error!
This is NOT controversial stuff, I didn’t invent it, it’s standard experimental methodology (i.e. minimise potential measurement error), and this particular pattern for timing code has been around since before I was born!
Anyway, here’s the modified version of your code:
I understood what you meant in the original comment, and agree completely about that change improving the accuracy of each individual result. What’s more important to me is that the overhead isn’t significant enough to change the eventual conclusion.
I’d definitely be interested to see any actual testing that leads to a different conclusion.
Ok, glad we’re in agreement! :)
However, only you have access to the experimental environment (your PC + test data) so please do run the modified code and compare the results yourself… I can’t do it for you!
Unfortunately (well, not really), I’ve upgraded to a new machine since then.
As many times as I ran those tests while I was writing the post, I’m confident in the conclusion. Over dozens of runs while I was writing the post, gathering data for the spreadsheet, etc the 500 iteration averages barely varied for each test. I wouldn’t have written the post otherwise.
My concern was primarily about poor experimental technique which others would likely copy/follow blindly, and therefore I thought it deserved critiquing – see my second post, displayed below.
That’s a fair point. I updated the methodology section of the post with a link down to your improved version of the testing code. Thanks for taking the time to share that.
PS – I’m not saying that your findings are wrong, just that your timings are likely to be inaccurate, and that your experimental method is flawed.
I’m a little confused. From what I can tell your article is essentially just underlining something that is a) common sense and b) explicitly suggested in the jQuery documentation. Narrowing down the search domain (by using parent containers) to drill down to the lowest common container-element before using a wildcard search will obviously speed the retrieval of matching elements!
If underlining helps a few people understand something better, I’m okay with that.
Your benchmark could be wildly inaccurate if one test nears on the boundary of a millisecond. You shouldn’t accumulate the samples of individual tests and take the average. Measure start time before any cycles, and end time after all, then divide by the number of cycles.
See the previous comments. Being able to accurately say that selector x took n ms isn’t the point here. It’s that selector x was faster or slower than selector y or selector z.