>_ blog

AI Engineering vs Software Engineering

Where the two overlap, where they diverge, and what software engineers must unlearn.

6 min read · July 2026

>_The same craft, different physics

I have shipped both kinds of systems, and the muscle memory betrays you. You open the same editor, write the same TypeScript, open the same pull request, and wire up the same CI. On the surface, AI engineering looks like software engineering with a fancier library bolted on. Underneath, the physics changed.

Software engineering rests on determinism. Give a function the same input and it returns the same output, forever, or you have a bug. AI engineering rests on probability. Give a model the same input twice and you may get two different answers, and neither one is a bug. That single shift ripples through every practice you thought was settled: how you spec, how you test, how you debug, how you version. Most of the pain teams feel on AI projects comes from applying deterministic instincts to a probabilistic system.

>_Determinism was the whole deal

We rarely say it out loud, but determinism underwrote everything we built. Reproducible builds, unit tests that pass or fail cleanly, caching, 'works on my machine', idempotent retries: all of it assumes behavior is a fixed function of inputs. When something broke, it broke the same way twice, and that repeatability was the lever you pulled to fix it.

A language model hands you a distribution instead of a function. Temperature, sampling, and the provider's own routing mean the output is a draw, not a lookup. You can pin temperature to zero and still see drift when the vendor updates the weights behind an endpoint. The comfortable equals sign at the center of testing, output equals expected, quietly stops being true, and everything downstream has to be rebuilt on statistics rather than certainty.

>_Specs become prompts, and prompts leak

In classic software the specification lives upstream of the code. A human reads it, argues about edge cases, and compiles intent into unambiguous logic. The spec is documentation; the code is the truth.

In AI engineering the prompt is both the spec and part of the runtime. Natural language becomes a control surface that executes at request time, and natural language is gloriously underspecified. The model fills every gap you leave with its own priors, which is powerful and maddening in equal measure. Writing a strong prompt is closer to writing a tight requirements doc than writing code, except the reader improvises. I go deep on that craft in /resources/what-is-prompt-engineering; the point here is that your spec is now executable, leaky, and only as good as the constraints you make explicit.

>_Testing gives way to evals

You cannot assert that generated prose equals a golden string. The moment output is open-ended, the assert-equals-expected reflex is useless, and teams that cling to it end up with either zero coverage or a thousand brittle snapshots that flap on every model tweak.

The replacement is evaluation. Instead of a binary pass on one input, you measure behavior across a dataset and track how often the system clears a bar you define. Some checks stay cheap and deterministic, like schema validation or a regex for a forbidden phrase. Others need a rubric graded by a stronger model acting as judge, or a human in the loop for the calls that carry real risk. You stop asking 'did it pass' and start asking 'what is the pass rate, and is it trending up'. The concrete playbook for standing that up lives in /resources/how-to-test-llm-applications.

  • -A curated dataset of real inputs, including the failures that embarrassed you in production
  • -A mix of graders: exact match where possible, model-as-judge for open-ended quality, humans for high-stakes calls
  • -Thresholds and regression gates, so a drop in pass rate blocks a release the way a red test would
  • -Versioned datasets and prompts, so today's score is actually comparable to last month's

>_Debugging becomes behavior forensics

When a deterministic system fails, it tells on itself. There is a stack trace, an exception, a line number, a reproducible path to the fault. When a model fails, it usually does so confidently and silently. A hallucinated citation throws no error. A subtly wrong summary returns HTTP 200. There is nothing to catch.

So debugging turns into forensics. You instrument everything and capture the full trace of what actually reached the model: the resolved prompt, the retrieved context, the tool calls, the raw completion. Then you collect failing cases, cluster them by symptom, and hunt for the layer at fault, because the bug might live in your retrieval, your prompt template, your context budget, or the model's own training. The skill is less 'read the stack trace' and more 'reconstruct the crime scene from the logs'.

>_Versioning a dependency you do not control

Every AI system has a dependency at its core that you neither compiled nor can freeze on your terms: the model. Providers update weights, deprecate snapshots, and change default behavior on their schedule, not yours. A prompt tuned for one checkpoint can quietly regress when the endpoint shifts underneath it, and you get no compiler error, only a slow bleed in your metrics.

Treat the model like the unstable third-party dependency it is.

  • -Pin explicit model versions in anything that matters, never a floating 'latest' alias
  • -Keep a golden set of inputs and re-run your evals on every model or prompt change before it ships
  • -Watch for deprecation notices and budget the migration the way you would a major framework upgrade
  • -Log the exact model version on every production trace, so you can correlate a metric shift with a vendor change

>_What transfers, and matters more

Here is the reassuring part, and I mean it. Most of your engineering discipline transfers, and some of it becomes more valuable, not less. Observability, CI/CD, incremental delivery, code review, clean separation of concerns, error budgets, and blameless postmortems all apply directly. A probabilistic core makes them count for more, precisely because you have fewer guarantees to fall back on.

The best AI engineers I work with are not the ones with the flashiest prompt tricks. They are strong software engineers who added a statistical layer to their thinking. They wrap the model behind real interfaces, validate its output at the boundary, build fallback paths, and instrument the whole thing so they can reason about it. The model is exotic; the system around it is still just good software.

>_What you must unlearn

The habits that hurt are the deterministic reflexes you never had to question.

  • -The chase for 100% correctness. A single bad output is not a fatal bug; the real questions are the rate and the blast radius
  • -The belief that more prompt equals more control. Past a point, extra instructions add ambiguity, not precision
  • -Treating the model as a pure function. It is sampled behavior, so design for variance with retries, guardrails, and graceful degradation
  • -Trusting output because it reads well. Fluency is not correctness, and confident wrongness is the default failure mode

>_The bar is still yours to set

Probability at the core does not lower the quality bar. It relocates it. The guarantees you used to get for free from a deterministic runtime now have to be engineered deliberately at the edges: validation, evals, guardrails, fallbacks, and monitoring. That is not a downgrade of the craft. It is the craft, applied to a harder substrate.

If you are a software engineer moving into this work, keep every ounce of your discipline and drop exactly one assumption: that the same input yields the same output. Rebuild your testing around evals, your specs around explicit constraints, and your debugging around traces. The engineers who own quality in the AI era, a theme I keep returning to in /resources/harnessing-quality-in-the-ai-era, are the ones who stopped fighting the probability and started designing for it.