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
| Feature | UTF-8 | UTF-16 |
|---|---|---|
| Encoding unit | 8-bit byte | 16-bit code unit |
| Length per code point | 1–4 bytes | 1–2 code units |
| ASCII compatibility | Yes | No byte-level compatibility |
| Byte order issue | No | Yes for serialized data |
| BOM required | No | Not always, but may indicate byte order |
| Common use | Web, APIs, files, Linux | JavaScript, Java, Windows APIs |
| Supplementary characters | 4 bytes | Surrogate pair |
| Typical Latin-text size | Usually smaller | Usually larger |
| Fixed-width | No | No |
| Unicode coverage | Full Unicode range | Full 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 range | UTF-8 length |
|---|---|
| U+0000–U+007F | 1 byte |
| U+0080–U+07FF | 2 bytes |
| U+0800–U+FFFF | 3 bytes |
| U+10000–U+10FFFF | 4 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
| Character | Code point | UTF-8 | UTF-8 bytes | UTF-16 | UTF-16 code units |
|---|---|---|---|---|---|
| A | U+0041 | 41 | 1 | 0041 | 1 |
| é | U+00E9 | C3 A9 | 2 | 00E9 | 1 |
| Ж | U+0416 | D0 96 | 2 | 0416 | 1 |
| 字 | U+5B57 | E5 AD 97 | 3 | 5B57 | 1 |
| 😀 | U+1F600 | F0 9F 98 80 | 4 | D83D DE00 | 2 |
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.
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.
| Encoding | Typical BOM bytes |
|---|---|
| UTF-8 | EF BB BF |
| UTF-16BE | FE FF |
| UTF-16LE | FF 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 type | Typical UTF-8 size | Typical UTF-16 size |
|---|---|---|
| ASCII-heavy English | Smaller | Larger |
| Western European text | Often smaller or similar | Usually 2 bytes per BMP code point |
| Many CJK BMP characters | Often 3 bytes each | Usually 2 bytes each |
| Supplementary emoji | Usually 4 bytes | Usually 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.
| Situation | Recommended default |
|---|---|
| HTML website | UTF-8 |
| REST or JSON API | UTF-8 |
| CSV export | UTF-8 |
| Source-code files | UTF-8 |
| Database connection | UTF-8-compatible configuration |
| JavaScript string processing | Use native strings carefully; encode as UTF-8 for external data |
| Windows API requiring wide strings | UTF-16 |
| Legacy UTF-16 file format | Required UTF-16 byte order |
Try these UnicodeNow tools
Use these tools to inspect bytes, code points, code units, normalization and conversion behavior.
UTF-8 Encoder and Decoder
Convert text to UTF-8 bytes and validate byte sequences.
Unicode Character Inspector
Inspect each Unicode character, encoding, category, script and normalization form.
Unicode Sequence Analyzer
Analyze code points, grapheme clusters, bytes, scripts and directionality.
Unicode Character Counter
Count code points, grapheme clusters, words, bytes and invisible characters.
Byte Length Calculator
Count UTF-8 bytes, code points, grapheme clusters and UTF-16 code units for text.
UTF-8 Validator
Validate hexadecimal byte sequences as UTF-8.
Unicode Normalizer
Normalize Unicode text to NFC, NFD, NFKC or NFKD.
Text to Hex
Convert UTF-8 text bytes into hexadecimal values.
Hex to Text
Decode hexadecimal byte values into UTF-8 text.
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
- The Unicode Standard
- Unicode glossary
- Unicode FAQ: UTF-8, UTF-16, UTF-32 and BOM
- RFC 3629: UTF-8, a transformation format of ISO 10646
- Unicode Standard Annex #15: Unicode Normalization Forms
- Unicode Standard Annex #29: Unicode Text Segmentation
- WHATWG Encoding Standard
- MDN: JavaScript strings
- MDN: TextEncoder
- Python documentation: Unicode HOWTO
- PHP manual: Multibyte String