Skip to main content

Rebel Against AI and Save your Soul — Learn C!

·13 mins

One crucial aspect of my job as a data engineer is deploying data scientists' Python code to production. Six months ago, this primarily involved reading through it line-by-line, understanding it deeply, and then refactoring it to be production-ready. While it could be fun, this was indeed a relatively rote job. With the utmost respect to my incredibly talented and capable data scientist colleagues: there is good reason for this job to exist. This would usually take a few days to a week.

You know what I’m going to say now. Today, the data science team points me to their code; they provide me a .md document detailing their changes, all generated by Claude, for Claude. I feed the doc to my own instance of Claude with some additional context. It does a pretty good job. I go back-and-forth with it to make sure we aren’t breaking anything, I review the code to make sure it does what it’s supposed to, I button some stuff up by hand, I test it thoroughly, I ship it.

This works. These projects are small and isolated enough to avoid the largest shortcomings of AI code generation: we don’t have to worry too much about security, since these are doing things like generating predictions completely internally to our company; its correctness is fairly easily reviewed and tested; and the overall code is short enough to be reviewable and withstand AI’s infamous verbosity1.

There are some projects that I haven’t let AI handle nearly as much of. Maybe it’s out of a feeling of ownership, commitment to the quality of the project, prioritization of quality over raw speed—whatever. But there are plenty that I have handed over to it, and it has come at a cost: I believe that I have allowed myself to become a worse programmer by using AI. Maybe you, kind reader, feel similarly.

This hurts my soul. Programming is something I love and take pride in; and even though I’m not the best at it, I am (was?) decent enough, and I like to be good at things. But I’ve been losing it. I’ve been trading my appreciation for nice, concise code for “10x productivity”. In a way, it feels like I’ve been selling my soul as a programmer.

Next month, I turn 30. I’ve been feeling that it’s about time to take ownership over my life, and signing away so much of my work—which I value and pride myself on—is the opposite of that. I decided to take back my soul. I didn’t just want to rip out the “ultimate” abstraction of AI; I wanted to rip out every single abstraction I could without risking my sanity. I wanted to struggle like I used to, more than I used to.

In what I like to romanticize as an act of rebellion against our new AI overlords, I downloaded Ghostty, configured vim, and started reading Beej’s Guide to C with the ultimate goal of creating some kind of game using only Raylib. As a result of this ongoing effort, I’m currently writing this post in a Markdown document in vim. It’ll be posted to my static site made with Hugo. I mapped save to F4, and the document is formatted with Prettier on save.

In this moment, I am euphoric. Not because of some phony tech conglomerate’s bloated editor with built-in “Agentic Coding” tools. But because, I am enlightened by my .vimrc file and Tim Pope’s incredible plugins.

Learn C! #

Are you like me? Have you been slinging more code with Claude the past six months than you could ever vomit out in a stream of consciousness fueled by risky levels of caffeine and ibuprofen? Maybe you’re a web developer whose whole career has been fighting with JS, bless your heart. Maybe you’ve heard of a compiler, but have never compiled a program yourself. I’m gonna keep on saying it to drive the point home: go learn C! In 2026! Write it by hand. Go play with pointers and hit some segfaults. Go and miss some semicolons. Go ruin a compiler’s day. Get humbled. You will be better for it. Your soul, as a programmer, will thank you.

The closest I have ever come to writing C before this was in the only CS course I took in college circa 20182 which was taught in C++. I’ve listened to enough of Jonathan Blow rant and rave about C++ that I felt it was just not what I was looking for. I decided that forgoing even std::string was acceptable.

This whole process has shaken cobwebs off of parts of my brain that I didn’t realize were there. Just having to manage pointers has thrown me for a loop more than once. My “capstone project” for learning just the bare basics of C was an implementation of a dynamic array. It’s nothing special, but it’s mine3. Moving from an array of fixed integer type to arbitrary (but homogeneous) type forced me to wrack my brain for at least an hour before writing a single line of code. I haven’t even begun to tackle implementing arrays of arbitrary, heterogeneous types.

Python shields you from exactly these kinds of struggles with its list object. The appreciation I now have for the fact that you can store any type of element in a Python list without thinking runs deep. It truly feels like magic.

Learning to love computers again #

Some folks like DHH are quite stoked on AI particularly because, it seems to me, that they see it as democratizing software; now anybody at any level of programming knowledge can summon custom software into existence. I suppose that for him and others who think similarly, it stokes a greater love of software and the computer.

No matter how much I try, I cannot feel the same way. No matter the level of planning, the amount of iterating, nor the quality of the resulting code, proompting an AI for code just brings me further away from the computer. I’ve loved computers since I was a little kid, and being so disconnected hurts my heart. Please forgive the dramatic, romantic language; I know this all sounds weird and fuzzy, but it’s real to me.

When using AI, I’m not seeing the computer for what it really is. It almost feels abusive to put so many layers of abstraction between myself and what’s really going on, then dictating its form through all those layers. It feels like it’s built on lies when code is supposed to be truth.

To be loved is to be seen. In marvelling at the miracle of a dynamic, heterogeneously typed list in Python and thinking “God, I bet the implementation of this is so complicated,” I really feel that love and appreciation for the computer and the work of those that have made programming into what it is. The Python list is an abstraction, but I see why it is. I mean, you can just .append() a string to a list of floats? Are you kidding me?

I can imagine the ways it’s doing its magic tricks. I don’t deeply understand them, but I have an appreciation for what lies beneath, and that it lies deep, an astounding distance between myself and the chrome. Despite the distance, I feel like I’m seeing the computer more than ever.

Learning abstractions #

I’ve really only begun to develop that game (don’t make fun of it, I’m learning), but most things have actually clicked more quickly for me than I expected them to at first. Most of Raylib’s tools made me think “well duh, of course that’s how that would work.” I got a window up; I drew a square in the window; I got that square to move with WASD; and I got an alarm to sound4 when it collided with another square.

Then I thought: let’s have the player square be able to rotate! After some struggle, I got it to rotate smoothly when I pressed spacebar using DrawRectanglePro(). But this somehow broke collisions. Huh. The player rect would trigger the alarm that rang on collision with the static “enemy” rectangle without actually touching it. Exactly where it would collide changed as I changed the origin of rotation; collision worked fine when rotating at (0, 0) (which puts the origin at its top-left corner), but was all off when I made it instead rotate about its center. And it certainly didn’t work if the square was rotated at all; it’s like the game was ignoring the rotation.

This really stumped me for a bit, because I couldn’t see where I had made a mistake. I banged my head against this for a while. Since I’d just listened to this interview with Harvard’s prolific CS50 professor David J Malan, I decided to use ChatGPT as a sort of “rubber duck” like how they do in the course. I insisted that it just guide me gently to find the issue, and not spoil what was going on. I do think using LLMs in this way to learn early on is valid and super helpful, but you must do it carefully. It did comply with my insistence and nudged me just enough to set a light bulb off.

That light bulb moment is what inspired this whole post.

DrawRectanglePro(), as its name suggests, draws the Rectangle that I give it. It doesn’t fundamentally change the Rectangle. I immediately remembered from reading Beej that functions only ever use copies of the values passed to them. I wasn’t passing a pointer; it couldn’t be modifying the original Rectangle, and it was that original Rectangle variable that I was feeding to the CheckCollisionRecs() function. I also remembered reading that Raylib was designed to pass copies around, hence its focus on having very small objects. It all made sense!

I thought: it’s like the player Rectangle has a “ghost.” Only, ironically, the “real, material” object the computer is using to calculate collisions is the invisible one. Its representation on the screen is the reverse: you can see it, but it’s “immaterial”; it’s a magic trick, it’s a show. There’s a split between the true object and its representation on the screen!

This moment of realization was a rush; so many ideas in my head suddenly became connected. I’ve been hearing about “rendering engines” and “physics engines” for years (product of being a young, internet-addicted gamer). I’ve heard game devs talk about how things are “drawn on screen” and how “collisions are handled”. I’ve burned many hours playing fighting games and have talked about “hitboxes” a lot.

Seeing this “ghost” for what it was felt like seeing through the matrix. There was a thread all the way from pointers to fighting games. I could see “from top to bottom”, if just a sliver of the whole.

My point here is that there are so many of exactly these kinds of things that you never have to worry about in programming. And it’s almost always because a lot of very smart people struggled with them and came up with very good solutions. To rely on them blindly eventually turns them into crutches. To understand them is to stand on the shoulders of giants.

Learning to learn again #

It’s dramatic to say, but this whole experience5, relative to my programming life, has been one of the most important things I’ve done in the past six months, maybe the past few years. I’ve developed a deeper appreciation for the computer. I’ve gotten closer to the chrome than I ever have before. I’ve gotten away from being a Python script kitty. I feel invigorated to build things again. And I just started!

AI tools help me a ton at work and will continue to. While I can still read, understand, write, and debug Python, there’s this active, effortful struggle that has been missing. I can’t be so selfish and prioritize my own understanding and fulfillment at my job. There are deadlines; we have customers to deliver to. Speaking solely about my programming skill6, I need to strike a balance of output with preserving my skills just enough to ensure that output is good, valuable, and sustainable. As sad as it is to say, I have traded my own growth for productivity.

But I’m not just an employee. As much as I love that I can make a living with computers and code and that I’m helping others along the way, programming means more to me than something I do for work. It’s a way to bring order to a chaotic world. It’s a way to express myself in a way that is forced to make sense. It’s a way to be independent using computers, some of the most powerful tools man has created. It’s empowering to program.

Just to be clear: my C code sucks. But it’s mine. I don’t care how thoroughly I prompt, plan, and iterate—the LLM’s code is not mine, even if I’m responsible for it. And I know I’m not the only one that feels this way, and that’s why I feel compelled to share this small experience, despite my shyness.

If you, too, feel like your skills are atrophying, or that you’ve been losing your programming soul, I invite you to join me in taking it back by writing some C. Go read Beej’s Guide; it’s some of the best writing on anything programming-related I’ve read. LLMs aren’t off-limits, they’re super useful—but use them carefully, lest they steal your soul.

So rebel! Rebel against AI and our AI-peddling overlords! Go write, not generate, some C code! Or Rust, or something—I mean, it’s your life. That’s the point.


  1. Your project’s code being 20% longer isn’t so bad when it would be 1,000 lines but is instead 1,200; it’s quite a bit worse when your codebase is ~800k lines and AI bumps it up to a million. Here’s looking at you, bun↩︎

  2. CMPSC 16 at UCSB with the great Ziad Matni, who graciously let me take the course when the CS major was heavily impacted and I wasn’t even in the major. ↩︎

  3. I did use Claude for one significant part of this project: I tried to get it to write a test suite so that I could have a similar experience I had in college, where we were provided tests to run against our solutions. (The quick feedback back then was awesome!) It was unbelievably difficult to do this. Instead of writing tests against intended behavior that I outlined with a thorough prompt (I thought), seemingly all it could do was write tests around what I’d written. When I was not even halfway done implementing the array, my implementation passed the generated suite 100%. I instantly became suspicious, and maybe ashamed, of every last AI-generated test I’d ever prompted into existence. I eventually got it to create something OK, but I mostly gave up on this effort and won’t be doing it again. ↩︎

  4. I admit that the callback function to generate the alarm sound, DumbAlarmCallback(), was generated with Claude. I originally had a sine wave-generating function copied from the docs, but decided I wanted something a little more entertaining for testing without going down the rabbit hole of how to implement it. Upon seeing the change it made to accomplish it, I felt a little stupid and like my younger, physics-major self would have facepalmed at the fact that I didn’t know how to do it off the bat. I mean, maybe. I was awful at physics. ↩︎

  5. Which occurred, according to my git commit history across the dynamic list and game repos, between May 11th and May 15th. But I had been reading Beej’s for maybe a week before putting “pen to paper”. ↩︎

  6. Everybody who has worked a “programming job”, from data analyst to software engineering, knows that programming is far from the only thing involved. ↩︎