JavaScript’s string model

JavaScript strings are immutable sequences of UTF-16 code units. That is the right starting point for reasoning about indexing, emoji, and the meaning of length.

Learning objective

Explain immutability and UTF-16 indexing, distinguish code units from code points, and choose the correct iteration model.

What to understand

A variable can be reassigned to a new string, but the existing string is not changed. Methods such as slice and replace return values; calling them without retaining the result leaves your variable unchanged. Prefer const when the variable itself will not be reassigned.

length and bracket indexing count UTF-16 code units. A supplementary character such as 🙂 occupies two units. The string iterator, used by for…of and spread, combines a well-formed surrogate pair into one code-point string. Combining accents remain separate points, so code-point operations are not automatically operations on visible characters.

Contracts must specify their coordinate system. A browser selection API or string slice may expect UTF-16 offsets, while a learning problem may explicitly request code-point positions. Converting to an array of points changes indices. For the Unicode exercises here, inputs are well-formed unless a problem states otherwise.

Worked example

const text = "A🙂B";
console.assert(text.length === 4);
console.assert([...text].length === 3);
console.assert(text.slice(1, 3) === "🙂");
console.assert([...text].reverse().join("") === "B🙂A");
const decomposed = "e\u0301";
console.assert(decomposed.length === 2);
console.assert([...decomposed].length === 2);
const original = "hello";
const changed = original.replace("h", "H");
console.assert(original === "hello" && changed === "Hello");

The same string has four code units but three code points. The slice boundaries 1 and 3 enclose the emoji’s pair. Spread-based reversal keeps that pair together, yet still reverses a base letter and combining mark independently. Assertions express the promised semantics as executable checks.

Common mistakes

split("") splits into code units and can break emoji when reversed. Conversely, spreading the string and returning those array indices is wrong when a caller requested UTF-16 offsets.

Do not describe a code-point preview as grapheme-safe. Emoji joined with zero-width joiners and combining sequences can still be divided. Also avoid new String(...): ordinary string primitives are the appropriate values for these exercises.

Guided practice

Predict the length and spread-array length of "🙂🙂". Then write a preview that keeps one code point and appends … only when characters were omitted. What happens for limit=0 and empty input?

Check your reasoning

The lengths are 4 units and 2 points. Convert to points, compare their count with the limit, and join a prefix only when truncating. A nonempty input with limit=0 becomes …; empty input remains empty because nothing was omitted.

Recap

Choose units deliberately. Use indexed access for UTF-16 offsets, iteration for code points, and a separate grapheme policy when visible-character behavior is required.

Continue with the linked problems below. Write down the input contract, a small counterexample, and your expected complexity before opening a hint. Run the starter after making one deliberate change, and explain any failing case before changing the code again.