TypeScript Strings
last modified October 15, 2026
A string is a sequence of characters used to represent text.
TypeScript strings are JavaScript strings: they are immutable, every method
returns a new string, and the characters are stored as UTF-16 code units. The
string type annotation ensures that only text is assigned to a
variable and that only string methods are called on it.
Creating Strings
Strings can be written with double quotes, single quotes, or backticks. Backticks create template literals, which can contain embedded expressions and line breaks.
const greeting: string = "Welcome";
const filename: string = 'app.js';
const path: string = `C:/projects/${filename}`;
const quote = "She said \"hello\"";
const multiline = `line one
line two`;
console.log(greeting);
console.log(path);
console.log(quote);
console.log(multiline);
Double and single quotes are equivalent; a project usually picks
one style. Inside quotes, special characters are escaped with a backslash, as
in \". The template literal in path inserts the
value of filename, and multiline keeps the line
break. The type of all variables is string; the annotations can
be left out, because the compiler infers them.
$ npx tsx declaring_strings.ts Welcome C:/projects/app.js She said "hello" line one line two
Length and Characters
The length property returns the number of UTF-16 code
units. Individual characters are read by index.
const word = "TypeScript"; console.log(word.length); console.log(word[0], word[4]); console.log(word.at(-1)); console.log(word[20]);
Indexes start at 0, so word[4] is the fifth
character. The at method also accepts negative indexes, which
count from the end. Reading an index past the end returns
undefined; the compiler types word[20] as
string unless the noUncheckedIndexedAccess option is
enabled.
$ npx tsx string_basics.ts 10 T S t undefined
Strings Are Immutable
A character of a string cannot be changed. Methods that seem to modify a string return a new one.
const word = "cat"; word[0] = "b";
TypeScript reports the assignment, because the index signature of
strings is read-only. In plain JavaScript, the assignment would be silently
ignored. To get "bat", a new string is built, for example with
"b" + word.slice(1).
$ npx tsc --noEmit immutable.ts(3,1): error TS2542: Index signature in type 'String' only permits reading.
String Methods
The String type has many methods for common text
operations.
const message = " TypeScript Essentials ";
const text = message.trim();
console.log(`[${text}]`);
console.log(text.toUpperCase());
console.log(text.toLowerCase());
console.log(text.slice(4, 10));
console.log(text.startsWith("Type"), text.endsWith("ials"));
console.log("7".padStart(3, "0"), "ab".repeat(3));
The trim method removes whitespace from both ends;
trimStart and trimEnd remove it from one side.
slice(4, 10) returns the characters from index 4 up to, but not
including, index 10. startsWith and endsWith test
the beginning and the end. padStart fills a string to a given
length, which is handy for numbers with leading zeros, and
repeat concatenates copies of a string.
None of these methods changes message or
text; each returns a new string.
$ npx tsx string_methods.ts [TypeScript Essentials] TYPESCRIPT ESSENTIALS typescript essentials Script true true 007 ababab
Splitting and Joining
The split method divides a string into an array of
parts; the join array method does the opposite.
const csv = "apple,banana,cherry";
const fruits = csv.split(",");
console.log(fruits, fruits.length);
console.log(fruits.join(" | "));
const words = " the quick brown fox ".trim().split(/\s+/);
console.log(words);
const title = words.map(w => w[0].toUpperCase() + w.slice(1)).join(" ");
console.log(title);
The type of fruits is string[]. The
separator can also be a regular expression: /\s+/ matches one or
more whitespace characters, so multiple spaces between words do not produce
empty strings. The last example capitalizes each word and joins the words
back into a string.
$ npx tsx split_join.ts [ 'apple', 'banana', 'cherry' ] 3 apple | banana | cherry [ 'the', 'quick', 'brown', 'fox' ] The Quick Brown Fox
Searching and Replacing
Strings can be searched with methods that return a position or a boolean, and parts of them can be replaced.
const text = "The cat sat on the mat with another cat.";
console.log(text.includes("cat"));
console.log(text.indexOf("cat"), text.lastIndexOf("cat"));
console.log(text.indexOf("dog"));
console.log(text.replace("cat", "dog"));
console.log(text.replaceAll("cat", "dog"));
console.log(text.replace(/\b(\w)at\b/g, "[$1at]"));
The indexOf method returns the position of the first
match or -1 if there is none. The replace method with a string
pattern replaces only the first occurrence; replaceAll replaces
all of them. With a regular expression and the g flag,
replace also replaces all matches, and $1 refers to
the first captured group. The last line marks every three-letter word ending
in at.
$ npx tsx search_replace.ts true 4 36 -1 The dog sat on the mat with another cat. The dog sat on the mat with another dog. The [cat] [sat] on the [mat] with another [cat].
String Interpolation
Template literals evaluate the expressions inside
${...} and insert their values into the string.
const user = "Alice";
const tasks: number = 5;
const done: number = 3;
console.log(`${user} has ${tasks} pending tasks`);
console.log(`${user} finished ${Math.round((done / tasks) * 100)}% of her work`);
console.log(`Status: ${done === tasks ? "complete" : "in progress"}`);
Any expression can appear inside the placeholders: variables,
arithmetic, function calls, or conditional expressions. Values that are not
strings are converted automatically. Template literals are easier to read
than chains of + operators.
$ npx tsx string_interpolation.ts Alice has 5 pending tasks Alice finished 60% of her work Status: in progress
String Validation
Simple checks can be done with string methods. For patterns, regular expressions are more precise.
function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function isValidUsername(name: string): boolean {
const trimmed = name.trim();
return trimmed.length >= 3 && trimmed.length <= 16 && /^[a-z0-9_]+$/i.test(trimmed);
}
console.log(isValidEmail("user@example.com"));
console.log(isValidEmail("invalid.email"));
console.log(isValidEmail("a@b"));
console.log(isValidUsername("jan_bodnar"));
console.log(isValidUsername("x"));
The regular expression in isValidEmail requires some
characters, an @, a domain, a dot, and an ending, without spaces.
It is a reasonable check for user input, not a full implementation of the
e-mail standard. isValidUsername combines a length check with a
pattern that allows only letters, digits, and underscores; the
i flag makes the match case-insensitive.
$ npx tsx string_validation.ts true false false true false
Iterating Over Strings
Strings are iterable, so the for...of loop visits one
character at a time.
const charset = "ABCD";
for (const char of charset) {
console.log(char);
}
const emoji = "hi 👋";
console.log(emoji.length, [...emoji].length);
The for...of loop and the spread operator iterate over
Unicode code points. This matters for characters outside the basic range,
such as many emoji, which take two UTF-16 code units. The
length of emoji is 5, but it has only 4
characters.
$ npx tsx string_iteration.ts A B C D 5 4
Comparing and Sorting Strings
Strings are compared with === for equality. For
ordering, localeCompare follows the rules of a language.
const a: string = "apple"; const b: string = "Apple"; console.log(a === b); console.log(a.toLowerCase() === b.toLowerCase()); const names = ["Žofia", "zuzana", "Adam", "eva", "Ľubo"]; console.log([...names].sort()); console.log([...names].sort((x, y) => x.localeCompare(y, "sk")));
Equality is case-sensitive, so a case-insensitive comparison
converts both strings first. The variables are annotated as
string: without the annotations, their types would be the
literals "apple" and "Apple", and the compiler would
report the comparison as always false. The default sort orders strings by
their UTF-16 code units: all uppercase letters come before lowercase ones,
and accented letters end up at the end. localeCompare with the
"sk" locale sorts the names as a Slovak reader expects.
$ npx tsx compare_strings.ts false true [ 'Adam', 'eva', 'zuzana', 'Ľubo', 'Žofia' ] [ 'Adam', 'eva', 'Ľubo', 'zuzana', 'Žofia' ]
String Literal Types
TypeScript can restrict a string to specific values with literal types. Template literal types build such types from other strings.
type Direction = "left" | "right" | "up" | "down";
type Size = "small" | "large";
type Color = "red" | "blue";
type Variant = `${Size}-${Color}`;
function move(direction: Direction, steps: number): string {
return `moving ${direction} by ${steps}`;
}
const variant: Variant = "large-red";
console.log(move("left", 3));
console.log(variant);
Calling move("forward", 3) would not compile, because
"forward" is not a Direction. The
Variant type combines the two unions into all four possible
strings: "small-red", "small-blue",
"large-red", and "large-blue".
$ npx tsx literal_types.ts moving left by 3 large-red
Best Practices
- Use Template Literals: Build strings from values with
${...}instead of chains of+. - Remember Immutability: String methods return new strings; assign the result to a variable.
- Trim and Validate Input: Remove surrounding whitespace and check user input with methods or regular expressions.
- Sort with localeCompare: The default sort order is based on code units and is wrong for most languages.
- Mind Unicode:
lengthcounts UTF-16 code units; iterate withfor...ofto work with whole characters. - Use Literal Types: Restrict strings that have a fixed set of valid values.
Source
This tutorial covered strings in TypeScript. String values and
methods come from JavaScript; TypeScript adds the string type,
checks method calls, and offers string literal types for fixed sets of
values.
Author
List all TypeScript tutorials.