---
title: Strings
source: 'https://academia.sh/en/courses/javascript-fundamentals/strings'
course: 'JavaScript Fundamentals'
language: en
updated: '2026-08-17T18:09:46+00:00'
license: 'CC BY-SA 4.0'
---

# Strings

Template literals, slicing and search methods, locale-dependent case conversion, the code-unit/grapheme distinction, and number formatting.

Up to this lesson, output was produced with `console.log` arguments. Writing report
text requires strings' own tools. This lesson defines those tools and finishes a subject
left open in the Primitive Types lesson — the practical consequence of a string being a
sequence of code units.

Strings are immutable; every method here produces a new string and does not change the
original value.

## Template Literals

Strings written with backticks carry three extra abilities: expression interpolation,
being multi-line, and different escaping rules.

```js
const record = { station: "A1", temperature: 21.5 };
const threshold = 20;

const template = `${record.station}: ${record.temperature} C (${record.temperature > threshold ? "above threshold" : "below threshold"})`;
console.log(template);

const multiLine = `station: ${record.station}
temperature: ${record.temperature}`;
console.log(multiLine);
console.log(`quote: "A1", backtick: \`A1\`, dollar: \${record}`);
```

```
A1: 21.5 C (above threshold)
station: A1
temperature: 21.5
quote: "A1", backtick: `A1`, dollar: ${record}
```

What gets interpolated is an **expression**; conditions are written with the ternary
operator from the Conditions and Selection lesson. Every expression is converted to
text — the ToString procedure from the Type Conversion and Coercion lesson applies — so
interpolating an object produces `[object Object]`.

A line break is written directly; no escape sequence is needed. A backslash is used to
escape the backtick and the dollar–brace pair.

In this course, text concatenation is written with template literals. The reason is to
avoid the `+` operator's two-faced behavior seen in the Type Conversion and Coercion
lesson: with a template, the syntax itself makes it clear the result is text.

## Slicing and Splitting

```js
const line = "A1;21.5;48";
console.log(line.length, line[0], line.at(-1));
console.log(line.slice(0, 2), line.slice(-2));
console.log(line.slice(4, 2), line.substring(4, 2));
console.log(line.split(";"));
console.log(line.split(";", 2));
console.log(["A1", "21.5"].join(";"));
```

```
10 A 8
A1 48
 ;2
[ 'A1', '21.5', '48' ]
[ 'A1', '21.5' ]
A1;21.5
```

The third line shows the difference between the two slicing methods. `slice` accepts a
negative index and gives an **empty string** when the start is greater than the end.
`substring` counts negative indices as zero and **swaps** its arguments if needed; this
is why it produced `;2`. The two results being written on the same line, separated by a
space, shows the first is empty. This course uses `slice`: its behavior matches array
`slice` and produces no surprises.

`split` splits by a separator, and a second argument limits the result count. The limit
does not append the remaining text to the last piece — it **discards** it, a common
source of unexpected data loss when parsing a file.

## Search and Replace

```js
const line = "station=A1;field=temperature;field=humidity";
console.log(line.includes("field"), line.indexOf("field"), line.lastIndexOf("field"));
console.log(line.startsWith("station"), line.endsWith("humidity"));
console.log(line.replace("field", "FIELD"));
console.log(line.replaceAll("field", "FIELD"));
console.log("  A1  ".trim() + "|", "  A1  ".trimStart() + "|");
console.log("A1".padStart(5, "0"), "A1".padEnd(5, "."), "-".repeat(10));
```

```
true 11 29
true true
station=A1;FIELD=temperature;field=humidity
station=A1;FIELD=temperature;FIELD=humidity
A1| A1  |
000A1 A1... ----------
```

When called with a text argument, `replace` changes **only the first match**;
`replaceAll` is used for all of them. This is a distinction that produces silent bugs.

Padding methods are the basis of producing aligned output, and a padding character can
be given. Trimming methods drop leading and trailing whitespace; which characters count
as whitespace is defined in the standard.

## Locale-Dependent Case Conversion

Case conversion is language-dependent, and the default methods apply a
language-independent rule:

```js
const name = "station";
console.log(name.toUpperCase());
console.log(name.toLocaleUpperCase("tr"));
console.log("ISTANBUL".toLowerCase());
console.log("ISTANBUL".toLocaleLowerCase("tr"));
```

```
STATION
STATİON
istanbul
ıstanbul
```

The difference shows up in languages where dotted and dotless **i** are separate
letters. The default conversion turns `i` into the dotless capital; the locale-aware
conversion produces the dotted capital.

Two rules follow from this. If text is being converted to show to a user, the locale
should be specified. In contrast, a conversion done **for comparison** should not use
the locale-aware form: the same text gives different results in different locales, and
the comparison becomes inconsistent. Key comparisons are done with the
language-independent form.

## Code Unit and Grapheme

The Primitive Types lesson said a string is a sequence of code units. The consequence
shows up in slicing:

```js
const text = "A1😀";
console.log(text.length);
console.log([...text].length);
console.log([...text]);
console.log(Array.from(text).length);
```

```
4
3
[ 'A', '1', '😀' ]
3
```

`length` gave four because the last character consists of two code units — the
surrogate pair from the How Computers Work course. Spread and `Array.from` walk the
string by **code points** and give three elements.

Cutting at a code-unit boundary corrupts the text:

```js
const text = "A1😀";
console.log(text.slice(0, 3).length);
console.log([...text].slice(0, 3).join(""));
```

```
3
A1😀
```

The first slice took only the first of the double code unit; the result is a code unit
with no meaning on its own, and it looks broken when displayed. The second way did not
split the character, because it operates on the sequence of code points.

A **grapheme** — the unit a user counts as a single character — can be larger than a
code point too; combining marks are an example:

```js
const combined = "é";
const single = "é";
console.log(combined.length, single.length, combined === single);
console.log(combined.normalize("NFC") === single);
```

```
2 1 false
true
```

The two strings look identical but are different sequences of code points; comparison
gives the wrong answer. Normalization brings both to a common form. Code that compares
text should normalize its inputs first.

## Number Formatting

```js
console.log((21.456).toFixed(2));
console.log((0.1 + 0.2).toFixed(2));
console.log((1.005).toFixed(2));
console.log((21.5).toString(2));
console.log(String(1e21), String(1e-7));
```

```
21.46
0.30
1.00
10101.1
1e+21 1e-7
```

`toFixed` rounds to the given digit count and returns **text**. The third line looks
like a surprise, but it is a consequence of the model from the Primitive Types lesson:
the value `1.005` cannot be represented exactly in floating-point, and the stored value
is slightly less than `1.005`, so it rounds down. The rounding error is not in the
formatting, it is in **the number itself**.

`toString` takes a base argument; the representation from the Binary Number System
lesson of the How Computers Work course is produced this way. The last line shows very
large and very small numbers switching to exponential notation; the thresholds are
defined in the standard.

## Writing the Measurement Report

When this lesson's tools combine, an aligned report comes out:

```js
const records = [
  { station: "A1", temperature: 21.5, humidity: 48 },
  { station: "A2", temperature: 19.75, humidity: 55 },
  { station: "B1", temperature: 23, humidity: null },
];

const header = `${"station".padEnd(10)}${"temperature".padStart(12)}${"humidity".padStart(9)}`;
const line = "-".repeat(header.length);

const rows = records.map((r) => {
  const humidityText = r.humidity == null ? "-" : String(r.humidity);
  return `${r.station.padEnd(10)}${r.temperature.toFixed(2).padStart(12)}${humidityText.padStart(9)}`;
});

console.log([header, line, ...rows].join("\n"));
```

```
station    temperature humidity
-------------------------------
A1               21.50       48
A2               19.75       55
B1               23.00        -
```

The missing humidity field was shown with `-`; the `== null` check is the exception
from the Equality Comparisons lesson. `toFixed(2)` brought every temperature to the
same digit count — the value `23` was written as `23.00`. The rows were joined into a
single text with `join` and printed in one call.

Alignment is based on code-unit count. If the report contains characters outside the
Latin set, the padding methods balance code-unit count, not visual width, and the
columns can drift.

## Summary

- Template literals interpolate expressions, are multi-line, and are this course's
  only form of text concatenation.
- `slice` accepts a negative index and gives an empty string on a reversed range;
  `substring` swaps its arguments.
- `replace` changes only the first match; `replaceAll` is needed for all of them.
- Case conversion is locale-dependent; use the locale-aware form for display, the
  language-independent form for comparison.
- `length` counts code units; use spread or `Array.from` for code-point-based
  operations, and normalize before comparing.
- `toFixed` returns text, and rounding is affected by the number's floating-point
  representation.

## Next Step

The report produced readable text, but not a format the machine can read back. The next
lesson takes up JSON: serialization and parsing, values the format does not support,
the error a circular reference produces, and callbacks that adapt the conversion. The
serialization problem left open in the BigInt lesson will also be finished there.
