String.startsWith(), endsWith(), and includes() in JavaScript

Hero image for String.startsWith(), endsWith(), and includes() in JavaScript. Image by Steve Johnson.
Hero image for 'String.startsWith(), endsWith(), and includes() in JavaScript.' Image by Steve Johnson.

There is a particular kind of JavaScript code that works perfectly well and still feels older than it needs to be.

String checks built around indexOf() are a good example.

For years, we wrote things like:

if (url.indexOf('/admin') === 0) {  // ...}

or:

if (filename.indexOf('.jpg') !== -1) {  // ...}

That is not wrong, but it forces you to translate lowlevel numeric conditions back into the string question you were actually asking.

ES6 gave us three methods that make this much clearer:

  • startsWith()
  • endsWith()
  • includes()

startsWith() says exactly what it means

If we want to know whether a string begins with a particular substring, the method is wonderfully direct:

const path = '/admin/settings';const isAdminRoute = path.startsWith('/admin');

That reads almost like English.

Compared with:

const isAdminRoute = path.indexOf('/admin') === 0;

the intent is much easier to spot immediately.


endsWith() is just as straightforward

This is useful for file extensions, route fragments, suffixbased naming, and plenty more:

const filename = 'photo.jpg';const isJpeg = filename.endsWith('.jpg');

Again, the code says what it is doing without the reader needing to remember how to interpret a number from indexOf().


includes() answers the general contains question

When the question is simply "does this string contain that substring anywhere?", includes() is the cleanest option:

const message = 'Your order has been dispatched';const mentionsDispatch = message.includes('dispatch');

That replaces the old pattern:

const mentionsDispatch = message.indexOf('dispatch') !== -1;

It is a small improvement in syntax, but quite a big improvement in readability.


Why Readability Matters Here

These checks appear everywhere:

  • URL handling
  • form validation
  • search filtering
  • feature toggles
  • filetype checks
  • little content rules in UI code

Because they are so common, making them easier to scan pays off quickly.

Good APIs are not only about capability. They are also about reducing the amount of mental decoding the reader has to do.


These Methods are Case‑Sensitive

This is one of the first practical caveats.

const name = 'JavaScript';console.log(name.includes('script'));

That returns false because 'script' and 'Script' are not the same sequence of characters.

If we want caseinsensitive matching, we need to normalise:

const containsScript = name.toLowerCase().includes('script');

That is not a flaw in the methods. It is just how exact string matching works.


startsWith() and includes() are not regular expressions

Another small but important limitation: these methods work with strings, not regex patterns.

If we need pattern matching, optional groups, or more advanced rules, regular expressions are still the right tool.

That said, developers often reach for a regex when all they really need is:

  • a simple prefix check
  • a simple suffix check
  • a straightforward substring test

In those cases, the dedicated methods are usually the cleaner choice.


The Optional Position Arguments are Worth Knowing

startsWith() can start checking from a later position:

const value = '---error';console.log(value.startsWith('error', 3));

That returns true.

includes() also accepts a starting position:

const text = 'banana';console.log(text.includes('na', 3));

That starts searching at index 3.

endsWith() has its own slightly different optional length parameter, which lets you treat the string as though it ended earlier:

const filename = 'report.txt.backup';console.log(filename.endsWith('.txt', 10));

That checks the string as though it were only ten characters long.

You will not use those extra parameters every day, but they are handy when you need them.


A Few Realistic Examples

Route handling:

const isArticleRoute = window.location.pathname.startsWith('/articles/');

Basic search filtering:

const matchesSearch = title.toLowerCase().includes(query.toLowerCase());

File extension check:

const isSvg = filename.endsWith('.svg');

These are all ordinary frontend tasks, and each one reads more clearly with the dedicated string methods than with numeric index comparisons.


Cleaner Syntax Also Reduces Bugs

Code like:

if (filename.indexOf('.jpg')) {  // ...}

is a classic source of mistakes because indexOf() returning 0 is falsy, while returning 1 is truthy enough to confuse people who are not thinking carefully.

The newer methods sidestep that entire class of bug because they return booleans directly.

That alone makes them safer for beginners and easier to review in larger codebases.


They are Not Only Nicer, They are More Honest

indexOf() answers the question "where is this substring?"

Sometimes that is what we genuinely need.

But quite often the real question is one of these:

  • does it start with this?
  • does it end with this?
  • does it contain this?

When the API matches the question directly, the code becomes easier to trust.


The Right Method is Usually Obvious Once You Name the Question Properly

If the logic feels slightly awkward, that is often a clue that the method does not match the intention.

If you want a boolean answer about containment or position at the edges of a string, startsWith(), endsWith(), and includes() are usually the right tools. Save indexOf() for the moments when the actual numeric position matters.

That is the real win here. ES6 did not introduce some brandnew capability. It gave us a better vocabulary for something we already did constantly.

And better vocabulary nearly always leads to better code.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript