What Your Code and Kids’ Drawings Have in Common
February 2, 2026
Most unreadable code isn’t broken. It works — but it’s tiring to understand.
You start reading a function to learn what it does. Halfway through, you’re reasoning about how it does it. Your brain keeps switching gears.
Kids make the same mistake when they draw people.

A child draws a person with an enormous head and carefully counted fingers… but the body is vague or missing.
It’s one picture built from mismatched layers:
- the idea (a person)
- what feels important (face, hands)
- execution detail (individual fingers)
Some parts get intense detail. Other parts are barely sketched. The drawing represents one thing, but it’s assembled from incompatible zoom levels.
Code does this constantly.
BAD: mixed abstractions
function processOrder(order) {
if (!order) throw new Error("Missing order");
// business rule
const total = order.items.reduce((sum, i) => sum + i.price, 0);
// formatting detail
const formattedDate = new Date().toISOString();
// infrastructure concern
try {
http.post("/orders", { ...order, total, formattedDate });
} catch {
retryWithBackoff();
}
}
This function is about processing an order — but it also worries about validation, formatting, networking, and retries. Each line makes sense; together they force the reader to constantly change mental mode.
GOOD: consistent abstraction
function processOrder(order) {
const payload = prepareOrder(order);
sendOrder(payload);
}
function prepareOrder(order) {
// validation, totals, domain rules
}
function sendOrder(payload) {
// HTTP, retries, timeouts
}
Now each function mostly speaks one “dialect.” You can understand the intent without being forced into the machinery.
How to spot mixed abstraction (fast)
You’re probably mixing layers when you see any of these:
1) “And then suddenly…” lines
While reading, you feel a gear shift: “Ok we’re processing an order… and then suddenly we’re constructing headers.” That “suddenly” is the smell.
2) Domain words and plumbing words in the same breath
Names from different worlds colliding:
order,invoice,policy,eligibilitynext tohttp,sql,json,headers,uuid,retry,timeout
When they’re interleaved, you’re doing two jobs at once.
3) Uneven detail
One part is very high-level (“process”, “sync”, “handle”), but another part is meticulous (“format ISO string”, “map status codes”, “loop indices”). That mismatch is exactly the kids-drawing problem: giant head, missing torso.
How to avoid it without ceremony
A) Do a “story pass” and a “mechanics pass”
Write (or refactor) in two passes:
- First pass: make the function read like a story of intent.
- Second pass: move the gritty details into helpers that have boring, specific names.
You’re not adding abstraction — you’re relocating detail.
B) Create explicit boundaries
A simple, durable boundary is:
- Orchestrator (high-level): “what happens next”
- Workers (low-level): “how this specific thing is done”
No architecture astronaut badge required.
C) Name functions by what they promise at their level
Good names act like a zoom lock:
prepareOrderPayloadsendOrderRequestcalculateTotal
Names that mix levels (processAndSendOrderWithRetries) are often confessions.
Good code improves the same way kids’ drawings do — not by adding detail, but by putting the right detail in the right place.