Here's a problem Netflix faced: tokens on client devices kept growing.
Each ad needed tracking URLs, metadata, vendor info. As they added more partners, tokens got fatter. Low-end TVs started struggling with memory.
The solution? Indirection.
The old approach stuffed everything into the token. Tracking URLs for three vendors, full metadata with title and duration and billing info, vendor-specific data blobs. A single token could hit 2KB easily.
The new approach? Ship a 50-byte token with just an ad ID and a metadata reference. When the device actually needs the full data, it looks it up from a registry.
Token dropped from 2KB to 50 bytes. Low-end TVs happy again.
The core idea is simple: instead of sending all the data upfront, send a small reference. When the data is actually needed, look it up. You're trading bandwidth now for a lookup later—and usually that's a great trade.
Message queues: Don't put large payloads in Kafka. Put a reference.
Database design: Instead of embedding everything, store an ID and join when needed.
Caching: The cache key itself is indirection. A tiny 8-byte key points to a 50KB value.
URLs: A short URL is pure indirection. bit.ly/abc123 points to a full URL with dozens of parameters.
This pattern shines when client devices have limited resources, when message sizes are exploding, when network bandwidth is precious, or when you need to update data without resending everything.
The trade-off is straightforward: direct data transfer gives you simplicity and low latency, but high bandwidth usage and tight coupling. If you need to change something, you resend everything.
Indirection gives you low bandwidth and loose coupling. Need to update the metadata? Just update the registry—every device gets the new data on their next lookup. But you pay with an extra hop (slightly higher latency) and more moving parts.
You're trading bandwidth for latency and size for complexity. Usually worth it when the data is large and changes frequently.
"All problems in computer science can be solved by another level of indirection."
And the corollary:
"...except for the problem of too many levels of indirection."
Know when to add it. Know when to stop.
One level of indirection solves your problem. Two creates a new one.
— blanho
Most devs treat payments like CRUD. Then money disappears.
UUIDs aren't always the answer. Here's when they hurt more than help.
Synchronous calls work until they don't. Then you need a message queue. Here's why.
// Before: 2KB token
{
"trackingUrls": [
"https://vendor1.com/track?id=abc&campaign=xyz&...",
"https://vendor2.com/track?id=def&campaign=xyz&...",
"https://vendor3.com/track?id=ghi&campaign=xyz&..."
],
"metadata": {
"title": "Product Ad",
"duration": 30,
"vendor": "...",
"billing": "..."
},
"vendorData": { "more": "stuff", "lots": "of it" }
}
// After: 50 bytes
{
"adId": "123",
"metadataRef": "abc-456"
}# Bad: 10MB payload in Kafka
producer.send("topic", large_image_bytes)
# Good: Reference to S3
producer.send("topic", {"s3_key": "images/abc123.jpg"})cache.get("user:123") # Returns full user object-- Bad: Denormalized blob
SELECT * FROM orders WHERE data->>'customer_name' = 'John'
-- Good: Indirection via ID
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE c.name = 'John'