Strings
TypeScript inherits JavaScript’s string type — an immutable sequence of UTF-16 code units, supporting Unicode through its full code point range. The conventional string forms are single-quoted, double-quoted, and template literals (backtick-delimited, admitting interpolation and multi-line content). TypeScript adds literal string types (typed as a specific string value) and template literal types (computed string-typed unions). The combination — JavaScript’s string operations and the TypeScript-specific string types — covers the routine text-processing surface.
String literals
Three forms:
const a = 'single quotes';
const b = "double quotes";
const c = `template literal`;
// Escape sequences:
const d = "line one\nline two";
const e = 'tab:\there';
const f = "quote: \"inner\"";
const g = "backslash: \\";
// Unicode:
const h = "é"; // é
const i = "\u{1F600}"; // 😀 (since ES2015)
// Template literals:
const name = "Alice";
const greeting = `Hello, ${name}!`;
const expr = `Sum: ${1 + 2}`;
const multiline = `
line 1
line 2
`;
The conventional contemporary style:
- Use single quotes — slightly more conventional than double quotes.
- Use template literals for interpolation and multi-line strings.
- Avoid double quotes in code (preferred for JSON).
Template literals
The backtick form admits embedded expressions:
const name = "Alice";
const age = 30;
const greeting = `Hello, ${name}! You are ${age} years old.`;
// Expression of any complexity:
const formatted = `Pi is approximately ${Math.PI.toFixed(2)}`;
const result = `${a > b ? "first" : "second"} is larger`;
// Nested:
const outer = `outer: ${`inner: ${42}`}`;
// Multi-line (preserves all whitespace):
const sql = `
SELECT id, name
FROM users
WHERE active = true
`;
The mechanism admits substantial conciseness for formatting; conventional for log messages, SQL, HTML templates, and similar contexts.
Tagged templates
A tagged template applies a function to the parts of a template literal:
function highlight(strings: TemplateStringsArray, ...values: unknown[]): string {
let result = "";
for (let i = 0; i < strings.length; i++) {
result += strings[i];
if (i < values.length) {
result += `<mark>${values[i]}</mark>`;
}
}
return result;
}
const html = highlight`Hello, ${name}! You are ${age} years old.`;
// "Hello, <mark>Alice</mark>! You are <mark>30</mark> years old."
The conventional uses are CSS-in-JS libraries, SQL builders, and i18n systems.
String operations
Common string methods:
const s = "hello world";
s.length; // 11
s[0]; // "h" (string of length 1)
s.charAt(0); // "h"
s.charCodeAt(0); // 104
s.codePointAt(0); // 104 (admits surrogate pairs)
s.includes("world"); // true
s.startsWith("hello"); // true
s.endsWith("world"); // true
s.indexOf("world"); // 6
s.lastIndexOf("o"); // 7
s.toUpperCase(); // "HELLO WORLD"
s.toLowerCase(); // "hello world"
s.trim(); // strip whitespace from both ends
s.trimStart();
s.trimEnd();
s.slice(0, 5); // "hello"
s.slice(-5); // "world"
s.substring(0, 5); // "hello" (similar to slice; differs on negative)
s.split(" "); // ["hello", "world"]
s.split(""); // ["h", "e", "l", "l", "o", ...]
s.replace("world", "TS"); // "hello TS"
s.replaceAll("l", "L"); // "heLLo worLd"
s.padStart(15, "*"); // "****hello world"
s.padEnd(15, "*"); // "hello world****"
s.repeat(3); // "hello worldhello worldhello world"
s.normalize("NFC"); // Unicode normalisation
For substring search with regex, the match and replace methods accept patterns:
const matches = "abc 123 def 456".match(/\d+/g); // ["123", "456"]
const replaced = "abc 123".replace(/\d+/g, "X"); // "abc X"
String iteration
A string admits iteration via for ... of (which yields Unicode code points):
const s = "héllo";
for (let i = 0; i < s.length; i++) {
console.log(s.charCodeAt(i)); // UTF-16 code units
}
for (const c of s) {
console.log(c); // Unicode code points (handles surrogate pairs)
}
const chars = [...s]; // ["h", "é", "l", "l", "o"]
const charsArray = Array.from(s); // same
A subtle point: the length property and indexed access work on UTF-16 code units, not code points. Characters outside the Basic Multilingual Plane (e.g., emoji) consist of two UTF-16 code units (a surrogate pair):
const emoji = "😀";
console.log(emoji.length); // 2 (two code units)
console.log([...emoji].length); // 1 (one code point)
The for ... of loop and the spread operator handle code points correctly; raw indexing does not.
String types
TypeScript’s principal string-related types:
string
The general string type:
let s: string = "hello";
Literal string types
let greeting: "hello" = "hello";
type Status = "active" | "inactive" | "banned";
let status: Status = "active";
The conventional uses are enum-like unions and discriminated unions.
Template literal types
Since TS 4.1, template literal types admit compile-time string manipulation:
type Greeting = `Hello, ${string}!`;
type World = `${"morning" | "afternoon" | "evening"}`;
type Joined = `${"a" | "b"}-${"x" | "y"}`; // "a-x" | "a-y" | "b-x" | "b-y"
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
// Common pattern: extract from a templated type:
type ExtractParam<T extends string> =
T extends `${string}{${infer P}}${string}` ? P : never;
type Param = ExtractParam<"GET /users/{id}">; // "id"
The mechanism admits substantial type-level computation; treated in Advanced types.
Built-in string utility types
TypeScript provides string-manipulation utility types:
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"
The utilities work at the type level only; for runtime equivalents, use the string methods.
Conversion to/from string
// Number to string:
const a = String(42); // "42"
const b = (42).toString();
const c = `${42}`; // template literal coercion
const d = (3.14).toFixed(2); // "3.14"
const e = (255).toString(16); // "ff" (hex)
// String to number:
const n = Number("42"); // 42
const f = parseFloat("3.14"); // 3.14
const i = parseInt("42", 10); // 42 (base 10)
const j = +"42"; // 42 (unary plus)
const k = parseInt("abc", 10); // NaN
// Boolean to string:
const truthy = String(true); // "true"
const falsy = String(false); // "false"
// Array to string:
const arr = [1, 2, 3].join(", "); // "1, 2, 3"
const arr2 = String([1, 2, 3]); // "1,2,3"
// Object to string:
const obj = JSON.stringify({ a: 1, b: 2 }); // '{"a":1,"b":2}'
const pretty = JSON.stringify(obj, null, 2); // pretty-printed
The conventional discipline:
- Use
String(x)for explicit conversion. - Use template literals for formatting.
- Use
JSON.stringifyfor object serialisation. - Use
parseIntwith explicit radix —parseInt("0x42")is parsed as hex.
Common patterns
String building
For substantial concatenation, array join is conventionally efficient:
// O(n²) (each += allocates a new string):
let s = "";
for (const item of items) {
s += item.toString() + "\n";
}
// O(n):
const s = items.map(x => x.toString()).join("\n");
// Or with a more imperative style:
const parts: string[] = [];
for (const item of items) {
parts.push(item.toString());
}
const s = parts.join("\n");
Template literal multi-line
const html = `
<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
</head>
<body>
<h1>${heading}</h1>
<p>${content}</p>
</body>
</html>
`;
const sql = `
SELECT id, name, email
FROM users
WHERE active = $1
ORDER BY created_at DESC
`;
String validation
function isEmpty(s: string | null | undefined): boolean {
return s == null || s.trim() === "";
}
function isEmail(s: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
}
function isUUID(s: string): boolean {
return /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(s);
}
Padding for tabular output
const items = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
];
for (const item of items) {
console.log(`${item.name.padEnd(10)} ${String(item.age).padStart(3)}`);
}
Splitting and rejoining
function normalisePath(path: string): string {
return path
.split("/")
.filter(p => p && p !== ".")
.join("/");
}
function camelToSnake(name: string): string {
return name.replace(/[A-Z]/g, (m, i) => (i === 0 ? "" : "_") + m.toLowerCase());
}
Discriminated string unions
type Theme = "light" | "dark" | "auto";
function applyTheme(t: Theme) {
switch (t) {
case "light": /* ... */ break;
case "dark": /* ... */ break;
case "auto": /* ... */ break;
}
}
The pattern admits compile-time discrimination on string values.
URL parsing
const url = new URL("https://example.com/path?q=1&r=2");
console.log(url.host); // "example.com"
console.log(url.pathname); // "/path"
console.log(url.searchParams.get("q")); // "1"
The URL class is conventional for URL manipulation.
Tagged template for SQL
function sql(strings: TemplateStringsArray, ...values: unknown[]): { text: string; values: unknown[] } {
const text = strings.reduce((acc, str, i) => {
return acc + str + (i < values.length ? `$${i + 1}` : "");
}, "");
return { text, values };
}
const query = sql`SELECT * FROM users WHERE id = ${userId} AND active = ${true}`;
// { text: "SELECT * FROM users WHERE id = $1 AND active = $2", values: [userId, true] }
The pattern admits SQL-injection-resistant query construction.
A note on Unicode
JavaScript’s string is UTF-16 — many “characters” are actually two UTF-16 code units. The conventional defences:
- Use
for ... offor code-point iteration. - Use
[...s]orArray.from(s)for code-point arrays. - Use
String.prototype.codePointAtin place ofcharCodeAtwhen handling user text. - Use the
uflag on regex for code-point-aware patterns. - Use
String.prototype.normalizebefore comparing user-input strings.
For grapheme cluster (“user-perceived character”) handling, the Intl.Segmenter (since ES2022) is conventional.
A note on the conventional discipline
The contemporary TypeScript strings advice:
- Use single quotes for string literals; double for JSON.
- Use template literals for interpolation and multi-line strings.
- Use array
.join()for substantial concatenation. - Use
String(x)and template literals for conversion. - Use
parseIntwith radix for integer parsing. - Use
for ... offor code-point-aware iteration. - Use literal string types for enum-like unions.
- Use template literal types for compile-time string-type computation.
- Use the
URLclass for URL parsing.
The combination — JavaScript’s string operations, template literals, the TypeScript-specific string-literal and template-literal types — is the substance of TypeScript’s text surface. The discipline produces clear, Unicode-aware string handling with substantial type-level computation.