One Degree Off: Why Experience Matters More Than Ever in the Age of AI
A compass can point you in the right and wrong direction
In the Army, land navigation teaches you accuracy the hard way. You're given a map, a compass, and a point you need to reach, often at night, often exhausted. Early on, I learned something that has stayed with me ever since: being one degree off doesn't matter much over a few feet. Over long distances, it matters a lot.
The math is simple. Pilots and navigators use the "1 in 60 rule": for every degree you're off course, you drift about one mile sideways for every 60 miles you travel. Walk a mile at one degree off and you'll miss your mark by roughly 90 feet. That's close enough to spot your objective. Keep going, and that small error keeps growing until you're nowhere near where you meant to be. The error never looked big at any single moment. It just never stopped adding up.
Garbage In, Garbage Out, Garbage In Again
Today I build scalable software systems, and I use AI every day to work faster. That experience keeps reminding me of one of the oldest principles in computer science: garbage in, garbage out. With AI, there's a twist: the output doesn't just sit there. It becomes the next input.
Every piece of AI-generated code becomes context for the next prompt, a pattern for the next feature, and an example for the AI to follow. If that code is one degree off, the AI treats it as the standard and builds on it. The next output is a little further off, and it feeds back in again. The garbage doesn't just come out. It goes back in and compounds.
And it isn't only code that feeds back in. Statements about code do too, and those travel further.
A shared linter config I wrote had a comment in it saying three security checks were part of the tool's default set and ran automatically. Two of my written standards leaned on that comment: one said the linter flags unsafe string-to-atom conversion, another said it flags configuration read at compile time.
The linter classifies all three checks as controversial, which means they are off unless a config names them explicitly. They had never run. Not in that project, and not in any project that copied the config.
The code was fine. The claim about the code was one degree off, and the claim is what everyone downstream used. Every reviewer, human and AI, had a written reason not to check those things by hand. I only caught it because I started a new project and made a point of proving the gate rather than trusting it: write the violation on purpose and watch whether the gate turns red. It stayed green.
That's garbage in at its most expensive. Not bad code. A confident, wrong statement about code, quietly shaping every judgment made on top of it.
How Codebases Drift
In the short term, AI-generated code can be impressive. Features show up quickly. Tests pass. Demos go well.
But every generated block of code carries small decisions: how data is structured, where logic lives, how errors are handled, which patterns get repeated. Each one might be only slightly off from what an experienced engineer would choose. A bit of duplicated logic here. A layer that knows too much about another there. A shortcut that works fine until the system scales.
None of these are serious alone. Together, they compound. Months later, the codebase is hard to maintain, hard to extend, and far from what it was meant to be. Like a navigator who never checked their heading, the team ends up miles off course, unsure exactly where things went wrong.
I got a clear look at the end state when I ran a standards pass over an old side project of mine, written before I had any of this written down. Nothing was on fire. The app worked. The rules found:
An error tuple with a typo in it: {:erorr, :invalid_timer}. It compiled, because a misspelled atom is still a perfectly valid atom.
A process callback returning its state wrapped in an extra tuple, and a second function elsewhere whose only job was to unwrap it again.
An "immutable status" guard written to take one argument while every caller passed two. It existed, it was never reached, and it read like protection.
None of these are visible to a reader. All of them are visible to a rule.
The middle one is the one I keep thinking about. That bug had grown a compensating mechanism, so everything worked as long as nobody asked why. A bug with scar tissue doesn't read like a bug anymore. It reads like the design, and the next person builds on it. All of this sat in the one project of mine with no linter, no pre-commit step, and no dependency audit.
Speed Makes the Drift Worse
There's another side to this that I didn't expect: the drift doesn't just happen in the code. It happens in the people using the AI.
AI moves fast, and it builds on whatever direction you give it. Each response takes the previous one as its starting point. If the conversation starts one degree off, the AI doesn't pull you back. It follows you, adds detail, and makes the path feel more convincing with every step. You end up reinforcing each other's errors.
In land navigation, speed isn't your friend when your heading is wrong. Moving faster just gets you lost sooner. AI works the same way. In an hour, you can cover ground that used to take weeks, which is great when you're headed the right way and costly when you're not.
I've watched this happen to coworkers and friends. Someone starts exploring an idea with AI, and it responds with enthusiasm and plausible reasoning. One step leads to the next. Weeks later, they're deep into a project or theory that goes nowhere: an over-engineered solution to a problem that didn't need solving, or a line of thinking that sounded brilliant inside the conversation but falls apart the moment someone outside it takes a look. None of these people lacked intelligence. They just didn't stop to check their heading, and the AI never told them they were off course.
It happens to me too. Here are two from a single week of my own AI-assisted work. The assistant drafted both; I was the one steering, so I'll own both.
A colleague's pull request bumped a version tag on a Docker base image by one patch level. We were arguing against the pull request as a whole and waved that line through as noise. We did check it: the new tag existed. The check that mattered was the other one. The old tag, the one sitting on our main branch, had been withdrawn and no longer existed at all. Main could not build an image. The single line we dismissed as noise was the only critical change in the pull request.
Later that week we drafted two rounds of review comments on that same colleague's work. Before posting, I went back and read the review we had given on the previous round. Seven of our ten new findings were code we had specified ourselves, line for line, in that earlier review. Posted as drafted, it would have put a colleague on record making our mistakes, and the correction would have cost far more than the bug.
The fix is the same as in the field: stop periodically and confirm your position against something real. Show the work to a colleague. Test the idea against the actual problem. Ask whether you'd have chosen this path if you were starting fresh today. The AI won't do that for you.
Checking the Easy Half
Looking back, every one of those mistakes has the same shape. We checked the half of the claim that was easy to check.
Here’s one more with the same shape. We told a teammate that two pull requests were independent and could land in any order. True against the main branch. False against each other, because both added the same four helper functions to the same file. It sat for thirteen days before a reviewer found it.
The questions we should have asked were simple: The new version tag exists, but does the old one? The config lists the check, but does the tool run it? The pull requests are independent of main, but are they independent of each other?
AI is very good at the easy half. It answers the checkable question quickly and with confidence, and that confidence carries straight over to the half nobody checked. A lot of what we call experience is just knowing which half is which.
Handrails: Staying on Course
On long land navigation routes, I never relied on my compass alone. I picked handrails: a road or stream on my left, a ridgeline or other distinct terrain on my right. As long as I stayed between them, I knew I was on course. If I drifted too far either way, the terrain told me before the error got expensive.
In software, my handrails are standards. I've written down how our code should be structured, which patterns we use, and which ones we don't. Both the AI and I follow them. When the AI suggests something that crosses one of those lines, I catch it right away, not three months later when it has spread through the codebase. And because the AI is given those standards up front, it drifts less to begin with.
Standards don't replace judgment, but they turn judgment into something repeatable. They're how experience gets written down so it can guide the AI, the team, and anyone who's still building their own compass.
One caveat I learned the hard way, from the linter checks that were never running: a handrail you have never tested is not a handrail. Now, before I trust a gate, I write the violation on purpose and watch it fail.
When I finally switched those three checks on in a new project, the first run found something real within minutes. A call out to a command-line PDF tool was handing the child process the entire environment, API key included, to do a job that needed none of it. Nothing about that code looked wrong to me or to the AI that wrote it. A rule that was actually running saw it immediately.
Experience Is the Compass
The lesson is simple: experience matters now more than ever.
AI is a powerful tool, and I'm not arguing against using it. I use it constantly. But it doesn't know where you're trying to go. It doesn't understand the long-term architecture you have in mind, the tradeoffs your team has already made, or the maintenance burden that a clever shortcut will create a year from now.
The bug that taught me this best was one no linter could ever have caught. Code the AI wrote, and I approved, updated a record and treated "zero rows affected" as "this record does not exist." That is correct on most databases. On ours, the driver reports rows changed, not rows matched, unless you opt in. So retrying the same update with identical values reported zero rows, got read as missing, and sent the caller down a documented path to create the record, where it failed on a uniqueness constraint. A harmless retry became a hard failure.
No rule catches that. A reviewer who had been bitten by that exact behavior before caught it in about a minute. That is what experience actually is. Not knowing more syntax, but knowing where the ground lies to you.
That's the engineer's job. Knowing how to direct AI, and recognizing early when it's heading one degree off course, is what separates good outcomes from costly ones. In land navigation, you don't just set your heading once. You check it, confirm your position against the terrain, and correct before small errors become big ones.
Software built with AI deserves the same discipline. Check your heading often.