+++
title = "Code is Cheap(er)"
description = """\
  In this essay, Carson Gross argues that as AI makes code cheap to produce, understanding code becomes the \
  expensive and scarce resource. He warns of the complexity that LLM code can generate and \
  proposes the subtractive, constraining engineer as the discipline needed to keep systems comprehensible & stable."""
date = 2026-06-04
updated = 2026-06-04
authors = ["Carson Gross"]
[taxonomies]
tag = ["posts"]
+++

There is no getting around the fact that, in the last year, code has gotten much cheaper to create.  AI is able to generate
reams and reams of code, often of reasonably decent quality, incredibly quickly.  There is no point in pretending that
this isn't the case.

At times, when confronted with this admittedly uncomfortable fact, I have seen people I respect say something like 
"coding was never the problem."  

While I appreciate the sentiment, I don't completely agree with that: certainly coding was at least *part* of the problem.

And that part of the problem has shrunk significantly with the advent of effective AI coding tools.

So what does raw coding becoming less important mean for software developers, people who, in the past, prided themselves
(and often compared themselves) on their ability to code?

## Understanding is Expensive(er)

One thing I see is that it means that *understanding* code has become more expensive.  This is because when reams 
and reams of code are generated, rather than emerging painfully from a particular programmer's fingers, there *is* no 
understanding of that code.

In as much as understanding that code needs to exist, it has to be done after the code is written, by reading the code.

Note that conventional wisdom is that reading someone else's code is harder than writing your own code.

Some AI enthusiasts say "Who cares, you don't understand the output of compilers."  

I think that is a category error for multiple reasons:

* Compilers are deterministic; LLMs are, by design, not
* Compiler workflows retain their original source code; LLM workflows typically do not
* Compiler output is to a narrowly constrained domain (machine code); LLM output is not (generalized software)

I maintain that, in most cases and certainly for mission-critical software, developers still need to understand the
underlying code even if it is generated by an LLM.

And if code *is* generated by an LLM there is a stark danger: the LLM can produce code far faster than you, or anyone else,
can understand it.  This is why I recommend incremental use of LLMs rather than allowing them to generate massive changelists
that neither you, nor anyone else, can understand.

(There are times when this can be appropriate, such as in a mechanical refactor, but it is extremely dangerous when new
semantics are being introduced into a code base.)

## The Sorcerer's Apprentice Trap

One movie scene that has been consistently coming back to me as I have watched AI garner more and more attention
is [The Sorcerer's Apprentice](https://video.disney.com/watch/sorcerer-s-apprentice-fantasia-4ea9ebc01a74ea59a5867853)
from Disney's movie *Fantasia*.

In this scene the apprentice decides to use magic to assist in the drudgery of cleaning.  He enchants a broom which
then proceeds to start cleaning things up.  Things appear to be going swimmingly for a while, until the broom starts
cleaning more and more vigorously, reaching a point where things start going swimmingly literally.

The chaos is resolved when the Sorcerer reappears and asserts control over the situation, glaring at the apprentice for
his foolishness.

This seems like an apt metaphor for the AI era: you want to be a sorcerer and not an apprentice.  

And a sorcerer has to understand the code.

## Complexity: Still Bad

Humans, generally, have a poor grasp of geometric and exponential curves.  

(This is why they believe in fairy tales such as compound interest.)

The core danger of code being cheap is [complexity](https://grugbrain.dev), which I assert, without proof, tends to grow
at least geometrically and often exponentially with the size of a system.

Before LLMs there were prolific human coders.  

Perhaps you have worked with one: they can write _a lot_ of code.

I have seen prolific coders who lack a proper fear of complexity heap more and more code on top of an existing problem 
until the whole system collapses into an unmodifiable steady state, where any change creates as many bugs as it fixes.

LLMs are incapable of fear of complexity, and are prolific coders.

Seems dangerous to me.

## The Subtractive, Constraining Engineer

To address this danger of LLM-generated code, I propose the subtractive, constraining engineer:

This engineer [says no](https://grugbrain.dev/#grug-on-saying-no), closely examines LLM output, suggests simplifications and 
generally retains a firm hand when dealing with LLM-generated code.

Rather than priding themselves on the code they create, they pride themselves on the code (and layers) they _remove_ from
or prevent from entering systems.

This ethos is more sculptor and less builder.

Where the builder ethos still applies, to an extent, is at the system design level: a good engineer will need to know
how to compose components effectively to create systems.  However, even here, I think that the subtractive mindset will
be useful: removing unnecessary components and system boundaries to simplify system deployment and inter-component interactions,
etc.

The subtractive engineer is a different kind of engineer than most coders have been in the past.  I will admit that I 
have always been sympathetic to the subtractive engineer mindset: I don't mind saying no, I don't mind polishing existing
systems rather than heroic rewrites, etc.

But, admitting my own biases, I believe this approach is a productive way to engage with LLMs that retains the art of 
computer programming and properly acknowledges a dual reality: code has gotten much cheaper to create and complexity 
remains our apex predator.