UTF-8 vs UTF-16 is a comparison of two ways to encode the same Unicode text. UTF-8 uses one to four bytes per Unicode scalar value. UTF-16 uses one or two 16-bit code units, which occupy two or four bytes when serialized. Neither encoding supports more languages than the other.

The practical differences concern byte size, compatibility, byte order, and the APIs that process the text. English-heavy data is usually smaller in UTF-8. Many common Chinese characters take fewer bytes in UTF-16. A supplementary code point, including many emoji characters, takes four bytes in both.

Choosing correctly also means separating a file's encoding from a program's string model. A JavaScript string can use UTF-16 code-unit indexing while the application sends UTF-8 over the network. The comparison below shows the byte rules, checks real examples, and explains how to convert without losing or misreading text.

The Practical Choice in One Paragraph

Use UTF-8 for new web content and general text interchange unless an interface specifies another encoding. Use UTF-16 when an API or file format requires it, with the byte order identified. Both represent the same Unicode scalar values; their byte counts, code-unit sizes, and compatibility rules differ.

Compare the Two Encodings Side by Side

The numbers in the names describe the size of a code unit, not a promise that every visible character occupies that many bits.

Property

UTF-8

UTF-16

Code unit

8 bits, or one byte

16 bits, or two serialized bytes

Code units per scalar value

1–4

1–2

Serialized bytes per scalar value

1–4

2 or 4

ASCII compatibility

Preserves ASCII byte values

ASCII values use two serialized bytes

Supplementary code point

Four bytes

A surrogate pair totaling four bytes

Byte-order variants

No LE/BE variants needed

UTF-16LE and UTF-16BE

Typical external use

Web content, interchange, text files

Interfaces and formats that specify UTF-16

These Unicode encoding forms are variable-length encodings. UTF-16 becomes two code units for supplementary characters, so treating every character as one 16-bit unit breaks valid text.

The comparison concerns encoded data. A runtime's actual memory use can include object overhead, compact string representations, buffers, or caches. File-byte calculations are not automatically measurements of a program's total memory consumption.

Understand What Each Encoding Represents

Unicode assigns code points to text elements. A code point is a numeric identifier, such as U+4E2D for 中. A character encoding defines how that identifier is represented by code units and, for files or transmission, bytes.

A Unicode scalar value is a code point outside the reserved surrogate range U+D800–U+DFFF. Well-formed UTF-8 and UTF-16 represent all scalar values. Surrogate code points are not standalone characters to encode as ordinary text.

The Basic Multilingual Plane, or BMP, covers U+0000–U+FFFF. Its scalar values fit in one UTF-16 code unit. Supplementary code points, from U+10000 through U+10FFFF, require a surrogate pair: a high surrogate (D800–DBFF) followed by a low surrogate (DC00–DFFF).

A reader-perceived character is another layer. A grapheme cluster can contain several code points, such as a base letter followed by a combining accent. As a result, bytes, code units, code points, and visible characters can all have different counts. Neither encoding removes the need to handle those distinctions.

Inspect the Same Characters as Hexadecimal Bytes

The table shows actual hexadecimal bytes, not just code-point numbers. All examples exclude a byte order mark.

Character / Code Point

UTF-8

UTF-16LE

UTF-16BE

G, U+0047

47

47 00

00 47

ñ, U+00F1

C3 B1

F1 00

00 F1

क, U+0915

E0 A4 95

15 09

09 15

中, U+4E2D

E4 B8 AD

2D 4E

4E 2D

Rocket emoji, U+1F680

F0 9F 9A 80

3D D8 80 DE

D8 3D DE 80

For the rocket code point, UTF-16 uses code units D83D and DE80. Little-endian serialization changes the order of bytes within each unit, producing 3D D8 80 DE. It does not reverse the order of the surrogate pair.

This distinction matters when a text-to-hex tool displays UTF-16 code units. A four-digit unit such as 4E2D is not by itself a statement about whether the serialized bytes are 4E 2D or 2D 4E. Check the selected encoding and byte order before comparing outputs.

Calculate File Size from the Actual Text

For well-formed text, the UTF-8 byte count per scalar value follows these ranges:

  • U+0000–U+007F: one byte.
  • U+0080–U+07FF: two bytes.
  • BMP scalar values from U+0800 upward: three bytes.
  • Supplementary scalar values: four bytes.

The surrogate range is excluded from the BMP rule. UTF-16 uses two bytes for each BMP scalar value and four for each supplementary scalar value.

Sample Text

UTF-8 Bytes

UTF-16 Bytes, Excluding BOM

Code

4

8

中文

6

4

OK中文

8

8

One rocket code point, U+1F680

4

4

These examples explain why neither format is always smaller. For the Chinese-only sample, UTF-16 uses one-third fewer bytes than UTF-8; conversely, UTF-8 uses 50% more than UTF-16. The percentage changes with the comparison's starting size.

ASCII markup, digits, spaces, and punctuation also count. A document containing Chinese prose can still include enough ASCII syntax to change the overall result. Measure representative files rather than predicting size from a language label alone.

The figures above exclude BOMs, compression, line-ending changes, and container overhead. Those conditions must match before file-size results can be compared fairly.

Grid-paper notebook, pencil, keyboard and USB cable on a desk.
Work through a small example and verify the result. Original AI-generated editorial photograph.

Byte Order and BOM: What the File Must Declare

Endianness describes how multibyte code units are serialized. UTF-16LE stores the least significant byte of each code unit first; UTF-16BE stores the most significant byte first. For G, the sequences are 47 00 and 00 47 respectively.

UTF-8 has one-byte code units, so there is no UTF-8LE or UTF-8BE distinction.

A byte order mark, or BOM, can identify the serialization at the beginning of a stream:

Encoding Signature

Initial Bytes

UTF-8 signature

EF BB BF

UTF-16 little-endian BOM

FF FE

UTF-16 big-endian BOM

FE FF

UTF-8 does not need a BOM to establish byte order. Some formats accept its signature, while others prohibit or discourage it. Network-transmitted JSON must not have a BOM added by its producer.

For UTF-16, byte order may be supplied by the format, an explicit encoding label, or a BOM where permitted. Follow that contract. Do not assume every unmarked file has the same order or that every decoder uses the same fallback.

A BOM is metadata with overhead: three bytes for a UTF-8 signature or two for a UTF-16 BOM. Explicit UTF-16LE/BE streams often rely on their declared encoding instead, so adding a marker automatically can conflict with the receiver's expectations.

Copper traces and a memory chip on a printed circuit board.
Keep the byte representation and its interpretation together. Original AI-generated editorial photograph.

JavaScript Length Is a Code-Unit Count

JavaScript string length counts UTF-16 code units. It does not count UTF-8 bytes or reliably count visible characters.

const rocket = "\u{1F680}";
console.log(rocket.length);                       // 2 code units
console.log([...rocket].length);                  // 1 code point
console.log(new TextEncoder().encode(rocket).length); // 4 bytes

The spread operator iterates this well-formed string by code point. It still does not solve every user-facing character-counting problem. For grapheme clusters, use an appropriate segmentation API, such as Intl.Segmenter.

For example, a base letter plus a combining mark may display as one unit of text while containing two code points. Truncating at an arbitrary code-unit boundary can also split a surrogate pair.

Native JavaScript strings use UTF-16 code-unit semantics, but engines can optimize their physical storage. Multiplying .length by two is not a reliable measurement of total runtime memory.

TextEncoder produces UTF-8 bytes. It does not provide a switch for UTF-16 output. Use TextDecoder with an explicit supported encoding when reading a byte stream, and distinguish those decoding options from the encoder's fixed UTF-8 output.

Hands typing on a computer keyboard beside a softly blurred screen.
Test a known example before converting a larger input. Original AI-generated editorial photograph.

Convert Between Encodings by Decoding and Re-encoding

Transcoding changes the byte representation while preserving the Unicode sequence. Decode the source using its actual encoding, then encode the resulting text using the destination encoding.

This Python example uses explicit byte-order labels and excludes BOMs:

text = "G中\U0001F680"
utf8 = text.encode("utf-8")
utf16le = text.encode("utf-16-le")
print(utf8.hex(" "))     # 47 e4 b8 ad f0 9f 9a 80
print(utf16le.hex(" "))  # 47 00 2d 4e 3d d8 80 de
print(len(utf8), len(utf16le))  # 8 8
converted = utf16le.decode("utf-16-le").encode("utf-8")

assert converted == utf8

Python's generic utf-16 encoder adds a BOM; the explicit utf-16-le and utf-16-be encoders do not add one. Select the codec required by the file format instead of removing bytes by guesswork.

For a UTF-8 conversion from an existing UTF-16 file that uses a BOM:

from pathlib import Path
source_bytes = Path("source-utf16.txt").read_bytes()
text = source_bytes.decode("utf-16", errors="strict")
Path("output-utf8.txt").write_bytes(text.encode("utf-8"))

Writing bytes here avoids an extra text-mode newline conversion. It also avoids adding a UTF-8 BOM. The example assumes the source follows the stated UTF-16 convention; a BOM-less file with known little-endian bytes should use utf-16-le explicitly instead.

Changing a filename extension or an encoding declaration does not transcode the contents. The bytes must actually be decoded and re-encoded.

Choose for the Interface, Then Measure Performance

For UTF-8 vs UTF-16, external requirements should lead the decision. Prefer UTF-8 for newly authored web content. For JSON exchanged outside a closed ecosystem, RFC 8259 requires UTF-8. A browser application's internal string model does not change that transport requirement.

Keep UTF-16 when a Windows API, an established data format, or another interface specifically requires it. A program can use one encoding internally and another externally, provided conversion happens at clearly defined boundaries.

Performance has no universal winner. Results depend on the text, algorithms, runtime, conversion frequency, and memory traffic. A UTF-16-native API may avoid a conversion step with UTF-16 input, while smaller UTF-8 data can reduce the bytes transferred through an ASCII-heavy pipeline.

Benchmark the actual workload when the difference matters. Measure complete operations, including parsing, validation, conversion, allocation, and output. A file-size comparison alone does not establish which application will run faster.

Compression can also change the storage comparison. Raw byte counts are useful for buffer limits and uncompressed data, but they do not predict compressed output exactly.

Catch Encoding Problems Before Saving Changes

Misreading valid bytes using the wrong decoder can produce corrupted-looking text, often called mojibake. The decoder may also fail outright. A successful decode is not conclusive proof of the encoding: some wrong interpretations still produce valid-looking Unicode.

For malformed input, choose an explicit error policy. Python's strict decoders raise UnicodeDecodeError. Replacement policies can insert U+FFFD, while ignoring errors can discard data. If text was replaced or removed, a later conversion cannot reconstruct the original sequence from the altered text alone.

Keep the original bytes until conversion is verified. Check the format's encoding label, inspect any BOM, compare known samples, and confirm that re-encoding preserves the intended text.

For streamed data, use an incremental or streaming decoder. A chunk can end in the middle of a UTF-8 sequence or a UTF-16 surrogate pair. Decoding arbitrary chunks independently can report errors even when the complete stream is valid.

Transcoding is also separate from Unicode normalization. Composed and decomposed text can look identical while having different code-point sequences. Normalize only when your application's rules call for it, rather than treating it as an automatic encoding-repair step.

Common Questions About These Unicode Formats

Is UTF-8 better than UTF-16?

UTF-8 is the usual choice for new web content and general interchange, but the receiving interface may require UTF-16. Both can encode the same Unicode scalar values. Choose based on compatibility first, then representative size and performance measurements. The larger number in the name does not indicate a more capable format.

Is UTF-16 always two bytes per character?

No. A BMP scalar value uses one 16-bit code unit, while a supplementary scalar value needs two, totaling four bytes. A visible character may contain multiple code points and require even more bytes. Specify whether “character” means a code point, code unit, or grapheme cluster before estimating storage.

Is UTF-8 always smaller than UTF-16?

No. ASCII uses one byte in UTF-8 and two in UTF-16. Many BMP characters, including common Chinese characters, use three bytes in UTF-8 and two in UTF-16. Supplementary scalar values use four in both. The whole file's mix of text, markup, BOM, and other formatting determines the final comparison.

Can both encodings represent emojis and all languages?

Yes. Both cover the full Unicode scalar-value repertoire, including supplementary characters. Many emoji code points use four bytes in either format, but a displayed emoji sequence can contain several code points. Whether the result displays correctly also depends on font and application support, not just the encoding's ability to represent it.

What is the difference between UTF-16LE and UTF-16BE?

They use the same UTF-16 code units but serialize each unit's two bytes in opposite orders. Little-endian puts each code unit's least significant byte first; big-endian puts its most significant byte first. The receiver must know which order applies. Neither variant changes the text's code points or gives access to additional characters.

Can I convert UTF-16 to UTF-8 without losing text?

Yes, for well-formed input decoded with the correct byte order and re-encoded without filtering or substitution. Both encodings preserve the same Unicode sequence. Malformed input, an incorrect source label, lossy error handling, or unrelated text transformations can change the result. Validate the conversion before replacing the only copy of a file.

Is UTF-16 the same as UCS-2?

No. UCS-2 is an older fixed-width format limited to the BMP and does not support surrogate pairs. UTF-16 extends that basic arrangement to cover supplementary code points using pairs. They agree on BMP scalar values, but software restricted to UCS-2 cannot correctly represent every well-formed UTF-16 sequence.

Keep Text Meaning Separate from Its Byte Layout

The useful answer to UTF-8 vs UTF-16 depends on where the text is going. Use UTF-8 for modern interchange unless a contract says otherwise; specify byte order when UTF-16 is required. Check bytes, code units, and visible-character counts separately, then verify conversion with real multilingual samples before changing stored data.