You should write bad code

Break things

I cannot overstate how much I love breaking things. If I can’t break something, I don’t really understand it. And if you can’t break something, you don’t know why it doesn’t break.

Most beginner programmers are scared to write bad code. They fear breaking something or introducing bugs, and that fear holds them back. But breaking things is how you learn. It’s how you figure things out.

This isn’t just about code—it applies to anything new. If you avoid breaking things, you avoid real understanding. So don’t let the fear of breaking things stop you. Embrace breaking things—it’s not just okay, it’s an essential part of your growth.

Learn by doing

When I started programming, I didn’t know what I was doing. I just had ideas and the drive to make them real. The internet said to start small, but I didn’t listen. I jumped straight into building a multiplayer game for me and my friends, even though I had no idea how multiplayer networking worked.

I didn’t let that stop me. Experimentation led to hacked-together solutions that somehow functioned. It was bad. The architecture was a mess, and I didn’t understand the implications of poor structure. Every new feature felt like a battle against spaghetti code. Eventually, it all collapsed under its own weight. But I had learned. I hadn’t been afraid to jump in and get my hands dirty.

The same thing happened when I tried building a programming language. I followed Crafting Interpreters and implemented everything step by step. The result was functional, but it was slow, and I knew something wasn’t right. I didn’t truly understand why it worked until I started messing with the code, breaking things, and tweaking things to see what happened. That was when everything clicked. By the end, the code was a mess, but it was exactly what I needed to finally understand why certain design choices worked or didn’t.

Looking back at that old code, I can barely read it. But I don’t regret it. It was necessary. If I’d waited until I fully understood everything, that game would still be a fantasy, and that language would still be a rough draft.

Dive into the unknown

When embedded programming grabbed my interest, I plunged in without hesitation. This new low-level coding felt alien compared to what I knew, but that didn’t matter.

Take the time I built a 3D software rasterizer for a resource-constrained device. The project felt like a fun challenge… until the renderer started running too slow when I added face filling. Meanwhile, my projectile system devoured memory—the device couldn’t handle it

Turns out embedded devices don’t appreciate unoptimized data structures. I had to adjust my approach and make things more efficient. I learned more about memory management than any tutorial could teach.

Breaking that renderer taught me why constraints breed creativity. If I’d been afraid to dive in, I’d never have learned that lesson.

Stop aiming for perfection

Perfectionism is the enemy of progress. I learned this the hard way when I spent countless hours designing data structures that were “perfect” but never used. I never even committed the code because I was obsessed with getting it just right.

You don’t need perfect code—you just need code that works. Start with something, no matter how rough, and improve it as you go. Perfect code that doesn’t exist is worse than bad code that does. So dive in, embrace the journey, and let the mistakes teach you.

You have to write bad code to write good code

Looking back, the pattern is obvious. Every time I’ve jumped into something new, I’ve written bad code. And every time, I’ve learned something that made me a better programmer.

Every skill leap started with bad code. The multiplayer spaghetti, the tangled language interpreter, the memory-crashing renderer.

I could have spent ages designing the perfect version of those projects, but instead, I jumped straight into building them—broken code and all. And each time, I learned something I never would have by waiting.

Bad code isn’t failure—it’s proof that you’re doing the work. Every broken system and messy code is a step toward mastery.

So I’ll keep writing bad code. And you should too.

You can just do things.