Every unnecessary body.decode() / .encode() on a hot path is a pure tax: allocate, scan, allocate again. The unfair advantage is drawing a hard boundary—bytes on the wire, str only inside the validated domain—and refusing to cross it twice.
⚡ TL;DR: Accept
bytesfrom sockets/ASGI; validate with parsers that consume bytes (orjson.loads, Pydanticvalidate_json); keep hashing/signing on bytes; decode once at the human/domain edge. Pair with Pydantic v2 Validators and orjson and msgspec.
The double tax pattern
# ❌ Decode early, encode for crypto, decode for logs...
raw = await request.body() # bytes
text = raw.decode("utf-8") # alloc #1
payload = json.loads(text) # parse from str
sig = hmac.new(key, text.encode(), hashlib.sha256) # alloc #2
# ✅ Stay in bytes until domain objects exist
import orjson
import hmac
import hashlib
raw = await request.body()
if not hmac.compare_digest(
hmac.new(key, raw, hashlib.sha256).digest(),
given_sig,
):
raise PermissionError("bad hmac")
payload = orjson.loads(raw) # bytes in
# domain objects / Pydantic here — str fields appear once
ASGI and framework boundaries
| Layer | Prefer | Why |
|---|---|---|
| ASGI receive | bytes |
Protocol truth |
| Signature / checksum | bytes |
Spec is octet |
| JSON parse | bytes → struct |
Avoid UTF-8 scan twice |
| Business rules | str / types |
Human domain |
| Outbound HTTP body | bytes |
One encode |
# ✅ Explicit codec at the edge only
def parse_header_name(value: bytes) -> str:
# header values: decode once with a strict error policy
return value.decode("latin-1") # HTTP header semantics
def user_message(msg: str) -> bytes:
return msg.encode("utf-8")
❌ Calling .decode("utf-8") inside a tight loop over Kafka messages when the consumer already gives bytes and the next hop is another binary protocol.
Logging without forcing str
# ✅ Log length + hash of bytes; decode only on sampled debug
import logging
log = logging.getLogger("edge")
def handle(raw: bytes) -> None:
log.info("recv", extra={"n": len(raw), "sha": hashlib.sha256(raw).hexdigest()[:12]})
# sampled:
# if sample(): log.debug("body=%s", raw[:200].decode("utf-8", "replace"))
Closing checklist
✅ Dos
– ✅ Type hot helpers as bytes → bytes where possible
– ✅ HMAC/JWT signing input = exact request bytes
– ✅ Use JSON libs that accept bytes
– ✅ Decode with explicit encoding + error policy
– ✅ Measure encode/decode CPU in flamegraphs
❌ Don’ts
– ❌ Don’t str(body) on bytes (adds quotes / wrong path)
– ❌ Don’t round-trip bytes → str → bytes for “clarity”
– ❌ Don’t assume UTF-8 for every header or binary blob
– ❌ Don’t build huge intermediate str for multipart file parts
Related reading
- Pydantic v2 Validators: Zero-Copy Paths
- orjson and msgspec
- Zero-Copy Node Streams
- Brotli vs Gzip in Node
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: orjson and msgspec: Serialization Wins for FastAPI Worker Fleets - CheatCoders