AI ships fast, I stopped asking if I still understand my own code
AI ships fast, I stopped asking if I still understand my own code
I ship AI-assisted code fast right now, on a deadline. Fast enough that I don’t stop to make the assistant explain itself. I take the diff, if it works I ship it, I move on. And somewhere in there the imposter syndrome creeps in: quick, sure. The low-level stuff, the actual how-it-works? I’m not learning it.
On weekends I do the opposite, on a side project I build for fun. Slow sessions, by hand, real math included, genuinely satisfying. But when I looked closely at the code the AI and I had written there, I noticed the imposter feeling isn’t one thing. It’s at least three.
1. No imposter syndrome — depth I already had, just in a different language
loop { tokio::select! { cmd = self.rx.recv() => { let Some(cmd) = cmd else { break }; // inbox closed, nothing left to do self.handle(cmd); } _ = wander.tick() => self.wander_step(), }}I’ve done this exact shape before, in C, matching on whatever async event came in first and
dispatching off it. The hard part of this whole block is three lines: the loop, the select!,
the channel recv arm. The rest is just workflow. Ten years of backend work means a chunk of what
AI writes isn’t actually new to me, it’s a known pattern wearing a different syntax. No imposter
syndrome here, because there’s nothing to be an imposter about. Kind of funny, actually: ten years
in and the AI-written code that worries me least is the one doing the least new stuff.
2. Real imposter syndrome — the AI wrote it for itself, not for me
fn next_wander_rng(&mut self) -> u64 { let mut x = self.wander_rng; x ^= x << 13; x ^= x >> 7; x ^= x << 17; self.wander_rng = x; x}fn best_ucb_child(arena: &[Node], parent: usize, c: f32) -> usize { let n_parent = arena[parent].visits.max(1) as f32; let ln_n = n_parent.ln(); let mut best = arena[parent].children[0]; let mut best_score = f32::NEG_INFINITY; for &child in &arena[parent].children { let n = arena[child].visits.max(1) as f32; let score = mean_value(&arena[child]) + c * (ln_n / n).sqrt(); if score > best_score { best_score = score; best = child; } } best}A hand-rolled xorshift step, and a UCB1 child-selection formula for a tree search. And here’s the
honest part: I’ve written xorshift before. I’ve done math like this before. This isn’t foreign
territory. But glance at mean_value(&arena[child]) + c * (ln_n / n).sqrt() and understand it in a
few seconds? I can’t. Not for lack of background — because the AI wrote it the way that’s natural to
it, not to me: dense, correct, and never laid out for a human to read at a glance. I could get
there. I’d just need to sit with it and give it the time. That’s the real gap — not that this is new
to me, but that at AI speed I stopped spending the time to make it legible to myself. I told myself
I’d go back and work it out by hand. That happens maybe one time in ten. The other nine, another bug
shows up first.
And here’s the part I don’t say out loud: I shipped it anyway. The formula runs, the tree search picks reasonable moves, the tests are green. So it’s “done.” But I couldn’t tell you if that constant is right, or if a subtle bug in the square root would just make it play slightly worse in a way no test would ever catch. I’m not trusting the code. I’m trusting that the AI has seen more UCB1 implementations than I ever will. That’s a new feeling. For ten years “it works” meant I made it work.
Now it means something closer to: I haven’t found where it doesn’t.
3. The inverse — I understand it fine, I just don’t trust the decision
Duplicated builder methods that could’ve been one generic function, the same shape repeated almost verbatim in a write-back function and a broadcast method elsewhere in the codebase:
pub fn with_experience(mut self, table: ExperienceTable) -> Self { self.experience = table; self}
pub fn with_race_stats(mut self, table: RaceStatTable) -> Self { self.race_stats = table; self}And two small patch-hacks. Tagging an edge case as “tolerated” instead of teaching the simulation to model it:
fn fidelity_tag(&mut self, tag: OffModel) { if let Some(pf) = self.state.pending_fidelity.as_mut() { if !pf.off_model.contains(&tag) { pf.off_model.push(tag); // "different from the reference sim? tag it, move on" } }}A struct that grew a new bool flag every time a behavior changed, instead of getting restructured:
let step = FidelityStep { actor, pre, action, expected_life, expected_ap, expected_mp, crit, aoe_order, steal_heals, buffs_modelled: true, cascade_deaths: true, chaotic_targeting: true, // three stacked on top weapon, expected_outcome, off_model,};None of this is a knowledge gap. I read every line fine. What’s missing isn’t understanding, it’s trust in the decision that got made. That’s a third state that “imposter syndrome” doesn’t really cover: not “I don’t get it,” not “I get it and it’s fine,” but “I get it completely and I can tell it’s the wrong call.”
So it’s not one feeling, it’s a grid. Two questions: do I understand the code, and do I trust the call that produced it.
| Trust the call → yes | Trust the call → no | |
|---|---|---|
| Understand ↓ yes | 1. known pattern (the select!) — no imposter syndrome | 3. the inverse (the patch-hacks) — I see it’s wrong, ship it anyway |
| Understand ↓ no | 2. on faith (the UCB1 math) — I could read it, I just didn’t | 4. the gate — can’t judge and it smells wrong → the one to stop at |
Same shrug in every cell I actually ship, opposite reasons: one because I can’t judge it, one because I can’t be bothered to act on the judgment I have. The only cell I never ship is that empty-looking fourth one, and that’s the instinct I’m trying to get back.
Where that leaves me
At some point the real question isn’t “do I understand this code” anymore. It’s “do I even try to.” Because when AI writes it, redoing it costs basically nothing. Diff’s wrong? Regenerate. The machine just writes it again. And again. So the failure mode isn’t “did I ship a bug,” it’s “did I actually look at all.”
That’s a weird kind of cheap. Ten years doing this by hand, rewriting code cost me time, my time, the one thing I actually rationed. Now what’s getting spent is compute. Compute doesn’t tire me out. It doesn’t make me hesitate before trying a third approach. So I don’t hesitate. I just let it run.
There’s a smaller symptom under all this, and it’s almost embarrassing to admit. When the AI wrote ninety percent of a file, I’ve caught myself not wanting to touch it by hand. Even a one-line fix. It’s not laziness. It’s a weird fear that if I reach in and edit directly I’ll desync something, break some invariant the AI was tracking that I wasn’t. The code is technically mine. My repo, my name on the commits. But editing it feels like touching someone else’s work. That’s the part that actually gets me: not that I can’t understand the code.
I’ve started treating my own files like they belong to the machine.
And I honestly don’t know if that’s a good trade. First look: yes, obviously, more shipped, less grinding, deadlines hit. But the second I try to actually do the ROI math, time saved vs understanding lost vs “I used to know every inch of this system,” the numbers don’t close. The real cost isn’t on any invoice. It shows up later, quietly, as the exact thing I opened this post with: not knowing my own code anymore.
Maybe “it works” is enough most days. But I keep landing on a worse question than “is this a good trade.” It’s this: if the code is disposable and the understanding is optional, what’s the part of this job that’s still mine? I used to think it was the building. I’m not sure anymore that the building was ever the point, or if it was just the thing that forced me to understand. Take that away and I don’t know what’s left, and I don’t like that I don’t know.
I’m not going to pretend I’ve got a system for this. But I made one rule, small enough that I might actually keep it: the stuff I can’t evaluate, the math, the things that scared me earlier, doesn’t ship until I can explain it back to myself out loud. Not rewrite it. Just explain it. Everything else I’ll keep treating as disposable, because it is. The line isn’t “understand everything.”
It’s knowing which things you’re not allowed to not understand.