Faster JavaScript Trim

Since JavaScript doesn't include a trim method natively, it's included by countless JavaScript libraries – usually as a global function or appended to String.prototype. However, I've never seen an implementation which performs as well as it could, probably because most programmers don't deeply understand or care about regex efficiency issues.

After seeing a particularly bad trim implementation, I decided to do a little research towards finding the most efficient approach. Before getting into the analysis, here are the results:

Method Firefox 2 IE 6
trim1 15ms < 0.5ms
trim2 31ms < 0.5ms
trim3 46ms 31ms
trim4 47ms 46ms
trim5 156ms 1656ms
trim6 172ms 2406ms
trim7 172ms 1640ms
trim8 281ms < 0.5ms
trim9 125ms 78ms
trim10 < 0.5ms < 0.5ms
trim11 < 0.5ms < 0.5ms

Note 1: The comparison is based on trimming the Magna Carta (over 27,600 characters) with a bit of leading and trailing whitespace 20 times on my personal system. However, the data you're trimming can have a major impact on performance, which is detailed below.

Note 2: trim4 and trim6 are the most commonly found in JavaScript libraries today.

Note 3: The aforementioned bad implementation is not included in the comparison, but is shown later.

The analysis

Although there are 11 rows in the table above, they are only the most notable (for various reasons) of about 20 versions I wrote and benchmarked against various types of strings. The following analysis is based on testing in Firefox 2.0.0.4, although I have noted where there are major differences in IE6.

  1. return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    All things considered, this is probably the best all-around approach. Its speed advantage is most notable with long strings — when efficiency matters. The speed is largely due to a number of optimizations internal to JavaScript regex interpreters which the two discrete regexes here trigger. Specifically, the pre-check of required character and start of string anchor optimizations, possibly among others.
  2. return str.replace(/^\s+/, '').replace(/\s+$/, '');
    Very similar to trim1 (above), but a little slower since it doesn't trigger all of the same optimizations.
  3. return str.substring(Math.max(str.search(/\S/), 0), str.search(/\S\s*$/) + 1);
    This is often faster than the following methods, but slower than the above two. Its speed comes from its use of simple, character-index lookups.
  4. return str.replace(/^\s+|\s+$/g, '');
    This commonly thought up approach is easily the most frequently used in JavaScript libraries today. It is generally the fastest implementation of the bunch only when working with short strings which don't include leading or trailing whitespace. This minor advantage is due in part to the initial-character discrimination optimization it triggers. While this is a relatively decent performer, it's slower than the three methods above when working with longer strings, because the top-level alternation prevents a number of optimizations which could otherwise kick in.
  5. str = str.match(/\S+(?:\s+\S+)*/);
    return str ? str[0] : '';

    This is generally the fastest method when working with empty or whitespace-only strings, due to the pre-check of required character optimization it triggers. Note: In IE6, this can be quite slow when working with longer strings.
  6. return str.replace(/^\s*(\S*(\s+\S+)*)\s*$/, '$1');
    This is a relatively common approach, popularized in part by some leading JavaScripters. It's similar in approach (but inferior) to trim8. There's no good reason to use this in JavaScript, especially since it can be very slow in IE6.
  7. return str.replace(/^\s*(\S*(?:\s+\S+)*)\s*$/, '$1');
    The same as trim6, but a bit faster due to the use of a non-capturing group (which doesn't work in IE 5.0 and lower). Again, this can be slow in IE6.
  8. return str.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1');
    This uses a simple, single-pass, greedy approach. In IE6, this is crazy fast! The performance difference indicates that IE has superior optimization for quantification of "any character" tokens.
  9. return str.replace(/^\s*([\S\s]*?)\s*$/, '$1');
    This is generally the fastest with very short strings which contain both non-space characters and edge whitespace. This minor advantage is due to the simple, single-pass, lazy approach it uses. Like trim8, this is significantly faster in IE6 than Firefox 2.

Since I've seen the following additional implementation in one library, I'll include it here as a warning:

return str.replace(/^\s*([\S\s]*)\b\s*$/, '$1');

Although the above is sometimes the fastest method when working with short strings which contain both non-space characters and edge whitespace, it performs very poorly with long strings which contain numerous word boundaries, and it's terrible (!) with long strings comprised of nothing but whitespace, since that triggers an exponentially increasing amount of backtracking. Do not use.

A different endgame

There are two methods in the table at the top of this post which haven't been covered yet. For those, I've used a non-regex and hybrid approach.

After comparing and analyzing all of the above, I wondered how an implementation which used no regular expressions would perform. Here's what I tried:

function trim10 (str) {
	var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
	for (var i = 0; i < str.length; i++) {
		if (whitespace.indexOf(str.charAt(i)) === -1) {
			str = str.substring(i);
			break;
		}
	}
	for (i = str.length - 1; i >= 0; i--) {
		if (whitespace.indexOf(str.charAt(i)) === -1) {
			str = str.substring(0, i + 1);
			break;
		}
	}
	return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
}

How does that perform? Well, with long strings which do not contain excessive leading or trailing whitespace, it blows away the competition (except against trim1/2/8 in IE, which are already insanely fast there).

Does that mean regular expressions are slow in Firefox? No, not at all. The issue here is that although regexes are very well suited for trimming leading whitespace, apart from the .NET library (which offers a somewhat-mysterious "backwards matching" mode), they don't really provide a method to jump to the end of a string without even considering previous characters. However, the non-regex-reliant trim10 function does just that, with the second loop working backwards from the end of the string until it finds a non-whitespace character.

Knowing that, what if we created a hybrid implementation which combined a regex's universal efficiency at trimming leading whitespace with the alternative method's speed at removing trailing characters?

function trim11 (str) {
	str = str.replace(/^\s+/, '');
	for (var i = str.length - 1; i >= 0; i--) {
		if (/\S/.test(str.charAt(i))) {
			str = str.substring(0, i + 1);
			break;
		}
	}
	return str;
}

Although the above is a bit slower than trim10 with some strings, it uses significantly less code and is still lightning fast. Plus, with strings which contain a lot of leading whitespace (which includes strings comprised of nothing but whitespace), it's much faster than trim10.

In conclusion…

Since the differences between the implementations cross-browser and when used with different data are both complex and nuanced (none of them are faster than all the others with any data you can throw at it), here are my general recommendations for a trim method:

  • Use trim1 if you want a general-purpose implementation which is fast cross-browser.
  • Use trim11 if you want to handle long strings exceptionally fast in all browsers.

To test all of the above implementations for yourself, try my very rudimentary benchmarking page. Background processing can cause the results to be severely skewed, so run the test a number of times (regardless of how many iterations you specify) and only consider the fastest results (since averaging the cost of background interference is not very enlightening).

As a final note, although some people like to cache regular expressions (e.g. using global variables) so they can be used repeatedly without recompilation, IMO this does not make much sense for a trim method. All of the above regexes are so simple that they typically take no more than a nanosecond to compile. Additionally, some browsers automatically cache the most recently used regexes, so a typical loop which uses trim and doesn't contain a bunch of other regexes might not encounter recompilation anyway.


Edit (2008-02-04): Shortly after posting this I realized trim10/11 could be better written. Several people have also posted improved versions in the comments. Here's what I use now, which takes the trim11-style hybrid approach:

function trim12 (str) {
	var	str = str.replace(/^\s\s*/, ''),
		ws = /\s/,
		i = str.length;
	while (ws.test(str.charAt(--i)));
	return str.slice(0, i + 1);
}

New library: Are you a JavaScript regex master, or want to be? Then you need my fancy XRegExp library. It adds new regex syntax (including named capture and Unicode properties); s, x, and n flags; powerful regex utils; and it fixes pesky browser inconsistencies. Check it out!

187 thoughts on “Faster JavaScript Trim”

  1. I rewrote trim12 somewhat…
    I don’t know if it’s as fast as the original, but can’t see why it would’nt be.

    String.prototype.trim = function(){
        return this.triml().trimr();
    };

    String.prototype.triml = function(){
        return this.replace(/^\s\s*/, ”);
    };

    String.prototype.trimr = function(){
        var ws = /\s/,
            i = this.length;
        while(ws.test(this.charAt(–i)));
        return this.slice(0, i + 1);
    };

  2. Hi,

    Firefox 3.6 complained about the original trim12() and the trim12() of #comment-13585, because of “str” is redeclared.
    In #comment-13585 also a “)” is missing.

    The working version are:
    function trim12 (str) {
    var str1 = str.replace(/^\s\s*/, ”),
    ws = /\s/,
    i = str1.length;
    while (ws.test(str1.charAt(–i)));
    return str1.slice(0, i + 1);
    }

    and

    function trim12(str) {
    var str1 = str.replace(/^\s\s*/, ”);
    var len = str1.length;
    if (len && /\s/.test(str1.charAt(len-1))) {
    var re = /.*\S/g;
    re.test(str1);
    str1 = str1.slice(0, re.lastIndex);
    }
    return str1;
    }

  3. function(string){
    var start=0, end=string.length, whitespace’=’ \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000′
    while(whitespace.indexOf(string.charAt(start))>-1 && start<end)start++
    while(whitespace.indexOf(string.charAt(end))>-1 && start<end)end–
    return string.slice(start,end+1)
    }

  4. function trim10(string){
    var start=0, end=string.length, whitespace=’ \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000′
    while(whitespace.indexOf(string.charAt(start))>-1 && start<end)start++
    while(whitespace.indexOf(string.charAt(end))>-1 && start<end)end–
    return string.slice(start,end+1)
    }

  5. Your benchmark 1000 times in Chrome 5.0.375.99
    Original length: 27661
    trim1: 506ms (length: 27654)
    trim2: 498ms (length: 27654)
    trim3: 733ms (length: 27654)
    trim4: 1ms (length: 27654)
    trim5: 1ms (length: 27654)
    trim6: 1ms (length: 27654)
    trim7: 1ms (length: 27654)
    trim8: 1ms (length: 27654)
    trim9: 1ms (length: 27654)
    trim10: 96ms (length: 27654)
    trim11: 88ms (length: 27654)
    trim12: 90ms (length: 27654)

  6. Seems that different browsers use *very* different optimization algorithms. I deliberately tested on my slowest computer because slow computers need the optimization most. Run 1000 times.

    IE8 latest:

    Original length: 27730
    trim1: 616ms (length: 27722)
    trim2: 515ms (length: 27722)
    trim3: 1867ms (length: 27722)
    trim4: 2710ms (length: 27722)
    trim5: 1125ms (length: 27722)
    trim6: 1758ms (length: 27722)
    trim7: 1157ms (length: 27722)
    trim8: 235ms (length: 27722)
    trim9: 3805ms (length: 27722)
    trim10: 54ms (length: 27722)
    trim11: 70ms (length: 27722)
    trim12: 63ms (length: 27722)

    Opera 10.60:

    Original length: 27730
    trim1: 480ms (length: 27722)
    trim2: 448ms (length: 27722)
    trim3: 1410ms (length: 27722)
    trim4: 421ms (length: 27722)
    trim5: 558ms (length: 27722)
    trim6: 755ms (length: 27722)
    trim7: 558ms (length: 27722)
    trim8: 40ms (length: 27722)
    trim9: 2834ms (length: 27722)
    trim10: 4ms (length: 27722)
    trim11: 4ms (length: 27722)
    trim12: 3ms (length: 27722)

    Looking at the results one comment above, the best cross browser algorithms still seem to be 10 to 12, despite the fact that the Chrome engine seems to be much better with with the pure regexp.

  7. Pingback: | Webdesgin
  8. this is absolutely amazing… I’ve seen this post some time ago and I was hoping to find it once more, as I wanted to use this trim code again… and here it is! thanks, great job!

  9. Pingback: Quora
  10. How do I trim just empty whitepace, so when just empty space( ) is given as a parameter, the return should be empty like (trimmed==””)? The above just trims the whitespace around texts and not removes the content altogether if the parameter is purely whitespace?

  11. hi at all
    I need to fully trim all spaces after single quote if after there is the letter “s” to perform possessive case
    Exemple:
    Mary’ s ball or Mary’ s ball or Mary’ s ball or Mary’ s ball must be simply Mary’s ball without space into possessive case
    Thank you

  12. function trim( str )
    {
    var l = str.length;
    var a = 0;
    var z = l – 1;

    while ( str.charAt( a ) <= ' ' && a < l )
    ++ a;
    while ( str.charAt( z ) <= ' ' && a < z )
    — z;
    return str.substring( a, z + 1 );
    }

  13. Leuke site!. Er zijn nog weinig goede sites over dit onderwerp te vinden.
    Ben blij met jullie post!
    Ik kan helaas geen bookmark aanmaken naar blog.stevenlevithan.com.sharedcopy.com in Firefox. 🙁 Weten jullie hoe dit komt?

    Groetjes Barbara

  14. Massimo, very similar to my solution, but yours has one minor issue:
    If length of string is 0, z will be -1 and you will do str.charAt(-1). This is not good coding practice IMHO.

    Here’s my solution that I’ve used for the past 10 years (which is very similar):

    function trim (s) {
    var whitespace = ” \t\r\n\f”;
    if (s !== null) {
    var front = 0;
    var back = s.length;
    while ((front !== back) && (whitespace.indexOf (s.charAt (front)) !== -1)) {
    front++;
    }
    while ((front !== back) && (whitespace.indexOf (s.charAt (back – 1)) !== -1)) {
    back–;
    }
    s = s.substring (front, back);
    }
    return (s);
    }

  15. Minimalist, similar in concept to trim10 as well as Peter’s and Massimo’s code.

    function trim(s){
    var whitespace=’ \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000′,l=s.length,i,j;
    for(i=0;i<l&&(whitespace.indexOf(s.charAt(i++))>-1);){}
    for(j=l;j>i-2&&(whitespace.indexOf(s.charAt(–j))>-1);){}
    return s.substring(i-1,j+1)
    }

    Seems to have a 15-18% speed gain over trim10 on Safari 4.1.3 Mac, but no noticeable (about 5%) speed gain on Firefox 3.6.15.

  16. Steve – I imagine you’re sick of this thread by now, but I would be interested to hear in more detail about why the expression you highlighted as exceptionally bad is so terrible.

    str.replace(/^\s*([\S\s]*)\b\s*$/, ‘$1’);

    You said that it performs badly on strings containing lots of word boundaries, and terribly on strings comprising nothing but whitespace. I’m struggling to see how either of these can be right, so perhaps you can enlighten me!

    It seems to me that a string with lots of word boundaries will match [\S\s]* up to the very end of the string, and then some backtracking will be required to find the last word boundary, but the number of word boundaries prior to that doesn’t seem to be relevant to me. RegexBuddy seems to confirm this – the number of backtracks is equal to the number of trailing whitespace characters, and isn’t affected by the number of word boundaries.

    More curiously, surely for strings composed entirely of whitespace, this expression wouldn’t match at all, since the string doesn’t contain any word boundaries? That seems to me to be the primary reason why this trim is no good.

  17. You may look here http://jsperf.com/mega-trim-test I put there all versions from this site, and also few own optimizations. trim18 looks the best there. Be aware that some trim implementations on this site, are buggy, they returns bad values in all or some browsers, in some special cases.

  18. This function is similar to trim12, except it does not use regx for leading spaces. Tested in Firefox 7.0.1 with Firebug 1.8.3 on desktop with 4G RAM 64 bit Win7 OS on 2.50 GHz Dual-core CPU, etc…

    What I did to test it:

    Went to your benchmark page > opened Firebug > right-clicked on the containing script tag > selected “Edit HTML” > replaced trim1’s code with the following function’s code block.

    …..function trimN() {
    ……….var f = 0, t = str.length – 1;
    ……….while (str.charAt(f) === ‘ ‘) { ++f; }
    ……….while (str.charAt(t) === ‘ ‘) { –t; }
    ……….return str.slice(f, t);
    …..}

    Result: 0ms

  19. Results @ 5000 times (note trim1 uses the code from my previous post):

    This is only in Firefox, however.

    Original length: 27661
    trim1: 2ms (length: 27657)
    trim2: 1706ms (length: 27654)
    trim3: 1809ms (length: 27654)
    trim4: 1322ms (length: 27654)
    trim5: 724ms (length: 27654)
    trim6: 9155ms (length: 27654)
    trim7: 7107ms (length: 27654)
    trim8: 4084ms (length: 27654)
    trim9: 2436ms (length: 27654)
    trim10: 12ms (length: 27654)
    trim11: 491ms (length: 27654)
    trim12: 431ms (length: 27654)

    Thanks for the great post.

  20. Thanks for the inspiring article!

    After much testing in the latest browsers (FF 7, IE 9, Safari 5, Chrome 15, and Opera 11.5) and with many strings, this is the implementation I’ve come up with. It is often faster than trim12, and is MUCH faster when there are more than a few spaces at the end of the string.

    var trimN;
    if(window.navigator.mozIsLocallyAvailable) //Firefox
    //Firefox is faster this way. So is Safari but I can’t reliably detect it.
    trimN = function (str) {
    str = str.match(/\S+(?:\s+\S+)*/);
    return str ? str[0] : “”;
    }
    else //other browsers
    trimN = function (str) {
    str = str.replace(/^\s+/, “”);
    if(!str) return “”;
    str = str.match(/^[\s\S]*\S(?=\s*$)/);
    return str ? str[0] : “”;
    }

  21. Although tests are performed with old browsers, your article really helps. I never thought different regular expressions make such big differences. Thank you for your work. (I wish there is an update with current browsers.)

  22. For trim10 the whitespace var needs to include \x00 (the null character)

    var whitespace = ‘ \x00\n\r\t\f\x0b\xa0\…etc

    I was using base64 encode/decode and the results returned the x00 character, which weren’t getting trimmed.

    For what it’s worth, with \x00 trim10 is the only one that actually trims the null character.

    Here is a simple test you can do (assuming you’ve got trim12() already in the JS file):

    function trimtest(){
    var bob = trim12(“abc” + “\x00” + “\x00”);
    var retval = “”;
    for (var i = 0; i < bob.length; i++) {
    retval += bob[i] + " : " + bob.charCodeAt(i) + "\n";
    }
    alert(retval);
    }

  23. @mike, character 0x00 is not whitespace, and should not be removed by trim. ES5’s native trim/trimRight/trimLeft methods also do not remove the null character.

  24. Using Node JS (chrome browser’s v8 engine)

    The best trim function is trim 11.

    Regular expression 4 took about the same time for both the magna carta, and a whitespace-only string, and took O(n) time.

    Trim 11 took the same time as the regular expression when set with the worst case scenario of handling whitespace-only strings. When set with the magna carta, trim 11 was shockingly faster.

    Units are in ms/1000tests. Lower is better.

    Long Whitespace Only
    [Trim Regexp, Trim 11]
    [ 114, 116 ], // 25,000 characters
    [ 69, 68 ], // 15,000 characters
    [ 46, 45 ],
    [ 23, 23 ],
    [ 11, 12 ],
    [ 5, 4 ],
    [ 3, 2 ],
    [ 1, 2 ],
    [ 0, 1 ],
    [ 0, 0 ],
    [ 1, 0 ],
    [ 0, 0 ] ] // 5 characters
    Short Whitespace Only

    Long Magna Carta
    [Trim Regexp, Trim 11]
    [ 187, 8 ],// 25,000 characters
    [ 113, 5 ],// 15,000 characters
    [ 75, 3 ],
    [ 38, 1 ],
    [ 19, 1 ],
    [ 7, 1 ],
    [ 4, 1 ],
    [ 2, 0 ],
    [ 1, 0 ],
    [ 1, 0 ],
    [ 1, 0 ],
    [ 1, 0 ], // 5 characters
    Short Magna Carta

  25. NodeJs users:

    Trim 11 and trim 12 are transformed into functionally identical code in V8. No performance advantage is gained by using one over the other. I use the following variation, as it is the most clear way to represent the code.

    function trim (str) {
    // Trim left
    var str = str.replace(/^\s\s*/, ”);

    // Trim right
    for (i= str.length; i–;)
    {
    if (/\S/.test(str[i]))
    {
    return str.substring(0, i + 1);
    }
    }
    return str
    }

  26. Im not sure, but why are you guys always checking an array of possible whitespace chars? Isnt that very slow? If think the fastest approach is to check the unicode values. you can do it with 3 checks.

    1. Smaller than 33 -> space
    2. Smaller than 256 -> non-space
    3. If bigger than smallest space character and smaller than the biggest-> Check other UnicodeRanges (but in range checks)

    Depending on the expeceted data set (especially the expected space chars), the order could be different.

  27. Pingback: js???????? | ?????

Leave a Reply

Your email address will not be published. Required fields are marked *