Why Basic Tools Stay Hard, and What We Can Do About It
Some technologies feel like they should be simple. Yet people can spend a decade or more wrestling with them. In a keynote at Strange Loop, Julia Evans explored why this happens — and what experienced engineers can do to shorten that learning curve for everyone else.
Evans opened with a confession: DNS took her roughly seven years to feel comfortable with, and even then, she kept hitting the same walls as friends who were just starting out. The frustration wasn't just personal. Watching others struggle with the same confusing systems, over and over, felt redundant and wasteful.
That frustration became her job. Evans runs Wizard Zines, a small publishing company that produces comics and zines demystifying topics like bash, HTTP, SQL, and DNS. Drawing on that work, she walked through what makes each of these areas so tricky — and what practical steps can make them easier.
Bash: When a Language Refuses to Fail
Bash is a programming language with some genuinely unusual behavior. Consider a simple script that moves a file and prints success!. In most languages, an error stops execution. In bash, it doesn't.
A failed mv command quietly lets the script continue, potentially causing bigger problems downstream. Adding set -e at the top of the script makes bash halt on errors — but even that isn't a complete fix. Put the same code inside a function and combine a failing command with || echo "failed", and the || operator has the side effect of disabling set -e for the entire function. The error is silently swallowed, and the script prints "success!" anyway.
This is documented behavior, not a bug. But it's the kind of obscure gotcha that makes bash intimidating — especially for people who only write a script every few months.
The solution isn't memorizing every quirk. It's finding tools that offload that burden.
Enter shellcheck, a linter that knows a staggering amount about what can go wrong in bash. Running it on a problematic script produces a clear warning:
$ shellcheck -o all bad-again.sh In bad-again.sh line 7: f || echo "failed!" ^-- SC2310 (info): This function is invoked in an || condition so set -e will be disabled. Invoke separately if failures should cause the script to exit.
Shellcheck's checks can catch issues that even experienced users miss. Some checks only run with the -o all flag, but the tool's value is in encoding hard-won lessons so that users don't have to carry them all in their heads.
There's a broader lesson here: when you find a tool that reduces cognitive load, share it. Evans described discovering shellcheck late and being "kind of mad" that no one had told her about it sooner. You don't need to build the linter yourself — telling colleagues about it is a form of teaching.
Similarly, sharing the stories behind "best practices" is more useful than reciting the practices themselves. Knowing that set -e can be disabled by an || operator — and which terrible failure that caused someone — is far more meaningful than hearing "bash is dangerous." Different people will draw different conclusions from the same story, and that's fine.
HTTP: The Problem Is the Browser
When a new developer says HTTP is confusing, the initial reaction might be puzzlement. An HTTP response is just a status code, some headers, and a body — a simple structure. What's the problem?
The problem isn't the structure; it's everything the browser does with it. Firefox consists of tens of millions of lines of code, evolving since the 1990s, with countless security modifications over the decades. Explaining HTTP headers properly means explaining the browser's behavior — and the browser is a vast, complicated system.
One workable approach is to shrink the problem space. The complete list of HTTP request headers is enormous; no one can carry that around. Instead, focus on the small subset you actually use. Evans drew a comic presenting 15 request headers, with about a dozen words explaining each one. That's roughly the amount of knowledge she carries for each header — for instance, that setting Accept-Encoding to gzip may return a compressed response. That minimal understanding is usually enough.
This "turn a big list into a small list" trick applies elsewhere too. The full grep man page is overwhelming, even after decades of daily use. More useful is hearing a veteran say: "I've used this for 20 years and here are the seven flags I actually know." One person's shortlist might differ from another's, and comparing those lists is itself educational.
When personal knowledge runs out, good references help. For HTTP, the Mozilla Developer Network is a common stop. A newer authoritative source is RFC 9110 (published 2022, with companion RFCs 9111 through 9114), which reorganizes HTTP specifications cleanly for lookup purposes. Whatever references you share, be honest about which ones you actually use — not the ones that sound impressive on paper.
SQL: Telling the Chronological Story
SQL queries look straightforward: SELECT, FROM, WHERE, GROUP BY. But the written order of a query isn't how the database executes it, and that mismatch confuses new learners.
One effective framing is to ask a simple question: What does the computer do first?
Conceptually, a query starts with the table in FROM, filters rows with WHERE, groups them with GROUP BY, filters the groups with HAVING, selects the final columns with SELECT, sorts with ORDER BY, and applies LIMIT last. Real databases optimize these operations in complex ways, but the chronological model maps well onto comprehension. Everything appears in the order written, with one notable exception: SELECT is fifth.
The chronological approach generalizes beyond SQL. CORS in HTTP becomes far more understandable when you walk through every message exchanged between browser and server, in order. When things are described as "what happens first, then next," complexity loses some of its mystery.
Even experts struggle with the chronology of their own systems. Evans compared a 2013 post about running "Hello World" on Linux with a 2023 update — the newer version was several times longer, not because Linux changed but because her step-by-step knowledge had deepened. Different specialists each know their slice of a system's chronology; assembling those slices together with colleagues is a great learning exercise.
DNS: Making the Invisible Visible
DNS is hard because so much of it is hidden. The system spans a browser, a resolution function, a cache, and an authoritative nameserver — and most of that pipeline is opaque to the user.
You can't easily tell which DNS library your program chose. The cache's contents are invisible and beyond your control. The communication between the cache and the source of truth happens entirely in the background.
One approach is to expose hidden layers deliberately. Evans built Mess With DNS, a tool that lets anyone create records for a real domain while displaying every request as it arrives from resolvers. Watching the actual queries come through — IPv4 and IPv6 lookups from real caches, in real time — reveals the invisible system in action. Similar visualizations exist for other opaque domains, like Bartosz Ciechanowski's float.exposed, which lets users step through floating-point representations bit by bit.
Even the tools meant to illuminate DNS can be off-putting. Consider dig, the standard DNS query utility. Its default output is dense and cryptic to newcomers. One misunderstood feature is the +norecurse flag, which asks a resolver to return only cached answers, verifying whether a domain has been requested recently. The output says SERVFAIL — which actually means "that name isn't cached" — but reads like something far more alarming.
The key habit here is "eraser eyes": ignoring most of a tool's output and focusing on the few fields that matter. Demoing a genuinely confusing tool and showing which parts you pay attention to is a generous act of teaching. Tools like dig are worth the trouble, offering features like +norecurse that alternatives lack — and they haven't changed in years, so learning their quirks pays off for a long time.
Everyone Has a Role
None of these techniques require a blog or a public platform. In any group, different people naturally help others understand complex systems:
- The grumpy old-timer who warns you about every way a thing can fail
- The loud newbie who asks what everyone else is wondering — especially valuable when a senior person takes that role
- The bug chronicler who insists that once something goes wrong, it never happens again
- The tool builder who prefers writing code over giving the same explanation forty times
- The "today I learned" person who shares fresh discoveries and bug stories
- The person with 700 browser tabs open, always able to point you to the right page
- The patient explainer who simply doesn't mind answering questions
These roles compound. Most long-form technical writing builds on conversations with the people above; no one works from a blank slate of personal discovery.
If you're stuck on something that feels like it should be basic, you aren't alone, and the struggle usually traces back to causes beyond personal ability. Sometimes it's a pile of gotchas, or a huge hidden codebase, or an invisible layer of a distributed system, or tool output that no interface designer ever touched. When shared frustrations share root causes, diagnosing those causes makes fixing them possible for everyone.



