JavaScript String Manipulation: substring() vs. substr()

Hero image for JavaScript String Manipulation: substring() vs. substr(). Image by Derek Story.
Hero image for 'JavaScript String Manipulation: substring() vs. substr().' Image by Derek Story.

There are quite a few different methods available in JavaScript for string manipulation (I talk briefly about some of the more confusing ones in my article here), with String.prototype.substring() being one of the most commonly used methods for extracting a section of a string. Understanding how to use substring() effectively and being aware of how it differs from the similarly named String.prototype.substr() is crucial for any JavaScript developer.


What is prototype.substring()?

As simply as possible: String.prototype.substring() is a JavaScript method which returns a substring, produced by passing in an original string, and two indices which define where the substring should begin and end.

For example, if you wanted a string made up of the third, fourth, and fifth letters of 'abcdefg', your use would look something like this: ('abcdefg').substring(2, 5);

This is a nondestructive method, which means that your original string remains unchanged.


Syntax of prototype.substring()

The substring() method takes two arguments:

  • indexStart: the zerobased index of the start of the substring.
  • indexEnd (optional): the zerobased index before which to end the extraction. If omitted, substring() extracts characters to the end of the string.
string.substring(indexStart[, indexEnd])

How Does It Work?

The substring() method extracts characters from indexStart up to, but not including, indexEnd. If indexEnd is less than indexStart, substring() will swap the two arguments around (effectively working backwards).


An Example in Code

const str = 'Kavanagh';const sub1 = str.substring(0, 3);console.log(sub1);  //=> 'Kav'

Here, we're using substring() to extract the characters between the first (index 0) and fourth (index 3) of the string 'Kavanagh'. It's worth repeating with this example: indexEnd is the index before which extraction stops. That is to say: the fourth index (or character) in the string is not included.


Special Cases

If either argument is less than 0 or is NaN, it is treated as if it were 0.

Negative indexStart value

So if we set the indexStart to 1 for example, the result would be the same as before:

const str = 'Kavanagh';const sub1 = str.substring(-1, 3);console.log(sub1);  //=> 'Kav'

Negative indexEnd value

However, if indexEnd is 1, then we receive back an empty string, as both indexStart and indexEnd are treated as 0.

const str = 'Kavanagh';const sub1 = str.substring(0, -1);console.log(sub1);  //=> ''

Negative indexStart and indexEnd values

If both indexStart and indexEnd are negative then again they will both be treated as zero and again you'll get back an empty string because there's no character to extract between 0 and 0:

const str = 'Kavanagh';const sub1 = str.substring(-4, -1);console.log(sub1);  //=> ''

When the indexStart is greater than indexEnd

This is where developers can get tripped up when using substring(). When the indexStart is greater than indexEnd, the substring() method swaps the two arguments and then proceeds with the operation.

Here's a code example to illustrate this behaviour:

const str = 'Kavanagh';const sub1 = str.substring(5, 1);console.log(sub1);  //=> 'avan'

Here, because 5 is greater than 1, substring() swaps the values around to behave as though the call was str.substring(1, 5). As a result, it extracts the characters starting at index 1 and ending before index 5, which gives us the output 'avan'.


The Difference Between substring() and substr()

It is fair to say that misuse and misunderstanding between substring() and substr() is one of the most common issues I see in code reviews, particularly with lessexperienced developers.

The substr() method was never formally part of the ECMAScript (ES) specification and is not part of the core JavaScript standard. Although it has been widely supported by browsers as far back (and probably even further than Internet Explorer 6), its future is not guaranteed, and it may be removed in future versions of JavaScript engines or browsers. The MDN Web Docs advise against using substr() and suggest using substring() or slice() instead.

Nevertheless, substring() often gets confused with substr(). Whilst both methods seem to perform a relatively similar function (and have very similar names), there are fundamental differences which make them incompatible with one another.

Parameters

  • substr() takes a start index and a length of characters to extract.
  • substring() takes a start index and an end index.

Negative Indexes

  • substr() supports negative index values, referring to the end of the string.
  • substring() interprets negative values as 0.

In Code

Here's what those differences look like in code:

const str = 'Kavanagh';console.log(str.substring(2, 5));  //=> 'van'console.log(str.substr(2, 5));  //=> 'vanag'console.log(str.substring(-5, 2));  //=> 'Ka'console.log(str.substr(-5, 2));  //=> 'an'

Issues with Confusion

As you can see, although similar, substring() and substr() fundamentally are not the same. This confusion can lead to several issues:

  • Unexpected Results

    : Misinterpreting substr()'s length parameter as an index can result in strings of unexpected length.
  • Negative Indexes

    : Using negative indexes with substring() expecting substr()like behaviour will not yield the intended substring.
  • Deprecation Concerns

    : Because substr() is considered a legacy function, it is not recommended for use in new JavaScript code. It's not part of the core JavaScript standard (ECMAScript) and could (and probably will) be removed from browsers in the future.

Wrapping‑Up

It is a common misunderstanding that substring() and substr() may seem interchangeable. Their differences are significant.

String.prototype.substring() is a reliable and essential method for string operations in JavaScript. Its behaviour is distinct from String.prototype.substr(), which is a legacy method with different parameters and potential for deprecation. Understanding these differences is paramount to avoiding bugs and maintaining clear, futureproof code.

When in doubt, prefer substring() for its consistency with the standard and its predictable, standardised behaviour. When refactoring legacy code, don't simply swap substr() out for substring() without paying close attention to the parameters.


Categories:

  1. Development
  2. Guides
  3. JavaScript