Topic 0.3
When to Use an LLM (and When Not To)
In one line
Use plain code for anything exact (sums, dates, lookups, rules) and an LLM for fuzzy language work. The strongest designs combine both: code handles what it can, the model handles the rest, and code checks the model.
Think of it like this
A shop has a calculator and a very well-read assistant. You'd never ask the assistant to add up the bill when the calculator is right there, and you'd never ask the calculator to write a friendly thank-you note. Good engineering gives each job to the right tool, and has the calculator double-check any number the assistant writes.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Deterministic
- Gives the same output every time for the same input. Ordinary code is deterministic; LLM output usually is not.
- Probabilistic
- Based on likelihoods, so the output can vary and can be wrong.
- Fallback
- A second way to handle a case when the first way can't.
- Validation
- Checking in code that a value is allowed (a real date, a total that matches).
- Long tail
- The many rare, odd cases that don't fit the common patterns.
Step by step
01Step 1: list the jobs in the feature
Take a feature like "answer a customer's order question". Break it into jobs: understand the question (language: model), look up the order (exact: code), compute the refund (exact: code), write the reply (language: model). Most features are a mix.
02Step 2: let the model write, let code compute
In the first example the model writes a friendly reply that includes the total. Code computes the true total from the order lines and compares. One reply matches; the other is off by 10, exactly the kind of mistake a model makes when it does arithmetic in text. The check costs one line and prevents sending a wrong bill.
03Step 3: code first, model for the rest
The second example handles dates. Two formats are parsed by code. next friday isn't a format code knows, so it goes to the model, and the model's answer is checked by the same code. 2026-13-40 is rejected by code: month 13 doesn't exist.
04Step 4: see why the model must not decide alone
Asked about 2026-13-40, the fake model (like many real ones) returns a plausible date instead of saying the input is wrong. Models are trained to be helpful, so they tend to produce *an* answer. When "this input is invalid" is the right answer, code is better at saying so.
05Step 5: count the calls
Only one of the four inputs reached the model. In production, log this ratio. If most inputs need the model, fine; if code could handle them with one more rule, add the rule and save the cost and latency on every future request.
Try it yourself
- 1
Catch a smaller mistake
Change the second fake reply to
Rs 1,159.01. Is it still caught? What tolerance would you use for a currency with paise, and why not compare floats with==? - 2
Add a format to code
Add a third format
YYYY/MM/DD(/with orderymd) toparse_with_code. Which order of formats is safe, given that10/10/2026and2026/10/10both use/? Add2026/10/12to the inputs and run. - 3
Make the model the last resort
Add the input
tomorrowand give the fake model an answer for it. Run, and check themodel callsline.
Code & diagrams
Two scripted replies from a model. Code computes the real total and catches the wrong one.
Expected output
Total computed by code: 1159.00
Model says 1159.00 -> OK
Model says 1169.00 -> MISMATCH, do not sendThe fake model stands in for a real call. Notice what it says about the impossible date.
Expected output
2026-10-10 -> 2026-10-10 (code)
10/10/2026 -> 2026-10-10 (code)
next friday -> 2026-10-16 (model, checked by code: ok)
2026-13-40 -> rejected (code: not a real date)
model calls: 1 of 4
Asked about 2026-13-40, the model says 2026-12-31: it invents a fix instead of saying no.Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Trust the model with the total
In the first example, replace the comparison with verdict = "OK" (so nothing is checked) and run.
Myth vs fact
Myth
Modern models are good at maths now, so checks aren't needed.
Fact
They are much better than before, but still not guaranteed, and a guarantee is what money needs. When code can compute it, compute it.
Myth
Using an LLM for everything makes a product smarter.
Fact
It makes it slower, costlier and less predictable where an exact rule existed. Smart products use models where language understanding is the real problem.
Myth
If the model can't parse something, it will say so.
Fact
Models lean towards giving an answer. Design the "invalid input" path in code.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Prefer having the model produce structure (fields, labels, IDs) and letting code produce facts (totals, dates, prices) and final text from templates. It shrinks the surface where a hallucination can reach the user.
- ▸
Measure the code/model split per input type in production logs. A new rule that removes 30% of model calls is often the cheapest latency and cost win available.
- ▸
For exactness the model can't give, give it a tool: a calculator, a database lookup, a date parser (Phase 7). The model decides *what* to compute; code computes it.
Remember this
- 1
Why this decision comes first. An LLM call is slow (often a second or more), costs money on every call, can be rate-limited, and gives answers that vary and are sometimes wrong. Ordinary code is instant, free to run, exact and the same every time. Using a model where code would do makes a feature slower, costlier and less reliable at the same time.
- 2
The "could plain code do it?" test. Before reaching for a model, ask: is there an exact rule? Adding prices, checking a date is valid, finding a customer by ID, sorting, converting units: code. Is the input open-ended human language? Understanding a complaint, summarising a document, pulling an address out of a messy email, answering a question in words: a model fits.
- 3
What LLMs are good at: classifying text ("is this a refund request?"), extracting fields from messy text, summarising, rewriting for a different reader, translating, drafting, answering questions from documents you provide, explaining and writing code, and turning a request into a structured call to your own functions (tools, Phase 7).
- 4
What they are bad at: exact arithmetic on long numbers, counting characters, keeping a promise that something is *always* true, facts they never saw or that changed after training, and anything where a wrong answer is dangerous and undetectable. The model's answer is a likely guess, so it must not be the only safeguard for money, health or security.
- 5
Combine them. The best pattern is a pipeline: code first for the cases it handles exactly, the model only for what is left, and code checks the model's answer wherever a check is possible. The second example parses dates with code, sends only
next fridayto the model, checks the model's answer with the same code, and shows that the model would happily "fix" an impossible date instead of rejecting it. - 6
Cost and speed follow from this. Every case code handles is a model call you don't pay for. In real systems the split is often lopsided: 80–95% of inputs follow a few known formats. Routing those to code cuts cost and latency dramatically and leaves the model for the long tail (Topic 12.6 does the same with cheap and expensive models).
Explain it without notes
Give two jobs you would never give an LLM and two you would, and explain the difference.
Why is "code first, model as fallback, code checks the model" a good default pipeline?
Why might a model return a date for an impossible input like 2026-13-40?
Practice
Write is_valid_iso_date(text) that returns True only for YYYY-MM-DD with a month 1–12 and a day that exists in that month (ignore leap years: February has 28 days).
Write route(text) that returns "code" if the text is a plain number like 42 or 3.5, and "model" otherwise.
Trade-offs
- ↔
More code rules: cheaper and more predictable, but each rule is code to maintain.
- ↔
More model: handles messy inputs with no new code, but costs per call and needs checks.
Done when you can
Done when you can apply the "could plain code do it?" test to a feature.
Done when you can list what LLMs are good and bad at.
Done when you can design a code-first pipeline that sends only the rest to a model.
Done when you check any exact value a model produces.