Your Code Is a Rough Draft

Nobody publishes their first attempt at anything worth keeping. Writing bad code on the way to good code isn't a failure of skill — it's the actual method, and treating it like a personal failing is what stalls people out.
There's a particular kind of dread that shows up right before you write the first line of a new function. Not "I don't know how to solve this", which is an honest and useful kind of stuck, but more like, "whatever I write first needs to already be correct". Which is itself a much quieter, much more damaging kind of stuck, because it doesn't feel like fear. It feels like standards.
I want to make the case — as directly and humanely as I can — that the standard is wrong; that the fear is doing nothing useful for you, and the actual craft of this job lives almost entirely on the other side of being willing to write something bad on purpose…on the way to writing something good.
First Drafts Are Supposed To Be Bad
An old line that I'm sure you've all heard in a college English or writing class that most attribute to Hemingway, is that the first draft of anything is garbage. Whether or not he actually said it in those words, every working writer I've ever known or talked to agrees w/the sentiment completely, and none of them find it at all discouraging.
A first draft isn't a failed a failed final draft. It's a different object, w/a different job: get the shape of the thing onto the page so there's something to react to, revise, and improve upon. Nobody hands a publisher their first pass, that would be insane. The first pass is not the product. It's the raw material the product gets carved out of, like a block of marble. Michelangelo's David wasn't just birthed from one crack at the block; same as your published work is never just a product of unedited word vomit.
Code deserves exactly the same permission, and almost never gets it in the real world, even through a vicious PR comment battle for change requests (it happened recently and I'm still salty about it). Somewhere along the way, a lot of us absorbed the idea that the first version of a function is supposed to already resemble the version that ships — properly named, abstracted, edge cases handled, no dead ends…the works — and that anything less is evidence we haven't fully understood the problem yet. That's backwards. You don't fully understand the problem until you've written something and watched it fail in a specific, informative kind of way. The rough draft isn't what you write once you understand the problem. It's how you get there.
I write ugly first passes constantly, on purpose, and have for years. I take a page out of the great Stephen King's "On Writing" book where he talks about (paraphrasing here), just start writing and get as much of your single stream of thoughts out as you can, and then only after it's all on paper, do you go back and edit and cleanup your mess.
Throwaway variable names like $tmp and data2 are poor examples, but you get the idea. Hardcoded values where a real function argument will eventually go. No error-handling, no edge cases, sometimes not even correct logic — just enough to prove the shape of an approach before I spend any real effort making it presentable. This isn't 'sloppiness'. It's sequencing. Getting the logic out of the brain and flowing matters more (at that stage), than getting the logic pretty, and conflating the two is exactly what turns a 5 minute function into a 40+ minute stall in front of a blinking cursor.
The difference between a newer developer and a more experienced one, in my experience, isn't that the experienced one writes less bad code. It's that the experienced one has stopped being surprised or embarrassed by the process of it. Early on, every line feels like a permanent commitment; something that will be read, judged, and held against you. With enough time in the job though, you start treating early code as scaffolding — necessary, temporary, and entirely expected to come down once the real structure is standing. That shift in attitude is a skill, not a personality trait some people are lucky enough to have, and it's one you can deliberately practice.
And once you've actually given yourself that permission, once the first draft is allowed to be rough, something quietly changes about how bugs and errors in your code feel too. A 'bug' in a rough draft isn't a crisis. It's just the draft, doing exactly what drafts do.
The Bug Is the Lesson, Not the Failure
There's a very specific sting that comes w/a bug you didn't expect — a small drop in the stomach, a flash of "how did I not see that", that has almost nothing to do w/the actual severity of the mistake and everything to do w/what we've decided the bug means about us. Treated as a verdict on your competence, a bug is demoralizing. Treated as information — which is all it actually is — a bug just the language telling you something true about itself that no tutorial managed to make stick the first time.
I've written before about how deceptively ordered asynchronous JavaScript can look on the page, and almost everyone who's chased a stray setTimeout() bug or a promise that resolved in the wrong order has learned the event loop's actual behavior that way (through the bug) far more permanently than through any diagram. You can read an explanation of micro and macro tasks a dozen times and nod along. You tend to remember it forever the first time your own code prints things in an order you swore was impossible, and you're forced to actually trace the mechanism yourself to find out why.
The programming language Perl hands out its own version of this lesson constantly; usually through context. Perl famously evaluates the same expression differently depending on whether it's used in list context or scalar context, and the first time this silently changes what a piece of your own code does — an array quietly collapsing into its length instead of its contents, for example — it feels like the language betrayed you. It didn't. It told you, precisely and consistently, exactly how it works. You just hadn't been forced to actually learn that mechanism yet, because nothing had gone wrong to make you look. The same is true of forgetting use strict and watching a typo'd variable name fail silently instead of raising an error. An infuriating 5 minutes that teaches you more about Perl's scoping than a week of reading documentation would have, precisely because you were forced to go looking for the actual answer instead of passively receiving it.
That's the deeper claim worth sitting with here: debugging is the process of being forced to build an accurate mental model of the runtime; your current, wrong mental model is exactly what produced the bug in front of you. A tutorial can't replicate that pressure.
Tutorials show you the happy path, by design, because that's what makes them legible as tutorials. Bugs show you the actual edges of the system, at the exact moment you're motivated enough to go find out where they are.
None of this means bugs feel good to deal with in the moment, and I'm not going to pretend a production incident at 11pm is secretly a gift. But the ordinary, everyday bugs — the ones that show up while you're building something new, in code nobody's depending on yet — are close to the best curriculum available to you, delivered at the exact moment you're ready to learn that specific lesson (for free), in the shape of your own actual problem instead of someone else's contrived series of examples.
Depth Beats Breadth: Why I'd Rather Know Two Languages Well
There's a constant, low hum of pressure in this industry to know a little about everything. Another framework, another language, another tool, added to the pile mostly out of anxiety that you'll be caught not knowing it or you'll "fall behind" in your area of the tech field. I've made a different bet, deliberately. I'd rather go deep in a small toolkit than stay shallow across a large one; and it's paid off in a way that directly supports everything in this piece so far.
The payoff of real depth is specifically the thing this whole piece has been building toward: the confidence to write bad code fearlessly comes from knowing you can reason your way back out of a mess, and that confidence only shows up once you actually understand a language's real model — not its syntax, its model. Shallow familiarity w/10 languages doesn't give you that safety net in any one of them. You can write a rough draft fearlessly in Perl because you've been wrong about Perl's context rules enough times to know exactly how to go check. You can't extend that same fearlessness to a language you've only ever followed tutorials in, because you have no accumulated intuition for what its failure modes actually look like.
Depth also compounds differently than breadth does, in a way that's easy to underestimate. Fully understanding why Perl's variable scoping works the way it does — lexical scope, package variables, the specific danger that use strict protects you from — sharpens how you reason about closures and scope in JavaScript, because you're now thinking about the underlying concept, not just local syntax. A deep lesson in one language tends to transfer as a genuine mental model. A shallow syntax lesson in 10 languages mostly just sits there, inert, disconnected from anything else you know; ready to be forgotten the next time you don't touch that language for 6 months.
The Importance of "The T Method"
My stack, built over years of learning what works and what doesn't, what I love and hate to use/program with, is intentionally small. And, I'm not the only senior engineer to find out that this is (objectively) one of the best ways to carry-on your programmer journey. I've met and worked with a wide range of different types of coders throughout my career, and the main mental process I've seen most of these amazing people land on is, that it is much better to focus on a couple of things you love to work with, than a hundred things you tolerate. My personal stack, my "main go-to's", are these things below that I consider essential for my process as a programmer:
JavaScript — as vanilla as possible, and as functional as possible (
FP > OOP)React & JSX — the single best library for anything that touches the browser
Sass / SCSS — for when you get sick of Tailwind, and want to touch real CSS again, efficiently
Perl & C — for anything software, text/files, systems, or the space between other programs
PERN Stack — for when you want to build clean, sophisticated full-stack apps/software
This is truly most of what I've found I ever actually need to create and maintain good, production-ready software or web apps. You'll find that everyone's personal favorite tech stack is different, at least to one degree or another, and that's okay! But also what that means, is in the world of programming, there's typically always more than 1 way to squash a bug.
None of this is an argument against curiosity — it's an argument about sequencing, as I mentioned. Get fluent enough in your chosen tools that you can be genuinely fearless inside them, rough drafts and all, and let curiosity pull you toward the next language only once that foundation is solid. Rather than frantically sampling every 'flavor-of-the-week' at once because standing still, even briefly, feels like falling behind. The 2 languages I know well didn't get chosen because they're trendy. They got chosen because they were enough and answered mostly all of the problems that required answering, and depth in "enough" beats breadth in "impressive" every time I've actually tested it against a real deadline.
Revision Is Where the Actual Craft Happens
If the rough draft's job is to just exist, the real craft of this work lives almost entirely in what happens next: revision. This is where the manuscript metaphor holds up completely. A first draft gets the ideas down. Editing is where the actual writing happens — cutting, reordering, clarifying, killing sentences that seemed clever at 11pm and read as nonsense the next morning.
Code follows the identical arc, and pretending otherwise is what makes people try to skip straight to a polished result they haven't earned yet. I've seen it happen too many times, but it truly is a great teaching lesson for the right senior engineer who actually cares to teach. (Unfortunately, most do not. I am not of that class, I thoroughly enjoy teaching someone something, and watching the lightbulb click on in their head as they realize the lesson.)
Here's what that revision pass actually looks like on real code: the first draft solves the problem w/a nested loop, some mutable state you're tracking by hand, and a chunk of logic copy/pasted from a few lines up because it was faster than thinking of the right abstraction in the moment. It works, for sure. It's also (structurally) a total mess. The revision pass is where that mess turns into something worth keeping — pulling the repeated logic into a named, pure function and replacing the mutable tracking variable w/a clean fold or reduce; giving things names that describe what they are instead of what they happened to be called while you were still figuring it out. This is the pure-functions-and-no-unnecessary-abstraction philosophy I actually believe in, applied honestly to good code architecture: as a process you arrive at through revision; not some purity test you're supposed to pass on your very first attempt.
This ordering matters more than it looks like it should. Trying to write the clean, properly abstracted, pure-function version on your first attempt (before you've even seen the real shape of the problem) is a common, specific cause of the exact paralysis this piece opened with. You cannot correctly abstract a problem you don't yet understand concretely. The ugly draft is how you learn the problem's actual shape, edge cases and all, which is a prerequisite for a good abstraction, not a shortcut you're allowed to skip past if you're clever enough.
Revision is also where a second, quieter round of self-education happens — going back over your own rough draft w/a few hours or a few months of distance, or reading someone else's code review comments on it, tends to teach you more than most books do, because it's a lesson calibrated precisely to your own actual blind spots rather than a generic curriculum written for an average reader who isn't you. The draft's entire purpose is to survive long enough to be revised. If you're withholding it until it's already good, you've swiftly skipped the stage where most of the real learning was ever going to happen.
Self-Education Is a Practice, Not a Panic Response
There's a version of "always be learning" that's driven entirely by anxiety — the treadmill feeling of a finish line that keeps moving, chasing whatever's newest because standing still feels like falling behind. That's not what I'm describing here, and I think the difference matters. The self-education worth actually doing is calm, chosen, and pointed inward at the tools you've already committed to rather than outward at whatever's trending this quarter. It's curiosity about your own languages, not fear of missing out on someone else's.
In practice, that looks like small, genuinely low-stakes habits: writing a throwaway script for no reason other than to deliberately break something and see what happens. A Perl one-liner built specifically to abuse context sensitivity on purpose, just to watch the failure mode up close instead of stumbling into it later under deadline pressure. A tiny JavaScript function snippet designed to make the event loop misbehave in exactly the way you already half-understand, just to confirm the mechanism instead of taking it onfaith. None of this shops anywhere. All of it teaches you something a tutorial's happy path never will, because you chose the failure on purpose instead of merely surviving it by accident.
Some of the best self-education I've done has had nothing to do w/a course or a book, and everything to do w/reading the actual source of truth for its own sake — a stretch of perldoc, an ECMAScript spec section, a V8 blog post about how a specific optimization works — not because a ticket required it, but because understanding the real mechanism underneath a tool I already use daily is its own quiet reward. This is, admittedly, a somewhat obsessive way to spend an evening. It's also the actual reason I know these two languages as well as I do, and it cost nothing but attention I was going to spend somewhere anyway.
Give yourself permission, explicitly, to remain a beginner at some corner of a language you've used for a decade. I still learn genuinely new things about JS and Perl on a regular basis, as it should be. And after fourteen years of programming professionally, it no longer bothers me even slightly, in fact it's one of the best parts! Because I've stopped treating unfamiliarity as a debt I owe and started treating it as a relationship w/my own tools that was never supposed to end. That's not a gap in expertise. It's what an ongoing relationship w/a language actually looks like, for anyone honest enough to admit outloud.
Rough drafts, survivable bugs, a deliberately close and manageable toolkit, patient revision, and calm, chosen curiosity are — underneath it all — the same habit wearing 5 different outfits: the willingness to be visibly unfinished in your work, on the way to being genuinely good at something.
Your code is a rough draft. So was mine, over a decade ago, and so is most of what I write to this day before I'm done w/it. The only real difference is I stopped being afraid of that sentence, and started treating it as a key component for the job description. Enjoy it, and let it be a part of your process.
// EOF




