One code unit per scalar does not mean one visible character
UTF-32 represents every Unicode scalar value using exactly one 32-bit code unit. Its central benefit is a direct correspondence between a valid scalar value and one code unit, avoiding surrogate-pair assembly and variable-length UTF-8 decoding for simple scalar indexing. Yet a scalar value is not a displayed glyph or a user-perceived character. Text can contain sequences of combining marks, emoji modifiers or joiners that users perceive as one symbol.
Unicode scalar values comprise U+0000..U+D7FF and U+E000..U+10FFFF. The interval U+D800..U+DFFF is reserved for surrogates and is excluded. Four-byte storage does not enlarge the allowable scalar range: a 32-bit integer above 0x10FFFF does not become a Unicode character simply because a container can store it.
Valid UTF-32 code unit values
The numerical value of each valid UTF-32 code unit equals the scalar value represented. The encoding form itself is a sequence of 32-bit units; a byte encoding scheme must still choose how each unit is serialized. Big-endian UTF-32 stores most significant octets first, while little-endian stores least significant octets first.
| Scalar | UTF-32BE bytes | UTF-32LE bytes |
|---|---|---|
| U+0041 LATIN CAPITAL A | 00 00 00 41 | 41 00 00 00 |
| U+00E9 LATIN SMALL LETTER E WITH ACUTE | 00 00 00 E9 | E9 00 00 00 |
| U+20AC EURO SIGN | 00 00 20 AC | AC 20 00 00 |
| U+1D400 MATHEMATICAL BOLD CAPITAL A | 00 01 D4 00 | 00 D4 01 00 |
| U+1F600 GRINNING FACE | 00 01 F6 00 | 00 F6 01 00 |
These examples deliberately separate scalar identity from byte order. The mathematical bold A is a single scalar U+1D400 in UTF-32, two code units in UTF-16 and four bytes in UTF-8. The same character is not “more Unicode” in one UTF than another; all well-formed UTF encodings represent the same scalar set.
Surrogates, out-of-range values and noncharacters
Any four-byte word corresponding to the surrogate interval or a number beyond U+10FFFF is invalid as a UTF-32 scalar. For example, 00 00 D8 00 interpreted as UTF-32BE equals U+D800 and must be rejected or recovered according to the decoder's error policy. Likewise 00 11 00 00 represents 0x110000, beyond the maximum legal scalar.
Noncharacters are a different concept. Unicode reserves values such as U+FDD0 and U+FFFF as noncharacters for internal-use conventions. They are valid Unicode scalar values and valid in UTF-8, UTF-16 and UTF-32, although higher-level applications may restrict their presence. Treating all noncharacters as malformed UTF encodings confuses application policy with conformance to the encoding form.
When UTF-32 helps, and what it costs
UTF-32 can simplify low-level algorithms that need direct scalar indexing: an array index corresponds to a scalar position in a well-formed stream. This does not solve locale-sensitive casing, normalization, collation, script shaping or grapheme navigation. A developer who assumes the i-th UTF-32 unit equals the i-th rendered character will still break combining sequences and many emoji.
With exactly four bytes per scalar, UTF-32 uses substantially more space for ASCII-heavy data than UTF-8. For ten ASCII letters, UTF-8 uses ten bytes while UTF-32 uses forty bytes before container overhead or a BOM. For ten supplementary scalars, both encodings take forty bytes; UTF-16 likewise uses forty bytes. Real memory and processing performance also depend on allocation, cache locality and runtime representations, not only the theoretical number of bytes per scalar.
A strict Python UTF-32 round trip
Python's explicit utf-32-le and utf-32-be codecs allow predictable byte-level testing without relying on a BOM. Use strict decoding to find malformed values; a successful file read in a permissive decoder does not prove that the source was well-formed or that no substitution occurred.
sample = "A😀𝐀"
big = sample.encode("utf-32-be")
little = sample.encode("utf-32-le")
assert big.hex(" ") == "00 00 00 41 00 01 f6 00 00 01 d4 00"
assert little.hex(" ") == "41 00 00 00 00 f6 01 00 00 d4 01 00"
assert big.decode("utf-32-be") == sample
assert little.decode("utf-32-le") == sample
try:
bytes.fromhex("00 00 d8 00").decode("utf-32-be", "strict")
raise AssertionError("decoded surrogate scalar")
except UnicodeDecodeError:
pass
The codec label utf-32 behaves differently from explicit endian labels: Python generally writes a BOM when encoding with the generic codec and uses a BOM to determine byte order for decoding. For a protocol that already fixes byte order, choose the explicit label; do not depend on host CPU endianness.
Manual UTF-32BE serialization in JavaScript
Web JavaScript commonly exposes UTF-8 via TextEncoder rather than a standard native UTF-32 TextEncoder option. UTF-32 serialization can be implemented explicitly using code-point iteration and a DataView, but input must be checked for isolated surrogates. Because code-point iteration over an ill-formed JavaScript string can yield an isolated surrogate as a number, the validation below is essential.
function utf32BE(input) {
const points = Array.from(input, ch => ch.codePointAt(0));
if (points.some(cp =>
cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF))) {
throw new TypeError("Input includes invalid scalar values");
}
const out = new Uint8Array(points.length * 4);
const view = new DataView(out.buffer);
points.forEach((cp, i) => view.setUint32(4 * i, cp, false));
return out;
}
console.log([...utf32BE("A")]); // [0, 0, 0, 65]
When reading UTF-32 from untrusted bytes, validate that the byte length is a multiple of four, choose the correct endian mode and reject units in the surrogate range or beyond 0x10FFFF. The example encoder is not a complete streaming decoder and does not prescribe a recovery behavior for malformed input.
Scalar offsets and grapheme-aware operations
Consider e followed by U+0301 COMBINING ACUTE ACCENT. It contains two scalar values—and therefore two UTF-32 code units—but is commonly perceived as one accented letter. The precomposed é contains one scalar and one UTF-32 unit. Both strings may be canonically equivalent after normalization without containing the same number of scalar values before normalization.
An emoji family may contain multiple scalars joined by U+200D ZERO WIDTH JOINER; UTF-32 indexing exposes each scalar independently but does not reveal the intended sequence boundary. For cursor movement, selection, truncation or UI limits, use UAX #29 segmentation or a platform's tailored text-boundary engine, not 32-bit unit indexing. UTF-32 is a storage model rather than a text segmentation algorithm.
Endianness, BOMs and protocol labels
A UTF-32BE stream that starts with U+FEFF has bytes 00 00 FE FF; a UTF-32LE stream starts with FF FE 00 00. These signatures may be used when external protocol metadata does not already define the endian scheme. Crucially, bytes FF FE alone are insufficient to identify UTF-16LE if the following two bytes are 00 00, because the four-byte UTF-32LE BOM shares that prefix.
UTF-32 is rarely used for ordinary HTML or network exchange. UTF-8 remains preferred in contemporary web contexts, while UTF-32 can be useful as an intermediate internal representation for scalar-oriented algorithms. Do not infer the file's encoding from how its text looks in an editor; verify signatures and the protocol declaration, then decode with an explicit error policy.
Conformance and boundary test cases
| 32-bit unit | Encoding status | Reason |
|---|---|---|
00000000 | Valid | U+0000 is a scalar, even if a protocol bans NUL |
0000D7FF | Valid | Last scalar before surrogate interval |
0000D800 | Invalid | Surrogate, not a scalar |
0000DFFF | Invalid | Surrogate, not a scalar |
0000E000 | Valid | First scalar following surrogate interval |
0000FFFF | Valid encoding | Noncharacter; application policy is separate |
0010FFFF | Valid | Highest Unicode scalar |
00110000 | Invalid | Above U+10FFFF |
Test buffer lengths that are not divisible by four, corrupt BOMs, both endian forms and surrogate/out-of-range words. For correctness, compare decoded scalar values; for end-user quality, add normalization, grapheme and font-display checks rather than claiming a one-unit-per-scalar representation guarantees a one-unit-per-character user experience.
Standards and implementation references
- Unicode FAQ — UTF-32, BOM and byte order
- Unicode Standard Chapter 3 — Encoding Forms
- Unicode FAQ — Private-use and noncharacter distinctions
- UAX #29 — Unicode Text Segmentation
- Python codecs — UTF-32 and error handling
Independent Unicode interoperability reference. Always validate encoded data separately from font rendering and user-perceived characters.