The complete guide to
Working With Strings in Modern JavaScript
This guide is intended to cover everything you need to know about creating, manipulating and comparing strings in JavaScript.
and
sections along the way will hopefully teach you something new, and help you to avoid common mistakes.
If I've missed anything, please let me know and I'll add it!
Remember to bookmark this page to refer to later!
Contents
Creating strings
There are fundamentally two categories of strings in JavaScript; string primitives and String objects.
Primitives
String primitives are created like this:
let example1 = "BaseClass"
// or
let example2 = String("BaseClass")
In almost all cases, you should use one of these methods to create a new string.
You can use either single quotes (' '
) or double-quotes (" "
) when defining a string literal.
Objects
You can create a String object using the new
keyword.
The only real advantage that an object has over a string primitive is that you can attach additional properties to it:
let website = new String("BaseClass")
website.rating = "great!"
There are very few cases where this is useful though. In almost all cases, you should create a string primitive.
All of the string methods you are familiar with are part of the String
object, not the primitive.
When you call a method on a string primitive, JavaScript 'auto-boxes', or wraps, the primitive in a String object, and calls the method on that object instead.
Template Literals
Basic Template Literals
Template Literals allow you to combine variables and text into a new string using a more readable syntax.
Instead of double or single-quotes, enclose the string in back-ticks and reference variables using the syntax ${variableName}
:
Before
let opinion = "Love"
let tweet = "I " + opinion + " JavaScript"
// "I Love JavaScript"
After
let opinion = "Love"
let tweet = `I ${opinion} JavaScript`
// "I Love JavaScript"
You can also include expressions in a Template Literal:
let age = 37
let result = `You ${age > 30 ? "do" : "don't"} remember VHS tapes!`
"You do remember VHS tapes!"
Brower support for Template Literals is now very good:
41+
13+
34+
9.1+
29+
You can also nest templates, as shown in this example from MDN:
const classes = `header ${ isLargeScreen() ? ''
: `icon-${item.isCollapsed ? 'expander' : 'collapser'}`
}`;
Tagged Templates
Tagged Templates allow you to create a function that parses a Template Literal.
This can be really powerful, and is most clearly demonstrated by an example:
Imagine we have a function called censor()
that removes any offensive words in a string entered by a user.
When we want to censor a string, we could manually call censor()
on every user entered value:
let response = `Hi ${censor(name)}, you entered ${censor(jobTitle)}.`
Or, we could use Tagged Templates.
This allows us to write a function that accepts the string values from the Template Literal, and all of the expressions used in the template:
let censorStrings = (strings, name, jobTitle) => {
// strings: ["Hi ", ", you entered ", "."]
// name: "Dave"
// jobTitle: "Developer"
}
let name = "Dave"
let jobTitle = "Developer"
let response = censorStrings`Hi ${name}, you entered ${jobTitle}.`
Note that in the last line we 'tag' the string with our function by adding it before the Template Literal, rather than explicitly calling the censorStrings()
function.
This means we can now manipulate the template literal and the values inside it.
The first argument to the tagging function is always an array of strings. The remaining arguments represent each variable/expression uses in the Template Literal.
That means that you won't necessarily know how many arguments to expect in your 'tagging' function.
In those cases, it's useful to put each of the remaining arguments in to an array (using the 'rest parameters' syntax), so that you can iterate through them:
let censorStrings = (strings, ...inputs) => {
// strings: ["Hi ", ", you entered ", "."]
// inputs: ["Dave", "Developer"]
}
Now we have access to the Template Literal and the individual arguments, we can sensor every variable used in the string:
let censorStrings = (strings, ...inputs) => {
// Censor each variable used in the template literal
let censored = inputs.map(i => censor(i))
}
let name = "Dave"
let jobTitle = "Developer"
let response = censorStrings`Hi ${name}, you entered ${jobTitle}.`
Finally, our tagging function has to return the finished string.
To do this, we simply interleave the original string array and the array of (modified) inputs in to a new array.
// (strings) (inputs)
// "Hi " ->
// <- "Dave"
// ", you entered " ->
// <- "Developer"
// "." ->
// "Hi Dave, you entered Developer."
Here we do that using .reduce()
:
return strings.reduce((result, currentString, i) => (
`${result}${currentString}${censored[i] || ''}`
), '')
Our tagging function is now complete, and can be used anywhere we need to censor user inputs:
let censorStrings = (strings, ...inputs) => {
// Censor each variable used in the template literal
let censored = inputs.map(i => censor(i))
// Interleave the string and (censored) inputs
// to create the finished string
return strings.reduce((result, currentString, i) => (
`${result}${currentString}${censored[i] || ''}`
), '')
}
let name = "Dave"
let jobTitle = "Developer"
let response = censorStrings`Hi ${name}, you entered ${jobTitle}.`
// "Hi Dave, you entered Developer."
The tagging function does not need to return a string.
For example, there are libraries for React that take the Template Literal and return a React component.
Raw Strings
String.raw
is a pre-defined tag function.
It allows you to access the string without interpreting any backslash escape sequences.
For example, when using a string containing \n
with String.raw
, instead of getting a new line in the string you would get the actual \
and n
characters:
// NOT using String.raw
`Hello\nWorld`
// Hello
// World
// WITH String.raw
String.raw`Hello\nWorld`
// Hello\nWorld
This makes it useful for (among other things) writing strings in which you'd usually have to escape lots of backslash characters, such as file paths:
// Without string.raw, we have to escape
// the \ folder separators
var path = `C:\\Program Files\\file.json`
// With string.raw, no need for escape sequences
var path = String.raw`C:\Program Files\file.json`
When using string.raw
, the \
character does escape the final back-tick.
That means that you cannot end a raw string with a \
like this:
// The final back-tick (`) would be escaped, meaning this
// string is not terminated correctly
String.raw`c:\Program Files\`
Combining strings
Concatenating strings
You can combine (or 'concatenate') multiple strings to create a new one using the +
symbol:
let name = "Base" + "Class"
// Output: "BaseClass"
This can also be used to split string creation over multiple lines for readability:
let name = "This really is " +
"a very " +
"long string"
// "This really is a very long string"
You can also concatenate strings with variables (non-string variables will be converted to strings):
let precinct = 99
let name = "Brooklyn " + precinct
// Output: "Brooklyn 99"
To create a new string by adding to the end of an existing one, use +=
:
let name = "Three "
name += "Blind "
name += "Mice"
// "Three Blind Mice"
You can also concatenate strings and variables using the string.concat()
method, but that is not recommended for performance reasons.
Instead, use the +
or +=
operators above
Repeating a string
The repeat()
method returns a new string, which contains the original string repeated a number of times:
let warning = "Londons Burning. "
let result = warning.repeat(2)
// "London's Burning. London's Burning. "
You can use this in the following browsers:
41+
12+
24+
9+
28+
Joining strings
You can combine an array of strings into one using the .join()
method on the array.
The default is to separate the items with a comma:
let heros = ["Superman", "Wonder Woman", "Hulk"]
heros.join()
// "Superman,Wonder Woman,Hulk"
You can also specify the string used to separate the elements:
let heroes = ["Superman", "Wonder Woman", "Hulk"]
heroes.join(" or ")
// "Superman or Wonder Woman or Hulk"
Passing an empty string to string.join
will join elements with nothing between them:
let heroes = ["Superman", "Wonder Woman", "Hulk"]
heroes.join("")
// "SupermanWonder WomanHulk"
When toString()
is used on an array, it also returns a comma-separated list of the strings.
let heroes = ["Superman", "Wonder Woman", "Hulk"]
heroes.toString()
// Output: Superman,Wonder Woman,Hulk"
Splitting a string
You can split a string in to an array using the split()
method.
Common use cases for this are:
Turning a sentence into an array of words, by splitting it on spaces:
"Welcome to Paradise".split(" ")
// [ "Welcome", "to", "Paradise" ]
.. or splitting a multi-line string into individual lines:
let song = `Hello,
Is it me you're looking for?`
song.split("\n")
// [ "Hello,", "Is it me you're looking for?" ]
You can also limit the number of items you would like back from split()
, by passing an optional second parameter:
// Get only the first two words
"The quick brown fox".split(" ", 2)
// Output: [ "The", "quick" ]
If you need to turn a string in to an array of characters, the split()
method does not work well for Unicode characters that are represented by 'surrogate pairs':
"👋!".split("") // ["�", "�", "!"]
In modern browsers, you can use the spread operator instead:
[..."👋!"] // ["👋", "!"]
Comparing strings
Equality
When you know you are comparing two string primitives, you can use either the ==
or ===
operators:
"abba" === "abba" // true
"abba" == "abba" // true
If you are comparing a string primitive to something that is not a string, ==
and ===
behave differently.
When using the ==
operator, the non-string will be coerced to a string. That means that JavaScript will try and make
it a string before comparing the values.
9 == "9"
// becomes
"9" == "9" //true
For a strict comparison, where non-strings are not coerced to strings, use ===
:
9 === "9" // false
The same is true of the not-equal operators, !=
and !==
:
9 != "9" // false
9 !== "9" // true
If you're not sure what to use, prefer strict equality using ===
.
When using String
objects, two objects with the same value are not considered equal:
new String("js") == new String("js") // false
new String("js") === new String("js") // false
Case sensitivity
When a case-insensitive comparison is required, it is common to convert both strings to upper or lowercase and compare the result.
"Hello".toUpperCase() === "HeLLo".toUpperCase() // true
Sometimes you need more control over the comparison, though. See the next section for that..
Handling diacritics
Diacritics are modifications to a letter, such as é or ž.
You may wish to specify how these are handled when comparing two strings.
For example, in some languages it is common practice to exclude accents from letters when writing in uppercase.
If you need a case-insensitive comparison, simply converting two strings to the same case first using toUpperCase()
or toLowerCase()
would
not account for this addition/removal of accents, and might not give you the result you are expecting.
When you need more fine-grained control of the comparison, use localeCompare instead:
let a = 'Résumé';
let b = 'RESUME';
// Returns 'false' - the strings do not match
a.toLowerCase() === b.toLowerCase()
// Returns '0' - the strings match
a.localeCompare(b, undefined, { sensitivity: 'base' })
The localeCompare
method allows you to specify the 'sensitivity' of the comparison.
Here we used a 'sensitivity' of base
to compare the strings using their 'base' characters (meaning it will ignore case and accents).
This is supported in the following browsers:
24+
12+
29+
10+
15+
Greater/less than
When comparing strings using the <
and >
operators, JavaScript will compare each character in 'lexicographical order'.
That means that they are compared letter by letter, in the order they would appear in a dictionary:
"aardvark" < "animal" // true
"gamma" > "zulu" // false
When comparing strings using <
or >
, lowercase letters are considered larger than uppercase.
"aardvark" > "Animal" // true
This is because JavaScript is actually using each character's value in Unicode, where lowercase letters are after uppercase letters.
True or false strings
Empty strings in JavaScript are considered false when compared with the ==
operator
(but not when using ===
)
("" == false) // true
("" === false) // false
Strings with a value are 'true', so you can do things like this:
if (someString) {
// string has a value
} else {
// string is empty or undefined
}
Sorting strings
Simple Array.sort()
The simplest way to sort an array of strings is to use the Array.sort()
method:
["zebra", "aligator", "mouse"].sort()
// [ "aligator", "mouse", "zebra" ]
When sorting an array of strings, they are compared using each character's 'UTF-16 code unit' value
In Unicode, uppercase letters are before lowercase letters.
That means that strings that start with a capital letter will always end up before strings starting with lowercase letters:
["zebra", "aligator", "Mouse"].sort()
// [ "Mouse", "aligator", "zebra" ]
You can work around this by converting all of the strings to the same case first, or by using localeCompare
(see below), which is usually more efficient
localeCompare
Using localeCompare
as the sorting function allows you to compare strings in a case-insensitive way:
let animals = ["zebra", "aligator", "Mouse"]
animals.sort((a, b) => a.localeCompare(b));
/// [ "aligator", "Mouse", "zebra" ]
You can also use localeCompare
to ignore diacritics (like accents) while sorting strings. See the Handling Diacritics section for more
information.
Multi-line strings
You can add new lines in a string using \n
:
let result = "The winner is:\nBaseClass!"
// The winner is:
// BaseClass!
In a Template Literal, new lines are respected within the backticks:
let result = `The winner is:
BaseClass!`
// The winner is:
// BaseClass!
In Template Literals, you can escape line-breaks by adding a \
at the end of the line.
let result = `All on \
one line`
// "All on one line"
Padding strings
You can add whitespace to the start or end of a string until it reaches a specified length using padStart()
or padEnd()
:
// Pad the string "hello" with whitespace to make
// it 8 characters long:
"hello".padStart(8) // " hello"
"hello".padEnd(8) // "hello "
Instead of whitespace, you can pad the target string with another string, by passing it as the second parameter.
That string will be repeated until the target length is reached (the string will be truncated if it does not fit evenly into the required padding):
// Pad "Train" to 13 characters using the string "Choo"
"Train".padStart(13, "Choo")
// Output: "ChooChooTrain"
This is supported by all modern browsers:
string.padEnd()
57+
15+
48+
10+
44+
Extracting part of a String
Substrings
These methods take the index of the first character you wish to extract from the string.
They return everything from that character to the end of the string:
"I am Groot!".slice(5) // "Groot!"
"I am Groot!".substring(5) // Groot!
The second (optional) argument is the character at which you'd like to stop.
This final character is not included in the output:
// Extract a new string from the 5th character,
// up to (but not including!) the 10th character
"I am Groot!".slice(5, 10) // "Groot"
"I am Groot!".substring(5, 10) // "Groot"
So, which one should you use?
They are very similar, but with some subtle differences:
- If the end value is higher than the start value,
substring()
will 'correct' them by swapping them, butslice()
will just return an empty string. substring()
treats a negative index as0
. Withslice()
, you can use a negative number to count backwards from the end of the string. For example,.slice(-3)
will return the last 3 characters of the string.
There is also a substr()
method, that is similar to slice()
and substring()
.
This is a legacy API. While it is unlikely to be used soon, you should use one of the two methods above instead where possible.
Single characters
The charAt()
method returns a specific character from the string (remember, indexes are 0-based):
"Hello".charAt(1) // "e"
You can also treat a string as an array, and access it directly like this:
"Hello" [1] // e
Accessing a string as an array could lead to confusion when the string is stored in a variable.
Using charAt() is more explicit:
// What is 'someVariable'?
let result = someVariable[1]
// 'someVariable' is clearly a string
let result = someVariable.charAt(1)
Changing the case of a string
You can make a string all-caps like this:
"hello".toUpperCase() // "HELLO
Or all lowercase like this:
"Hello".toLowerCase() // "hello
These methods are commonly used to convert two strings to upper/lowercase in order to perform a case-insensitive comparison of them.
Depending on the strings you are comparing, you might want more control over the comparison. Consider using localeCompare
instead.
See this section.
Removing whitespace
The following methods will remove all spaces, tabs, non-breaking spaces and line ending characters (e.g. \n
) from relevant part the string:
" Trim Me ".trim() // "Trim Me"
" Trim Me ".trimStart() // "Trim Me "
" Trim Me ".trimEnd() // " Trim Me"
"With Newline\n".trimEnd() // "With NewLine"
trimStart()
and trimEnd()
were introduced in ES10, and are now the 'preferred' methods to use according to that specification.
At the time of writing, however, these are not supported in the Edge browser.
For compatibility in all modern browsers, use trimLeft()
and trimRight()
:
" Trim Me ".trimLeft() // "Trim Me "
" Trim Me ".trimRight() // " Trim Me"
Searching for text in a string
Find the position of a substring
You can search for a string within another string using indexOf()
.
This will return the position of first occurrence of the search term within the string, or -1
if the string is not found:
"Superman".indexOf("man") // '5'
"Superman".indexOf("foo") // '-1'
You can also use the regular expression method search()
to do the same thing:
"Superman".search("man") // '5'
To search for the last occurrence of a search term, use lastIndexOf()
:
"Yabba Dabba Doo".indexOf("bb") // '2'
"Yabba Dabba Doo".lastIndexOf("bb") // '8'
All of these methods will return -1
if the search term is not found in the target string.
Starts/Ends with
You can use the indexOf()
methods above to check whether a string begins with, or ends with, a search term.
However, ES6 added specific methods for this:
"Wonder Woman".startsWith("Wonder") // true
"Wonder Woman".endsWith("Woman") // true
string.endsWith()
41+
12+
17+
9+
28+
Includes
If you don't care about the specific position of the substring, and only care whether it is in the target string at all, you can use includes()
:
"War Games".includes("Game") // true
41+
12+
40+
9+
28+
Regular Expressions
This section is an overview.
I now have a full length course on Regular Expressions here.
To find the first occurrence of a regular expression match, use .search()
.
// Find the position first lowercase character
"Ironman 3".search(/[a-z]/) // '1'
To return an array containing all matches of a regular expression, use match()
, with the /g
(global) modifier:
// Return all uppercase letters
"This is England".match(/[A-Z]/g)
// Output: [ "T", "E" ]
(using match()
without the /g
modifier will return only the first match, and some additional properties such as
the index of the result in the original string, and any named capturing groups)
If you need more details about each match, including their index in the original string, you can use matchAll
.
This method returns an iterator, so you can use a for...of
loop over the results. You must use a regular expression
with the /g/
modifier with matchAll()
:
// Find all uppercase letters in the input
let input = "This is England"
let matches = input.matchAll(/[A-Z]/g)
// Turn the result into an array, using
// the spread operator
[...matches]
// Returns:
// [
// ["T", index: 0, groups: undefined]
// ["E", index: 8, groups: undefined]
// ]
Replacing characters in a string
You can use replace()
to replace certain text in a string.
The first argument to replace()
is the text to find and replace, the second is the text to replace it with.
Passing a string as the first argument replaces only the first instance of the term:
"no no no!".replace("no", "yes")
// "yes no no!"
If you want to replace all instances of the text, you can pass a regular expression with the 'greedy' modifier (/g
) as the first
argument:
"no no no!".replace(/no/g, "yes")
// "yes yes yes!"
For more information on Regular Expressions, check out the free course.
ES2021 added replaceAll()
, to make replacing all instances of a term easier:
"no no no!".replaceAll("no", "yes")
// "yes yes yes!"
You can use this if you only need to support very recent browsers:
85+
85+
77+
13.1+
71+
Do you want more like this?
I'll let you know when I'm working on new lessons and courses, and you'll get early access to anything I do.