UTF-8 vs UTF-16: What Is the Difference?

UTF-8 and UTF-16 are both Unicode encodings, but they store code points using different-sized units. UTF-8 uses one to four bytes, while UTF-16 uses one or two 16-bit code units.

On this page

UTF-8 vs UTF-16 at a glance

FeatureUTF-8UTF-16
Encoding unit8-bit byte16-bit code unit
Length per code point1–4 bytes1–2 code units
ASCII compatibilityYesNo byte-level compatibility
Byte order issueNoYes for serialized data
BOM requiredNoNot always, but may indicate byte order
Common useWeb, APIs, files, LinuxJavaScript, Java, Windows APIs
Supplementary characters4 bytesSurrogate pair
Typical Latin-text sizeUsually smallerUsually larger
Fixed-widthNoNo
Unicode coverageFull Unicode rangeFull Unicode range

UTF-8 stores Unicode as variable-length bytes. UTF-16 stores Unicode as one or two 16-bit code units.

What are UTF-8 and UTF-16?

Unicode assigns code points such as U+0041, U+00E9 and U+1F600. UTF-8 and UTF-16 are encoding forms that represent those code points for storage, APIs and binary data. Both can represent the full Unicode range, and neither changes the identity of the character or code point.

Character → Unicode code point → UTF encoding → bytes or code units

😀 → U+1F600
UTF-8 → F0 9F 98 80
UTF-16 → D83D DE00

The difference is serialization. UTF-8 serializes Unicode scalar values into byte sequences. UTF-16 serializes them into 16-bit code units, which need a byte order when written to bytes. For background, read Unicode vs UTF-8 or use the Unicode Character Inspector.

How UTF-8 works

UTF-8 uses one to four 8-bit bytes per Unicode scalar value. ASCII-range code points use one byte. Higher code points require additional bytes, and continuation bytes follow a recognizable bit pattern. UTF-8 has no byte-order ambiguity.

Code-point rangeUTF-8 length
U+0000–U+007F1 byte
U+0080–U+07FF2 bytes
U+0800–U+FFFF3 bytes
U+10000–U+10FFFF4 bytes

Surrogate code points are excluded from valid UTF-8 because they are not Unicode scalar values. Invalid byte sequences should be rejected or handled explicitly, especially when reading untrusted files or network input.

A → U+0041 → 41
é → U+00E9 → C3 A9
Ж → U+0416 → D0 96
字 → U+5B57 → E5 AD 97
😀 → U+1F600 → F0 9F 98 80

Use the UTF-8 Encoder and Decoder to convert text to bytes, and the UTF-8 Validator to check byte sequences before decoding them.

How UTF-16 works

UTF-16 uses 16-bit code units. Code points from U+0000 to U+FFFF normally use one code unit, except the surrogate range. Supplementary code points from U+10000 to U+10FFFF use two code units called a surrogate pair.

A → U+0041 → 0041
é → U+00E9 → 00E9
Ж → U+0416 → 0416
字 → U+5B57 → 5B57
😀 → U+1F600 → D83D DE00

U+D83D is the high surrogate.

U+DE00 is the low surrogate.

Together they represent U+1F600.

UTF-8 and UTF-16 examples

CharacterCode pointUTF-8UTF-8 bytesUTF-16UTF-16 code units
AU+004141100411
éU+00E9C3 A9200E91
ЖU+0416D0 96204161
U+5B57E5 AD 9735B571
😀U+1F600F0 9F 98 804D83D DE002

The code points are the same in both encodings. The storage representation changes. Fewer UTF-16 code units does not always mean fewer bytes because each code unit is 16 bits.

Interactive UTF-8 and UTF-16 comparison

This comparison runs locally in your browser. It is useful when a string looks short on screen but occupies more bytes or UTF-16 code units than expected.

UTF-8 and UTF-16 comparison

Inspect each code point as UTF-8 bytes and UTF-16 code units.

Processed locally in your browser

Limit: 2,000 UTF-16 code units. Input is not sent to UnicodeNow servers, analytics or logs.

Character Unicode name Code point UTF-8 hexadecimal bytes UTF-8 byte count UTF-16 hexadecimal code units UTF-16 code-unit count UTF-16 byte count

ASCII compatibility

UTF-8 preserves ASCII byte values. UTF-16 does not preserve ASCII at the raw-byte level because each 16-bit code unit is serialized as two bytes. ASCII text encoded as UTF-8 is unchanged, while ASCII text encoded as UTF-16 normally contains additional zero bytes.

Text: ABC

UTF-8:
41 42 43

UTF-16BE:
00 41 00 42 00 43

UTF-16LE:
41 00 42 00 43 00

UTF-16 byte order

A UTF-16 code unit contains two bytes. Serialized UTF-16 data must define whether those bytes are stored big-endian or little-endian. UTF-16BE places the most significant byte first. UTF-16LE places the least significant byte first.

U+0041

UTF-16BE → 00 41
UTF-16LE → 41 00

Internal programming-language strings may abstract byte order away. JavaScript string code units are conceptually UTF-16 values, but ordinary JavaScript code usually does not manually handle their byte order until converting to binary data, files or network payloads.

What is a byte order mark?

The byte order mark is U+FEFF. At the start of UTF-16 data, it can indicate byte order. In UTF-8, a BOM is optional and unnecessary because UTF-8 has no byte-order ambiguity. A UTF-8 BOM can cause problems in tools that do not expect it.

EncodingTypical BOM bytes
UTF-8EF BB BF
UTF-16BEFE FF
UTF-16LEFF FE

Not every UTF-16 file must have a BOM. An explicit external encoding declaration can define byte order. Inside text, U+FEFF historically had other implications, but word joining should use U+2060. Use the UTF-8 Validator when debugging suspicious leading bytes.

Which encoding uses less space?

The answer depends on the text. Mostly ASCII or English text usually uses about one byte per character in UTF-8 and two bytes per BMP code point in UTF-16. Many European accented characters often use one or two bytes in UTF-8. Many CJK BMP characters use three UTF-8 bytes but one UTF-16 code unit. Many emoji use four bytes in both encodings.

Text typeTypical UTF-8 sizeTypical UTF-16 size
ASCII-heavy EnglishSmallerLarger
Western European textOften smaller or similarUsually 2 bytes per BMP code point
Many CJK BMP charactersOften 3 bytes eachUsually 2 bytes each
Supplementary emojiUsually 4 bytesUsually 4 bytes

Code points, code units and visible characters

Counts differ because a visible grapheme, a Unicode code point, a UTF-8 byte and a UTF-16 code unit are different layers. 😀 is typically 1 grapheme cluster, 1 code point, 4 UTF-8 bytes and 2 UTF-16 code units.

The family emoji 👨‍👩‍👧‍👦 may display as one emoji but contains multiple code points, zero-width joiners, many UTF-8 bytes and many UTF-16 code units. The letter é can be the precomposed U+00E9 or the decomposed sequence U+0065 U+0301.

Read Code Points, Code Units and Grapheme Clusters, count text with the Unicode Character Counter, inspect sequences with the Unicode Sequence Analyzer, and normalize intentionally with the Unicode Normalizer.

Why JavaScript uses UTF-16 code units

JavaScript strings are sequences of UTF-16 code units. String.length returns the number of UTF-16 code units, so BMP characters usually count as one and supplementary code points usually count as two. Array iteration and for...of handle code points better than indexing, but grapheme clusters still require segmentation.

const emoji = "😀";

console.log(emoji.length);       // 2 UTF-16 code units
console.log([...emoji].length);  // 1 code point
const family = "👨‍👩‍👧‍👦";

console.log(family.length);
console.log([...family].length);

const segmenter = new Intl.Segmenter("en", {
    granularity: "grapheme",
});

console.log([...segmenter.segment(family)].length); // Usually 1
const bytes = new TextEncoder().encode("😀");
console.log([...bytes]); // [240, 159, 152, 128]

TextEncoder produces UTF-8 bytes from JavaScript strings. Use the Byte Length Calculator and Unicode Sequence Analyzer to compare these counts without writing a script.

UTF-8 and UTF-16 in Python

Python str represents Unicode text. Developers normally do not treat Python strings themselves as UTF-8 or UTF-16. Encoding converts str to bytes, and decoding converts bytes to str. UTF-16 output usually needs explicit byte-order consideration.

text = "Aé😀"

utf8 = text.encode("utf-8")
utf16_le = text.encode("utf-16-le")
utf16_be = text.encode("utf-16-be")

print(utf8.hex(" "))
print(utf16_le.hex(" "))
print(utf16_be.hex(" "))

decoded = utf8.decode("utf-8")

Using "utf-16" may include or interpret a BOM depending on the operation, while "utf-16-le" and "utf-16-be" specify byte order explicitly.

UTF-8 and UTF-16 in PHP

PHP strings are byte sequences. Encoding-aware functions are required for Unicode text operations. strlen() returns bytes, mb_strlen() can count characters in a specified encoding, and encoding conversion may use mb_convert_encoding().

$text = "Aé😀";

echo strlen($text);
echo mb_strlen($text, "UTF-8");

$utf16le = mb_convert_encoding($text, "UTF-16LE", "UTF-8");

Do not assume PHP strings natively store UTF-16. The behavior depends on the bytes in the string and the function you call.

UTF-8 and UTF-16 on the web

UTF-8 is the standard practical choice for HTML, CSS, JavaScript source, JSON and APIs. HTML should declare UTF-8 near the beginning, and HTTP may include a charset. UTF-16 web content is possible in some contexts but is generally less interoperable.

<meta charset="utf-8">
Content-Type: text/html; charset=utf-8

URL percent encoding is based on bytes, commonly UTF-8 for Unicode input. JSON exchanged over modern systems is usually UTF-8. Use URL Encoder and Decoder, JSON Escape and Unescape and HTML Entity Encoder and Decoder when text crosses web syntax boundaries.

Common UTF-8 and UTF-16 mistakes

Assuming UTF-16 means two bytes per character

Surrogate pairs and grapheme clusters make that false. UTF-16 uses one or two code units per code point, and visible characters can contain several code points.

Assuming JavaScript length equals visible characters

JavaScript length counts UTF-16 code units, not grapheme clusters.

Reading UTF-16 with the wrong byte order

If UTF-16LE bytes are read as UTF-16BE, code units are byte-swapped and text can become corrupted.

Losing or duplicating the BOM

File pipelines should handle BOMs intentionally so byte order is not lost and unexpected leading characters are not introduced.

Treating UTF-16 bytes as UTF-8

This often creates null-byte-heavy output or replacement characters because the byte stream is not valid UTF-8 text.

Cutting a string at arbitrary bytes or code units

Truncation can split UTF-8 sequences, UTF-16 surrogate pairs or grapheme clusters.

Encoding text more than once

Double encoding turns already encoded data through the wrong layer again and can produce repeated corruption.

Confusing normalization with encoding

NFC and NFD affect code-point sequences. They do not decide whether bytes are UTF-8 or UTF-16. Read Unicode Normalization Explained or use the Unicode Normalization Checker.

Using encoding detection as certainty

Detection tools return likely matches and may be ambiguous. Keep original bytes and validate assumptions with tools such as Mojibake Repair and the UTF-8 Validator.

When should you use UTF-8?

Use UTF-8 for web pages, APIs, JSON, XML unless another encoding is explicitly required, source code, configuration files, CSV files, databases and database connections, logs, cross-platform data exchange, and Linux or Unix-like environments.

The practical reasons are ASCII compatibility, no byte-order ambiguity, strong interoperability, compact storage for ASCII-heavy content and common tooling support. UTF-8 is a strong default, but not mandatory in every system.

When might UTF-16 be appropriate?

UTF-16 can be appropriate when integrating with existing JavaScript, Java or Windows APIs, internal representations already based on UTF-16 code units, legacy binary formats, protocols explicitly requiring UTF-16, datasets dominated by BMP code points where storage characteristics matter, or systems that natively exchange UTF-16.

Which should you choose?

Choose UTF-8 for new files, web pages, APIs and cross-platform data unless a specific system or protocol requires UTF-16. Use UTF-16 when integrating with an environment or file format that explicitly expects it.

SituationRecommended default
HTML websiteUTF-8
REST or JSON APIUTF-8
CSV exportUTF-8
Source-code filesUTF-8
Database connectionUTF-8-compatible configuration
JavaScript string processingUse native strings carefully; encode as UTF-8 for external data
Windows API requiring wide stringsUTF-16
Legacy UTF-16 file formatRequired UTF-16 byte order

Try these UnicodeNow tools

Use these tools to inspect bytes, code points, code units, normalization and conversion behavior.

Unicode Character Inspector

Inspect each Unicode character, encoding, category, script and normalization form.

UnicodeProcessed locally

Unicode Sequence Analyzer

Analyze code points, grapheme clusters, bytes, scripts and directionality.

UnicodeProcessed locally

Unicode Character Counter

Count code points, grapheme clusters, words, bytes and invisible characters.

Text ComparisonProcessed locally

Byte Length Calculator

Count UTF-8 bytes, code points, grapheme clusters and UTF-16 code units for text.

EncodingProcessed locally

UTF-8 Validator

Validate hexadecimal byte sequences as UTF-8.

EncodingServer tool

Unicode Normalizer

Normalize Unicode text to NFC, NFD, NFKC or NFKD.

NormalizationProcessed locally

Text to Hex

Convert UTF-8 text bytes into hexadecimal values.

EncodingProcessed locally

Hex to Text

Decode hexadecimal byte values into UTF-8 text.

EncodingProcessed locally

Frequently asked questions

Is UTF-16 better than UTF-8?

Neither is universally better. UTF-8 is generally preferred for interchange, while UTF-16 remains important in JavaScript, Java, Windows APIs and some file formats.

Is UTF-16 always two bytes per character?

No. Supplementary code points require two 16-bit code units, and visible graphemes may contain multiple code points.

Is UTF-8 always smaller than UTF-16?

No. UTF-8 is usually smaller for ASCII-heavy content, while UTF-16 may be smaller for many BMP characters that require three bytes in UTF-8.

Why does JavaScript use UTF-16?

JavaScript strings are historically defined as sequences of UTF-16 code units.

Does UTF-16 require a byte order mark?

No. Byte order may be declared externally, but a BOM is often used in files to indicate endianness.

Does UTF-8 need a byte order mark?

No. UTF-8 has no byte-order ambiguity.

Can UTF-8 and UTF-16 represent the same characters?

Yes. Both cover the full Unicode range.

Can converting between UTF-8 and UTF-16 lose data?

A correct conversion between valid Unicode representations should preserve the text. Data loss can occur from invalid sequences, unsupported software or incorrect error handling.

Why does an emoji have length two in JavaScript?

Because many emoji are supplementary code points represented by two UTF-16 code units.

Which encoding should a database use?

Use a full-Unicode UTF-8-compatible configuration where supported, while checking database-specific character-set and collation settings.

References