Skip to content
Fundamentals

What Is Text Encoding? From Telegraph Codes to Unicode and UTF-8

Computers store only numbers, so every piece of text is a sequence of numbers under some agreed code. Here is what a text encoding is, how we got from telegraph codes and ASCII to Unicode, how UTF-8 and UTF-16 work byte by byte, and why text still goes wrong.

A computer stores nothing but numbers. Every letter you read on a screen, every name in a database and every line of source code is held as a sequence of numbers, and something has to agree on which number means which character. That agreement is a text encoding. When it works, nobody notices. When two programs disagree, “café” turns into “café”, a customer’s name is rejected as invalid, or an emoji crashes a database insert. This article explains what a text encoding is, how the world got from telegraph codes to Unicode, how the modern encodings work bit by bit, and where text still goes wrong. The examples were run with Python 3.10 and Node.js 12, and their output is shown as printed.

What a text encoding is

A text encoding is a rule for turning characters into bytes and back. It has two halves: a list of the characters it can represent, each with a number, and a method for storing those numbers as bytes.

The Unicode Standard separates those halves more carefully, into layers that are worth keeping apart even when they are casually called by one name:1

The layers between a character and the bytes that store it, using the euro sign. The abstract character EURO SIGN is assigned the code point U+20AC in Unicode. An encoding form turns the code point into code units: three 8-bit units E2 82 AC in UTF-8, or one 16-bit unit 20AC in UTF-16. An encoding scheme fixes the byte order: UTF-16 big-endian stores 20 AC, little-endian AC 20. Finally a font draws a glyph for the character.Abstract characterwhat the writer meansEURO SIGNCode pointthe number Unicode assigns itU+20ACEncoding formcode units of a fixed sizeUTF-8: E2 82 AC (three 8-bit units)UTF-16: 20AC (one 16-bit unit)Encoding schemebytes, in a fixed orderUTF-8: E2 82 ACUTF-16BE: 20 AC UTF-16LE: AC 20decode to code points; a font draws a glyphGlyphthe shape a font draws€
From character to bytes to picture, using the euro sign. Most text bugs come from confusing two of these layers.
LayerWhat it isExample for €
Abstract characterA unit of meaning in writing: a letter, digit, punctuation mark or symbolEURO SIGN
Coded character setA mapping from characters to numbers, called code pointsU+20AC
Character encoding formA way to represent each code point as one or more code units of a fixed sizeUTF-8: E2 82 AC; UTF-16: 20AC
Character encoding schemeThe exact byte sequence, including byte orderUTF-16BE: 20 AC; UTF-16LE: AC 20
GlyphThe visual shape a font draws; not part of the encoding at all€ in a particular typeface

Two consequences follow. First, a character is not a byte: in most encodings in use today, one character can take several bytes. Second, a character is not a glyph: the same code point looks different in every font, and some glyphs on screen are built from several code points.

A history of text encoding

Telegraph codes and punched cards

Text was encoded as signals long before computers. Morse code used variable lengths of dots and dashes, but machines needed fixed-length codes. In the 1870s Émile Baudot designed a five-unit telegraph code, and a modified version by Donald Murray became the International Telegraph Alphabet No. 2 (1931), the standard for teleprinters. With five bits there are only 32 combinations, so the code used two “shift” characters to switch between letters and figures.2 Herman Hollerith’s punched cards used their own codes for digits and letters, and each computer maker devised character codes for its own machines.

ASCII

By the early 1960s, incompatible codes were a serious obstacle to exchanging data. The American Standards Association published the American Standard Code for Information Interchange (ASCII) in 1963; a 1967 revision, issued after the association had been renamed the USA Standards Institute, added the lowercase letters.23 ASCII is a 7-bit code with 128 positions: 33 control codes (such as line feed and carriage return) and 95 printable characters, the space included.4 In 1969 RFC 20 proposed it as the format for text exchanged between ARPANET computers.4

ASCII covered English and nothing else. IBM’s EBCDIC, introduced with the System/360 in 1964, was an incompatible 8-bit code that survives on IBM mainframes.35

Code pages: one byte, many meanings

Computers stored characters in 8-bit bytes, so ASCII left 128 values free, and every country and vendor filled them differently. The ISO/IEC 8859 series defined one standard per region; ISO 8859-1 (Latin-1), first published in 1987, covered Western European languages.6 Microsoft’s Windows-1252 put 27 more characters, including €, in the range Latin-1 leaves for control codes (0x80 to 0x9F),7 and DOS and Windows had dozens of other code pages.8 Chinese, Japanese and Korean needed thousands of characters, so they used multi-byte encodings such as Shift_JIS, GB 2312 and Big5.

The same byte sequence therefore meant different things on different machines, and a file carried no record of which code page it used. Text that crossed a border came out garbled, a result common enough that it is widely known by a Japanese name, mojibake (文字化け): text garbled by the wrong encoding.9

Unicode

In late 1987 Joe Becker and Lee Collins of Xerox and Mark Davis of Apple began working on a single character set for all the world’s writing systems. Becker coined the name, and his 1988 paper Unicode 88 set out its principles.101112 The Unicode Consortium was incorporated in January 1991, and Unicode 1.0 appeared in two volumes in 1991–92. Unicode 1.1 (1993) was synchronised with the parallel international standard ISO/IEC 10646-1, and the two have assigned the same characters to the same code points ever since.1013

The original design assumed that 16 bits, 65,536 characters, would be enough. It was not. Unicode 2.0 (1996) extended its code space to more than a million code points, U+0000 to U+10FFFF, and introduced surrogate pairs so that 16-bit systems could still represent all of them; that became UTF-16. (ISO/IEC 10646 had defined a larger 31-bit space; both standards now stop at U+10FFFF.)141516 Unicode 18.0, released in 2026, defines 172,808 characters.17

UTF-8

Unicode needed an encoding that would not break the enormous amount of software built around ASCII bytes. In September 1992 Ken Thompson, working with Rob Pike at Bell Labs, designed UTF-8 over dinner in a New Jersey diner, sketching it on a placemat, and within days their Plan 9 operating system was running it.1819 They presented it at the Winter 1993 USENIX conference in San Diego.20 UTF-8 was standardised by the IETF as RFC 2279 (1998), and RFC 3629 (2003) restricted it to the Unicode range, so that no character takes more than four bytes.19

UTF-8 won. In 2008 Google reported that since December 2007 Unicode, in practice UTF-8, had been the most common encoding on the web pages it indexed,21 and today W3Techs finds UTF-8 on 99.1 percent of the websites whose encoding it can determine.22 The WHATWG Encoding Standard, which governs web browsers, requires new formats and protocols to use UTF-8 exclusively.8

A timeline at a glance

YearMilestone
1870sBaudot’s five-unit telegraph code
1963ASCII published; revised with lowercase in 1967
1964EBCDIC with the IBM System/360
1969ASCII proposed for ARPANET text (RFC 20)
1987ISO 8859-1 (Latin-1); Unicode work begins
1988Becker’s Unicode 88 proposal
1991Unicode Consortium incorporated; Unicode 1.0
1992UTF-8 designed by Thompson, with Pike
1993Unicode 1.1 synchronised with ISO/IEC 10646; UTF-8 presented at USENIX
1996Unicode 2.0: code space to U+10FFFF, surrogates, UTF-16
1998UTF-8 as an Internet standard (RFC 2279)
2003RFC 3629 limits UTF-8 to U+10FFFF (four bytes)
2008Google: Unicode (UTF-8) now the web’s most common encoding
2026Unicode 18.0: 172,808 characters

How Unicode works

Code points and planes

Unicode assigns every character a code point, written U+ followed by at least four hexadecimal digits: A is U+0041, é is U+00E9, € is U+20AC and 😀 is U+1F600. The code space runs from U+0000 to U+10FFFF, 1,114,112 code points, divided into 17 planes of 65,536 each.16

The Unicode code space. It is divided into 17 planes of 65,536 code points each, from U+0000 to U+10FFFF. Plane 0, the Basic Multilingual Plane, holds the characters of almost all modern scripts. Plane 1, the Supplementary Multilingual Plane, holds historic scripts, symbols and most emoji. Planes 2 and 3 hold additional CJK ideographs. Planes 4 to 13 are unassigned, plane 14 holds special-purpose characters, and planes 15 and 16 are for private use. Within the Basic Multilingual Plane, the range D800 to DFFF is reserved for UTF-16 surrogates and E000 to F8FF is a private use area.17 planes × 65,536 code points = 1,114,112 code points, U+0000 to U+10FFFF0123planes 4–13: unassigned1415160: Basic Multilingual Plane1: historic scripts, symbols, emoji2–3: more CJK ideographs (and Seal script)14: special purpose15–16: private usePlane 0 in detail, U+0000 to U+FFFFmost modern scripts, symbols and CJK0000FFFFD800–DFFF: surrogates, reserved for UTF-16E000–F8FF: private use
The Unicode code space. Almost every character in everyday use is in plane 0; most emoji are in plane 1.

Plane 0, the Basic Multilingual Plane (BMP), contains the characters of almost all modern writing systems. Its range U+D800 to U+DFFF is permanently reserved for UTF-16 surrogates and never assigned to characters. Most emoji and historic scripts live in plane 1, additional CJK ideographs in planes 2 and 3, and planes 15 and 16 are for private use.16

UTF-32 and UTF-16

The simplest encoding form, UTF-32, stores every code point as one 32-bit number. It is easy to index but wastes space: plain English text becomes four times larger than in ASCII.

UTF-16 stores each BMP code point as one 16-bit code unit. Code points above U+FFFF are split across a surrogate pair: subtract 0x10000, leaving a 20-bit number, then put its high 10 bits into a high surrogate (U+D800 to U+DBFF) and its low 10 bits into a low surrogate (U+DC00 to U+DFFF).15 For U+1F600, 0x1F600 − 0x10000 = 0xF600, whose high ten bits are 0x3D and low ten bits 0x200, giving the pair D83D DE00.

UTF-16 is the native string format of Windows, Java and JavaScript, all designed when Unicode still fitted in 16 bits.232425 It is why a JavaScript string’s length counts UTF-16 code units, not characters:

js
const smile = "\u{1F600}";
console.log(smile.length);          // UTF-16 code units
console.log([...smile].length);     // code points
console.log(smile.charCodeAt(0).toString(16), smile.charCodeAt(1).toString(16));
txt
2
1
d83d de00

Because a 16-bit unit can be stored with either byte first, UTF-16 has two byte orders, big-endian (UTF-16BE) and little-endian (UTF-16LE). A file may start with a byte order mark (BOM), U+FEFF, to say which: the bytes FE FF mean big-endian, FF FE little-endian.15

UTF-8

UTF-8 encodes each code point as one to four bytes. The number of leading 1 bits in the first byte says how many bytes the character takes, and every following byte starts with the bits 10:19

The four UTF-8 byte patterns. Code points U+0000 to U+007F use one byte, 0xxxxxxx, carrying 7 bits. U+0080 to U+07FF use two bytes, 110xxxxx 10xxxxxx, carrying 11 bits. U+0800 to U+FFFF use three bytes, 1110xxxx 10xxxxxx 10xxxxxx, carrying 16 bits. U+10000 to U+10FFFF use four bytes, 11110xxx followed by three 10xxxxxx bytes, carrying 21 bits. The x positions hold the bits of the code point.Code pointsByte 1Byte 2Byte 3Byte 4BitsU+0000–U+007F0xxxxxxx7U+0080–U+07FF110xxxxx10xxxxxx11U+0800–U+FFFF1110xxxx10xxxxxx10xxxxxx16U+10000–U+10FFFF11110xxx10xxxxxx10xxxxxx10xxxxxx21
The four UTF-8 byte patterns. Accented bits are fixed markers; the x positions carry the code point’s bits.

Encoding the euro sign shows the method. U+20AC needs 16 bits, so it takes the three-byte form; its bits are split 4 + 6 + 6 and dropped into the x positions:

Encoding the euro sign, U+20AC, in UTF-8. In binary the code point is 0010 000010 101100. Because it is between U+0800 and U+FFFF it takes three bytes. The first 4 bits go into 1110xxxx, giving 11100010, hex E2. The next 6 bits go into 10xxxxxx, giving 10000010, hex 82. The last 6 bits go into 10xxxxxx, giving 10101100, hex AC.U+20AC in binary, 16 bits, split 4 + 6 + 60010000010101100Three bytes111000101000001010101100In hexE282ACmarker bitssay which byteis which
Encoding U+20AC in UTF-8, step by step.

Python can show the same characters in all three encoding forms:

python
for ch in ["A", "é", "€", "😀"]:
    print(f"{ch}  U+{ord(ch):04X}  "
          f"UTF-8: {ch.encode('utf-8').hex(' ').upper():12}  "
          f"UTF-16BE: {ch.encode('utf-16-be').hex(' ').upper():12}  "
          f"UTF-32BE: {ch.encode('utf-32-be').hex(' ').upper()}")
txt
A  U+0041  UTF-8: 41            UTF-16BE: 00 41         UTF-32BE: 00 00 00 41
é  U+00E9  UTF-8: C3 A9         UTF-16BE: 00 E9         UTF-32BE: 00 00 00 E9
€  U+20AC  UTF-8: E2 82 AC      UTF-16BE: 20 AC         UTF-32BE: 00 00 20 AC
😀  U+1F600  UTF-8: F0 9F 98 80   UTF-16BE: D8 3D DE 00   UTF-32BE: 00 01 F6 00

The design has properties that explain why it took over:2019

  • ASCII compatibility. Every ASCII file is already valid UTF-8, byte for byte, and ASCII characters never appear inside a multi-byte sequence. Software that only looks for ASCII characters such as /, < or newline works unchanged.
  • No zero bytes except for U+0000 itself, so C strings, which end at a zero byte, keep working.
  • Self-synchronisation. Lead bytes and continuation bytes look different, so a program can start reading anywhere and find the next character boundary within three bytes.
  • Sorting is preserved. Sorting UTF-8 strings byte by byte gives the same order as sorting them by code point.
  • No byte order. UTF-8 is a sequence of bytes, so it needs no BOM, though some Windows tools still write the bytes EF BB BF at the start of a file.

The cost is variable length: finding the 1,000th character means scanning from the start, and non-Latin scripts take two to four bytes per character.

Characters, code points and what users see

Grapheme clusters

What a reader thinks of as one character can be several code points. Unicode calls the user-perceived character a grapheme cluster, and defines the rules for finding their boundaries.26

What a reader sees as one character can be several code points. The letter é can be stored as the single code point U+00E9, or as the letter e, U+0065, followed by the combining acute accent, U+0301; normalisation converts between the two forms. The family emoji is five code points: MAN, ZERO WIDTH JOINER, WOMAN, ZERO WIDTH JOINER and GIRL, which software that supports the sequence draws as one picture.One letter, two encodingsU+00E9é, precomposedNFC ↔ NFDU+0065e+U+0301combining acuteboth displayéOne picture, five code pointsU+1F468MANU+200DjoinerU+1F469WOMANU+200DjoinerU+1F467GIRLone grapheme cluster18 bytes in UTF-8,8 units in UTF-16
Two cases where one visible character is several code points.
  • Combining marks. é can be stored as one precomposed code point, U+00E9, or as e followed by the combining acute accent, U+0301. Both display the same.
  • Emoji sequences. Many emoji are built from several code points joined by the ZERO WIDTH JOINER, U+200D, or combined with skin-tone modifiers. A flag is a pair of regional indicator letters: 🇧🇷 is the indicators for B and R.27

So “how long is this string?” has several correct answers:

python
import unicodedata

for label, text in [
    ("composed é", "\u00e9"),
    ("decomposed é", "e\u0301"),
    ("family emoji", "\U0001F468\u200D\U0001F469\u200D\U0001F467"),
    ("flag of Brazil", "\U0001F1E7\U0001F1F7"),
]:
    print(f"{label:15} code points: {len(text)}  "
          f"UTF-16 units: {len(text.encode('utf-16-le')) // 2}  "
          f"UTF-8 bytes: {len(text.encode('utf-8'))}")

composed, decomposed = "\u00e9", "e\u0301"
print(composed == decomposed)                                  # False
print(unicodedata.normalize("NFC", decomposed) == composed)    # True
print([unicodedata.name(c) for c in decomposed])
txt
composed é      code points: 1  UTF-16 units: 1  UTF-8 bytes: 2
decomposed é    code points: 2  UTF-16 units: 2  UTF-8 bytes: 3
family emoji    code points: 5  UTF-16 units: 8  UTF-8 bytes: 18
flag of Brazil  code points: 2  UTF-16 units: 4  UTF-8 bytes: 8
False
True
['LATIN SMALL LETTER E', 'COMBINING ACUTE ACCENT']

Each of these is one grapheme cluster on screen. Python’s len counts code points, JavaScript’s counts UTF-16 units, and a database column limit may count bytes; none of them counts what the user sees.

Normalisation

Because the same text can be encoded in different ways, comparing strings code point by code point can give the wrong answer, as the False above shows. Normalisation converts text into a standard form first. Unicode defines four normalisation forms:28

FormWhat it doesTypical use
NFCComposes characters where possible (e + U+0301 → é)Storing and exchanging text; the form W3C recommends for web content29
NFDDecomposes characters into base plus combining marksText processing; Apple’s HFS+ file system stores names fully decomposed30
NFKCLike NFC, and also replaces compatibility characters (fi → fi, ² → 2)Identifiers, search
NFKDLike NFD, with the same compatibility replacementsSearch, sorting keys

Normalise text before comparing it, hashing it or using it as a key, or two visually identical usernames can be different accounts.

Case and sorting

Changing case and sorting are also language-dependent, and do not map one character to one character:3132

python
print("straße".upper())              # one character becomes two
print("İ".lower(), len("İ".lower())) # Turkish capital dotted I
print("fi".upper())                   # a ligature splits in two
txt
STRASSE
i̇ 2
FI

German ß uppercases to SS; the Turkish capital dotted İ lowercases (in the default, language-neutral rules) to i plus a combining dot; and the fi ligature becomes two letters. Alphabetical order differs between languages too: Swedish sorts ä after z, German sorts it with a. The Unicode Collation Algorithm defines a default order that each locale can tailor.32

When text goes wrong

Mojibake

Mojibake happens when bytes are decoded with a different encoding from the one that wrote them. Since a single-byte code page such as Windows-1252 maps almost every byte to a character (and browsers, following the WHATWG Encoding Standard, map all 256), the decoder usually raises no error; it simply produces the wrong characters:8

How mojibake happens. The word café is saved in UTF-8 as five bytes: 63, 61, 66, C3 and A9; the é takes two bytes. Decoded as UTF-8, the bytes give back café. Decoded as Windows-1252, which maps each of these bytes to one character, C3 becomes à and A9 becomes ©, giving café."café" saved as UTF-8636166C3A9read as UTF-8cafécorrect636166read asWindows-1252C3A9cafémojibake
UTF-8 bytes decoded as Windows-1252. Each byte of the two-byte é becomes a character of its own.
python
data = "café".encode("utf-8")
print(data)                          # the bytes on disk
print(data.decode("utf-8"))          # read correctly
print(data.decode("cp1252"))         # read as Windows-1252: mojibake
txt
b'caf\xc3\xa9'
café
café

The pattern à followed by another character is the classic sign of UTF-8 read as Latin-1 or Windows-1252. The cure is never to guess: record the encoding with the data, as HTTP does in Content-Type: text/html; charset=utf-8 and HTML in <meta charset="utf-8">,33 or simply use UTF-8 everywhere.

Limits that were never meant for every character

Systems designed before supplementary planes were common sometimes cannot store them. MySQL’s original utf8 character set, now called utf8mb3, allows at most three bytes per character, so it cannot hold emoji or other characters beyond the BMP; the character set that holds all of Unicode is utf8mb4.34 Code that slices UTF-16 strings by index can cut a surrogate pair in half, and code that slices UTF-8 bytes can cut a character in half, leaving invalid data.

Security

Text encoding is also an attack surface:

  • Overlong and invalid sequences. Early decoders accepted non-shortest encodings, such as the two bytes C0 AF for /, which let attackers sneak characters past filters that looked only for the one-byte form.35 RFC 3629 makes such sequences invalid and requires decoders to protect against decoding them.19
  • Look-alike characters. Latin a (U+0061) and Cyrillic а (U+0430) look identical, so a domain name can impersonate another. Unicode’s security mechanisms define how to detect such confusables.36
  • Bidirectional controls. Invisible characters that change the display direction of text can make source code look different to a reviewer than it does to the compiler, an attack published as Trojan Source in 2021.3738

In programming languages

Languages represent strings in different ways, which explains much of the confusion:

LanguageWhat a string islen / length counts
Python 3A sequence of code points, stored internally in 1, 2 or 4 bytes each depending on the widest character39Code points
JavaScript, Java, C#A sequence of UTF-16 code units2524UTF-16 code units
GoBytes, conventionally UTF-8; source code is UTF-840Bytes
Ruststr is guaranteed to be valid UTF-841Bytes
CBytes, in whatever encoding the program assumesBytes up to the first zero

The practical rule is the same in all of them: decode bytes into text as soon as they enter the program, work with text inside, and encode back to bytes, almost always UTF-8, only at the boundary.

Putting it together: typing é on a web page

Following one character from keyboard to screen touches every layer:

  1. Input. The keyboard layout and input method turn your key presses into the code point U+00E9; on some layouts you type e and an accent key, and the system composes them.
  2. In the program. The browser holds the text field’s contents as a JavaScript string, in UTF-16: the single code unit 00E9.
  3. On the wire. When the form is submitted, the browser encodes the text as UTF-8, the bytes C3 A9 (sent percent-encoded as %C3%A9), because forms are submitted in the page’s own encoding and the page declared charset=utf-8.42
  4. On the server. The server decodes the bytes as UTF-8 and stores the string; the database stores the same two bytes in a utf8mb4 column.
  5. Back to the screen. On the next page load the bytes C3 A9 are decoded to U+00E9 again, and the browser asks a font for a glyph for that code point and draws é.

If any step uses the wrong encoding, the reader sees é instead.

Why it matters

Text is the most common kind of data, and most of it now crosses languages, systems and countries. The encoding problem looks solved, since almost everything is UTF-8, but the remaining bugs are subtle precisely because the common case works: names that are truncated in the middle of a character, lengths counted in the wrong unit, two identical-looking strings that do not compare equal, emoji that a database silently rejects. Knowing the difference between a character, a code point, a code unit, a byte and a glyph is what turns those bugs from mysteries into routine fixes.

References


  1. Whistler, K., & Freytag, A. (2022). Unicode Technical Report #17: Unicode Character Encoding Model (Revision 9). Unicode Consortium. https://www.unicode.org/reports/tr17/ ↩︎

  2. Fischer, E. (2000). The Evolution of Character Codes, 1874–1968 [Unpublished manuscript]. https://web.archive.org/web/20081217135753/http://www.transbay.net/~enf/ascii/ascii.pdf ↩︎ ↩︎

  3. Mackenzie, C. E. (1980). Coded Character Sets: History and Development. Addison-Wesley. ↩︎ ↩︎

  4. Cerf, V. (1969). ASCII format for network interchange (RFC 20, STD 80). Network Working Group. https://www.rfc-editor.org/rfc/rfc20 ↩︎ ↩︎

  5. IBM. (n.d.). The EBCDIC character set. In z/OS Basic Skills. https://www.ibm.com/docs/en/zos-basic-skills?topic=mainframe-ebcdic-character-set ↩︎

  6. International Organization for Standardization. (1998). ISO/IEC 8859-1:1998 Information technology — 8-bit single-byte coded graphic character sets — Part 1: Latin alphabet No. 1. https://www.iso.org/standard/28245.html ↩︎

  7. Unicode Consortium. (n.d.). CP1252.TXT: cp1252 to Unicode table [Mapping file, Microsoft vendor mappings]. https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT ↩︎

  8. WHATWG. (n.d.). Encoding Standard (Living Standard). https://encoding.spec.whatwg.org/ ↩︎ ↩︎ ↩︎

  9. Kotobank. (n.d.). 文字化け (mojibake). https://kotobank.jp/word/%E6%96%87%E5%AD%97%E5%8C%96%E3%81%91 ↩︎

  10. Unicode Consortium. (n.d.). Summary narrative. History of Unicode. https://www.unicode.org/history/summary.html ↩︎ ↩︎

  11. Unicode Consortium. (n.d.). Early years of Unicode. History of Unicode. https://www.unicode.org/history/earlyyears.html ↩︎

  12. Becker, J. D. (1988, August 29). Unicode 88 [Reprinted 1998]. https://www.unicode.org/history/unicode88.pdf ↩︎

  13. Unicode Consortium. (1993). Unicode 1.1.0. https://www.unicode.org/versions/Unicode1.1.0/ ↩︎

  14. Unicode Consortium. (n.d.). UTF-8, UTF-16, UTF-32 & BOM. Unicode FAQ. https://www.unicode.org/faq/utf_bom.html ↩︎

  15. Hoffman, P., & Yergeau, F. (2000). UTF-16, an encoding of ISO 10646 (RFC 2781). Internet Engineering Task Force. https://doi.org/10.17487/RFC2781 ↩︎ ↩︎ ↩︎

  16. Unicode Consortium. (2026). The Unicode Standard, Version 18.0.0: Core Specification. Chapters 2 and 3. https://www.unicode.org/versions/Unicode18.0.0/core-spec/ ↩︎ ↩︎ ↩︎

  17. Unicode Consortium. (2026). Unicode 18.0.0. https://www.unicode.org/versions/Unicode18.0.0/ ↩︎

  18. Pike, R. (2003, April 30). UTF-8 history [Email]. Archived by M. Kuhn. https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt ↩︎

  19. Yergeau, F. (2003). UTF-8, a transformation format of ISO 10646 (RFC 3629). Internet Engineering Task Force. https://doi.org/10.17487/RFC3629 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  20. Pike, R., & Thompson, K. (1993). Hello world or Καλημέρα κόσμε or こんにちは 世界. Proceedings of the Winter 1993 USENIX Conference, 43–50. https://plan9.io/sys/doc/utf.html ↩︎ ↩︎

  21. Davis, M. (2008, May 5). Moving to Unicode 5.1. Official Google Blog. https://googleblog.blogspot.com/2008/05/moving-to-unicode-51.html ↩︎

  22. W3Techs. (2026, October). Usage statistics of character encodings for websites. https://w3techs.com/technologies/overview/character_encoding ↩︎

  23. Microsoft. (n.d.). Unicode. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/intl/unicode ↩︎

  24. Oracle. (n.d.). Class Character: Unicode character representations. In Java SE 21 API Specification. https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#unicode ↩︎ ↩︎

  25. Ecma International. (2026). ECMAScript 2026 Language Specification (ECMA-262, 17th ed.), Section 6.1.4, The String Type. https://262.ecma-international.org/17.0/#sec-ecmascript-language-types-string-type ↩︎ ↩︎

  26. Hadley, J. (2026). Unicode Standard Annex #29: Unicode Text Segmentation (Revision 49). Unicode Consortium. https://www.unicode.org/reports/tr29/ ↩︎

  27. Davis, M., & Holbrook, N. (2026). Unicode Technical Standard #51: Unicode Emoji (Revision 31). Unicode Consortium. https://www.unicode.org/reports/tr51/ ↩︎

  28. Whistler, K. (2026). Unicode Standard Annex #15: Unicode Normalization Forms (Revision 58). Unicode Consortium. https://www.unicode.org/reports/tr15/ ↩︎

  29. W3C. (2026). Character Model for the World Wide Web: String Matching (W3C First Public Working Draft). https://www.w3.org/TR/charmod-norm/ ↩︎

  30. Apple. (2004). Technical Note TN1150: HFS Plus Volume Format. https://developer.apple.com/library/archive/technotes/tn/tn1150.html ↩︎

  31. Unicode Consortium. (n.d.). SpecialCasing.txt. Unicode Character Database. https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt ↩︎

  32. Whistler, K., & Scherer, M. (2026). Unicode Technical Standard #10: Unicode Collation Algorithm (Revision 55). Unicode Consortium. https://www.unicode.org/reports/tr10/ ↩︎ ↩︎

  33. WHATWG. (n.d.). Specifying the document’s character encoding. In HTML Standard (Living Standard). https://html.spec.whatwg.org/multipage/semantics.html#charset ↩︎

  34. Oracle. (n.d.). The utf8mb4 character set (4-byte UTF-8 Unicode encoding). In MySQL 8.4 Reference Manual. https://dev.mysql.com/doc/refman/8.4/en/charset-unicode-utf8mb4.html ↩︎

  35. Bugtraq. (2000). IIS Unicode directory traversal (Web server folder traversal) [Mailing list archive]. https://diswww.mit.edu/menelaus/bt/17324 ↩︎

  36. Hadley, J., & Pournader, R. (2026). Unicode Technical Standard #39: Unicode Security Mechanisms (Revision 34). Unicode Consortium. https://www.unicode.org/reports/tr39/ ↩︎

  37. Boucher, N., & Anderson, R. (2021). Trojan Source: Invisible vulnerabilities (arXiv:2111.00169). arXiv. https://arxiv.org/abs/2111.00169 ↩︎

  38. Boucher, N., & Anderson, R. (2023). Trojan Source: Invisible vulnerabilities. Proceedings of the 32nd USENIX Security Symposium, 6507–6524. https://www.usenix.org/conference/usenixsecurity23/presentation/boucher ↩︎

  39. von Löwis, M. (2010). PEP 393: Flexible string representation. Python Software Foundation. https://peps.python.org/pep-0393/ ↩︎

  40. The Go Authors. (n.d.). The Go programming language specification: Source code representation and String types. https://go.dev/ref/spec ↩︎

  41. The Rust Project. (n.d.). Primitive type str. In The Rust Standard Library. https://doc.rust-lang.org/std/primitive.str.html ↩︎

  42. WHATWG. (n.d.). Selecting a form submission encoding. In HTML Standard (Living Standard). https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#selecting-a-form-submission-encoding ↩︎