[javascript] How to check if a string "StartsWith" another string?

How would I write the equivalent of C#'s String.StartsWith in JavaScript?

var haystack = 'hello world';
var needle = 'he';

haystack.startsWith(needle) == true

Note: This is an old question, and as pointed out in the comments ECMAScript 2015 (ES6) introduced the .startsWith method. However, at the time of writing this update (2015) browser support is far from complete.

This question is related to javascript string startswith

The answer is


Since this is so popular I think it is worth pointing out that there is an implementation for this method in ECMA 6 and in preparation for that one should use the 'official' polyfill in order to prevent future problems and tears.

Luckily the experts at Mozilla provide us with one:

https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith

if (!String.prototype.startsWith) {
    String.prototype.startsWith = function(searchString, position) {
        position = position || 0;
        return this.indexOf(searchString, position) === position;
    };
}

Please note that this has the advantage of getting gracefully ignored on transition to ECMA 6.


Without a helper function, just using regex's .test method:

/^He/.test('Hello world')

To do this with a dynamic string rather than a hardcoded one (assuming that the string will not contain any regexp control characters):

new RegExp('^' + needle).test(haystack)

You should check out Is there a RegExp.escape function in Javascript? if the possibility exists that regexp control characters appear in the string.


Another alternative with .lastIndexOf:

haystack.lastIndexOf(needle, 0) === 0

This looks backwards through haystack for an occurrence of needle starting from index 0 of haystack. In other words, it only checks if haystack starts with needle.

In principle, this should have performance advantages over some other approaches:

  • It doesn't search the entire haystack.
  • It doesn't create a new temporary string and then immediately discard it.

data.substring(0, input.length) === input

Based on the answers here, this is the version I am now using, as it seems to give the best performance based on JSPerf testing (and is functionally complete as far as I can tell).

if(typeof String.prototype.startsWith != 'function'){
    String.prototype.startsWith = function(str){
        if(str == null) return false;
        var i = str.length;
        if(this.length < i) return false;
        for(--i; (i >= 0) && (this[i] === str[i]); --i) continue;
        return i < 0;
    }
}

This was based on startsWith2 from here: http://jsperf.com/startswith2/6. I added a small tweak for a tiny performance improvement, and have since also added a check for the comparison string being null or undefined, and converted it to add to the String prototype using the technique in CMS's answer.

Note that this implementation doesn't support the "position" parameter which is mentioned in this Mozilla Developer Network page, but that doesn't seem to be part of the ECMAScript proposal anyway.


Here is a minor improvement to CMS's solution:

if(!String.prototype.startsWith){
    String.prototype.startsWith = function (str) {
        return !this.indexOf(str);
    }
}

"Hello World!".startsWith("He"); // true

 var data = "Hello world";
 var input = 'He';
 data.startsWith(input); // true

Checking whether the function already exists in case a future browser implements it in native code or if it is implemented by another library. For example, the Prototype Library implements this function already.

Using ! is slightly faster and more concise than === 0 though not as readable.


The best performant solution is to stop using library calls and just recognize that you're working with two arrays. A hand-rolled implementation is both short and also faster than every other solution I've seen here.

function startsWith2(str, prefix) {
    if (str.length < prefix.length)
        return false;
    for (var i = prefix.length - 1; (i >= 0) && (str[i] === prefix[i]); --i)
        continue;
    return i < 0;
}

For performance comparisons (success and failure), see http://jsperf.com/startswith2/4. (Make sure you check for later versions that may have trumped mine.)


var str = 'hol';
var data = 'hola mundo';
if (data.length >= str.length && data.substring(0, str.length) == str)
    return true;
else
    return false;

The string object has methods like startsWith, endsWith and includes methods.

  • StartsWith checks whether the given string starts at the beginning or not.

  • endsWith checks whether the given string is at the end or not.

  • includes checks whether the given string is present at any part or not.

You can find the complete difference between these three in the bellow youtube video

https://www.youtube.com/watch?v=E-hyeSwg0PA


Also check out underscore.string.js. It comes with a bunch of useful string testing and manipulation methods, including a startsWith method. From the docs:

startsWith _.startsWith(string, starts)

This method checks whether string starts with starts.

_("image.gif").startsWith("image")
=> true

I recently asked myself the same question.
There are multiple possible solutions, here are 3 valid ones:

  • s.indexOf(starter) === 0
  • s.substr(0,starter.length) === starter
  • s.lastIndexOf(starter, 0) === 0 (added after seeing Mark Byers's answer)
  • using a loop:

    function startsWith(s,starter) {
      for (var i = 0,cur_c; i < starter.length; i++) {
        cur_c = starter[i];
        if (s[i] !== starter[i]) {
          return false;
        }
      }
      return true;
    }
    

I haven't come across the last solution which makes uses of a loop.
Surprisingly this solution outperforms the first 3 by a significant margin.
Here is the jsperf test I performed to reach this conclusion: http://jsperf.com/startswith2/2

Peace

ps: ecmascript 6 (harmony) introduces a native startsWith method for strings.
Just think how much time would have been saved if they had thought of including this much needed method in the initial version itself.

Update

As Steve pointed out (the first comment on this answer), the above custom function will throw an error if the given prefix is shorter than the whole string. He has fixed that and added a loop optimization which can be viewed at http://jsperf.com/startswith2/4.

Note that there are 2 loop optimizations which Steve included, the first of the two showed better performance, thus I will post that code below:

function startsWith2(str, prefix) {
  if (str.length < prefix.length)
    return false;
  for (var i = prefix.length - 1; (i >= 0) && (str[i] === prefix[i]); --i)
    continue;
  return i < 0;
}

If you are working with startsWith() and endsWith() then you have to be careful about leading spaces. Here is a complete example:

var str1 = " Your String Value Here.!! "; // Starts & ends with spaces    
if (str1.startsWith("Your")) { }  // returns FALSE due to the leading spaces…
if (str1.endsWith("Here.!!")) { } // returns FALSE due to trailing spaces…

var str2 = str1.trim(); // Removes all spaces (and other white-space) from start and end of `str1`.
if (str2.startsWith("Your")) { }  // returns TRUE
if (str2.endsWith("Here.!!")) { } // returns TRUE

  1. The question is a bit old, but I wanted to write this answer to show you some benchmarks I made based on all the answers provided here and the jsperf shared by Jim Buck.

I basically needed a fast way to find if a long needle is within a long haystack and they are very similar except for the last characters.

Here's the code I have written which for each function (splice, substring, startsWith, etc.) tests both when they return false and true against a haystack string (nestedString) of 1.000.0001 characters and a falsy or truthy needle string of 1.000.000 chars (testParentStringFalse and testParentStringTrue, respectively):

// nestedString is made of 1.000.001 '1' repeated characters.
var nestedString = '...'

// testParentStringFalse is made of 1.000.000 characters,
// all characters are repeated '1', but the last one is '2',
// so for this string the test should return false.
var testParentStringFalse = '...'

// testParentStringTrue is made of 1.000.000 '1' repeated characters,
// so for this string the test should return true.
var testParentStringTrue = '...'

// You can make these very long strings by running the following bash command
// and edit each one as needed in your editor
// (NOTE: on OS X, `pbcopy` copies the string to the clipboard buffer,
//        on Linux, you would probably need to replace it with `xclip`):
// 
//     printf '1%.0s' {1..1000000} | pbcopy
// 

function testString() {
    let dateStart
    let dateEnd
    let avg
    let count = 100000
    const falseResults = []
    const trueResults = []

    /* slice */
    console.log('========> slice')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.slice(0, testParentStringFalse.length) === testParentStringFalse
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'slice',
        avg
    }
    console.log(`testString() slice = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.slice(0, testParentStringTrue.length) === testParentStringTrue
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'slice',
        avg
    }
    console.log(`testString() slice = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== slice')
    console.log('')
    /* slice END */

    /* lastIndexOf */
    console.log('========> lastIndexOf')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.lastIndexOf(testParentStringFalse, 0) === 0
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'lastIndexOf',
        avg
    }
    console.log(`testString() lastIndexOf = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.lastIndexOf(testParentStringTrue, 0) === 0
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'lastIndexOf',
        avg
    }
    console.log(`testString() lastIndexOf = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== lastIndexOf')
    console.log('')
    /* lastIndexOf END */

    /* indexOf */
    console.log('========> indexOf')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.indexOf(testParentStringFalse) === 0
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'indexOf',
        avg
    }
    console.log(`testString() indexOf = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.indexOf(testParentStringTrue) === 0
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'indexOf',
        avg
    }
    console.log(`testString() indexOf = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== indexOf')
    console.log('')
    /* indexOf END */

    /* substring */
    console.log('========> substring')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.substring(0, testParentStringFalse.length) === testParentStringFalse
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'substring',
        avg
    }
    console.log(`testString() substring = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.substring(0, testParentStringTrue.length) === testParentStringTrue
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'substring',
        avg
    }
    console.log(`testString() substring = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== substring')
    console.log('')
    /* substring END */

    /* startsWith */
    console.log('========> startsWith')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.startsWith(testParentStringFalse)
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'startsWith',
        avg
    }
    console.log(`testString() startsWith = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.startsWith(testParentStringTrue)
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'startsWith',
        avg
    }
    console.log(`testString() startsWith = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== startsWith')
    console.log('')
    /* startsWith END */

    falseResults.sort((a, b) => a.avg - b.avg)
    trueResults.sort((a, b) => a.avg - b.avg)

    console.log('false results from fastest to slowest avg:', falseResults)
    console.log('true results from fastest to slowest avg:', trueResults)
}

I runned this benchmark test on Chrome 75, Firefox 67, Safari 12 and Opera 62.

I haven't included Edge and IE because I do not have them on this machine, but if someone of you wants to run the script against Edge and at least IE 9 and share the output here I would be very curious to see the results.

Just remember that you need to recreate the 3 long strings and save the script in a file which you then open in your browser as copy/paste on the browser's console will block it as each string's length is >= 1.000.000).

Here are the outputs:

Chrome 75 (substring wins):

false results from fastest to slowest avg:
1)  {"label":"substring","avg":0.08271}
2)  {"label":"slice","avg":0.08615}
3)  {"label":"lastIndexOf","avg":0.77025}
4)  {"label":"indexOf","avg":1.64375}
5)  {"label":"startsWith","avg":3.5454}

true results from fastest to slowest avg:
1)  {"label":"substring","avg":0.08213}
2)  {"label":"slice","avg":0.08342}
3)  {"label":"lastIndexOf","avg":0.7831}
4)  {"label":"indexOf","avg":0.88988}
5)  {"label":"startsWith","avg":3.55448}

Firefox 67 (indexOf wins):

false results from fastest to slowest avg
1)  {"label":"indexOf","avg":0.1807}
2)  {"label":"startsWith","avg":0.74621}
3)  {"label":"substring","avg":0.74898}
4)  {"label":"slice","avg":0.78584}
5)  {"label":"lastIndexOf","avg":0.79668}

true results from fastest to slowest avg:
1)  {"label":"indexOf","avg":0.09528}
2)  {"label":"substring","avg":0.75468}
3)  {"label":"startsWith","avg":0.76717}
4)  {"label":"slice","avg":0.77222}
5)  {"label":"lastIndexOf","avg":0.80527}

Safari 12 (slice wins for false results, startsWith wins for true results, also Safari is the fastest in terms of total time to to execute the whole test):

false results from fastest to slowest avg:
1) "{\"label\":\"slice\",\"avg\":0.0362}"
2) "{\"label\":\"startsWith\",\"avg\":0.1141}"
3) "{\"label\":\"lastIndexOf\",\"avg\":0.11512}"
4) "{\"label\":\"substring\",\"avg\":0.14751}"
5) "{\"label\":\"indexOf\",\"avg\":0.23109}"

true results from fastest to slowest avg:
1) "{\"label\":\"startsWith\",\"avg\":0.11207}"
2) "{\"label\":\"lastIndexOf\",\"avg\":0.12196}"
3) "{\"label\":\"substring\",\"avg\":0.12495}"
4) "{\"label\":\"indexOf\",\"avg\":0.33667}"
5) "{\"label\":\"slice\",\"avg\":0.49923}"

Opera 62 (substring wins. Results are similar to Chrome and I am not surprised as Opera is based on Chromium and Blink):

false results from fastest to slowest avg:
{"label":"substring","avg":0.09321}
{"label":"slice","avg":0.09463}
{"label":"lastIndexOf","avg":0.95347}
{"label":"indexOf","avg":1.6337}
{"label":"startsWith","avg":3.61454}

true results from fastest to slowest avg:
1)  {"label":"substring","avg":0.08855}
2)  {"label":"slice","avg":0.12227}
3)  {"label":"indexOf","avg":0.79914}
4)  {"label":"lastIndexOf","avg":1.05086}
5)  {"label":"startsWith","avg":3.70808}

It turns out every browser has its own implementation details (apart Opera, which is based on Chrome's Chromium and Blink).

Of course, further test with different use cases could and should be performed (e.g. when needle is really short compared to haystack, when haystack is shorter than needle, etc...), but in my case I needed to compare very long strings and wanted to share it here.


I just learned about this string library:

http://stringjs.com/

Include the js file and then use the S variable like this:

S('hi there').endsWith('hi there')

It can also be used in NodeJS by installing it:

npm install string

Then requiring it as the S variable:

var S = require('string');

The web page also has links to alternate string libraries, if this one doesn't take your fancy.


Best solution:

function startsWith(str, word) {
    return str.lastIndexOf(word, 0) === 0;
}

And here is endsWith if you need that too:

function endsWith(str, word) {
    return str.indexOf(word, str.length - word.length) !== -1;
}

For those that prefer to prototype it into String:

String.prototype.startsWith || (String.prototype.startsWith = function(word) {
    return this.lastIndexOf(word, 0) === 0;
});

String.prototype.endsWith   || (String.prototype.endsWith = function(word) {
    return this.indexOf(word, this.length - word.length) !== -1;
});

Usage:

"abc".startsWith("ab")
true
"c".ensdWith("c") 
true

With method:

startsWith("aaa", "a")
true
startsWith("aaa", "ab")
false
startsWith("abc", "abc")
true
startsWith("abc", "c")
false
startsWith("abc", "a")
true
startsWith("abc", "ba")
false
startsWith("abc", "ab")
true

You can also return all members of an array that start with a string by creating your own prototype / extension to the the array prototype, aka

Array.prototype.mySearch = function (target) {
    if (typeof String.prototype.startsWith != 'function') {
        String.prototype.startsWith = function (str){
        return this.slice(0, str.length) == str;
      };
    }
    var retValues = [];
    for (var i = 0; i < this.length; i++) {
        if (this[i].startsWith(target)) { retValues.push(this[i]); }
    }
    return retValues;
};

And to use it:

var myArray = ['Hello', 'Helium', 'Hideout', 'Hamster'];
var myResult = myArray.mySearch('Hel');
// result -> Hello, Helium

I just wanted to add my opinion about this.

I think we can just use like this:

var haystack = 'hello world';
var needle = 'he';

if (haystack.indexOf(needle) == 0) {
  // Code if string starts with this substring
}

I am not sure for javascript but in typescript i did something like

var str = "something";
(<String>str).startsWith("some");

I guess it should work on js too. I hope it helps!