Not every problem needs machine learning. In fact, the mark of a senior engineer is often knowing when to skip it. A junior developer once spent three weeks training a neural network to convert Celsius to Fahrenheit. The model needed thousands of synthetic data points, careful hyper-parameter tuning, and a GPU for training. The result was a blob of weights that usually gave the right answer but occasionally produced nonsense temperatures, and no one could explain why. The correct solution is one line of arithmetic: multiply by 1.8 and add 32. That story is extreme, but the mistake is common — reaching for a fancy tool when a simple one is faster, cheaper, and more reliable. Learning when to say no to ML is as valuable as learning how to build it.
flowchart TD
A["Problem to solve"] --> B{"Is the rule known?"}
B -->|"yes"| C["Use rule / formula"]
B -->|"no, examples exist"| D["Use classical ML"]
B -->|"creative needed"| E["Use generative AI"]
C --> F["Fast, cheap, explainable"]
style C fill:#166534,color:#fff
style F fill:#166534,color:#fff
Rules, formulas, and lookup tables win on three fronts. First, they are deterministic: the same input always produces the same output, which makes testing trivial and guarantees repeatable behaviour. Second, they are cheap: no data collection, no training infrastructure, no monitoring for model drift, and no team of specialists to maintain the pipeline. Third, they are explainable: you can show a doctor, a judge, or a regulator exactly how the decision was made and why it will not change tomorrow. When the underlying relationship is known — physics equations, business rules, country codes — encoding it directly is almost always better than inferring it from data. The question is not 'Can ML solve this?' but 'Is ML the best way to solve this?'
def celsius_to_fahrenheit(c):
return c * 1.8 + 32
print(celsius_to_fahrenheit(0))
print(celsius_to_fahrenheit(100))
The hidden costs of machine learning stack up quickly. You need labeled data, which means either paying annotators or building collection pipelines. You need compute for training and retraining. You need monitoring because models decay as the world changes — a phenomenon called drift. When the data distribution shifts, yesterday's accurate model becomes tomorrow's liability. Debugging an ML system is harder than debugging code because the bug lives inside millions of numbers rather than a conditional you can read. All of this overhead is justified when the pattern is too complex to write down, but it is pure waste when a formula already exists. The total cost of ownership for a rule is often orders of magnitude lower.
Consider a routing algorithm that decides which warehouse ships a package. The business rule is simple: choose the closest warehouse with stock. A model trained on historical shipments might learn a similar pattern, but it will also absorb historical accidents — the day the closest warehouse was closed for maintenance, the week a misconfiguration sent everything to the wrong coast. The model bakes those exceptions into its weights. A rule can be updated in five minutes when a new warehouse opens. A model needs retraining, validation, and a deployment pipeline. The rule is not just simpler; it is more aligned with the actual business logic.
flowchart LR A["Rule / Formula"] --> B["Development: low"] A --> C["Runtime: fast"] A --> D["Explainability: high"] E["Machine Learning"] --> F["Development: high"] E --> G["Runtime: moderate"] E --> H["Explainability: low"] style A fill:#166534,color:#fff style E fill:#b45309,color:#fff
STATE_CODES = {
"California": "CA",
"New York": "NY",
"Texas": "TX",
}
def state_code(name):
return STATE_CODES.get(name, "UNKNOWN")
print(state_code("Texas"))
print(state_code("Florida"))
Explainability is not a nice-to-have in every domain. In medicine, a clinician needs to know why a model flagged a scan as suspicious before ordering a biopsy. In finance, regulators require justification for loan decisions. In aviation, a flight-control recommendation must be traceable to a specific sensor and rule. A neural network that says 'trust me' is not acceptable when the stakes are high and the error is costly. For these situations, a decision tree with visible splits, a linear model with readable coefficients, or even a plain lookup table is the responsible choice. The right tool is the one whose failure mode you can analyse and mitigate.
Maintenance is where the gap widens further. A formula from 1980 still works today because the laws of physics do not drift. A lookup table of country codes only changes when the UN adds a member state. But an ML model trained on last year's customer behaviour is already slightly wrong this year, and noticeably wrong after three years. Retraining is not free: it requires fresh data, verification that the new model is better, and a safe rollout. When the underlying truth is stable, a rule is a one-time cost. When it is fluid, ML is worth the ongoing investment. The art is telling the two apart.
flowchart TD
A["High-stakes decision"] --> B{"Must explain why?"}
B -->|"yes"| C["Rule / linear model / lookup"]
B -->|"no"| D["Complex ML acceptable"]
C --> E["Auditable and defensible"]
style C fill:#166534,color:#fff
style E fill:#166534,color:#fff
What does this print? A straightforward temperature conversion.
def celsius_to_fahrenheit(c):
return c * 9 / 5 + 32
print(celsius_to_fahrenheit(25))
25 times 9 divided by 5 equals 45.
45 plus 32 equals 77.
Write shipping_cost(weight_kg) that returns 5.00 for weight less than or equal to 1 kg, and 5.00 plus (weight_kg - 1) * 3.00 for heavier packages.
def shipping_cost(weight_kg):
# flat rate up to 1 kg, then 3.00 per extra kg
pass
Use an if for the flat-rate case.
For heavier packages, add 3.00 per kg above 1.
def shipping_cost(weight_kg):
if weight_kg <= 1:
return 5.00
return 5.00 + (weight_kg - 1) * 3.00
This country-code lookup crashes with a KeyError when the country is missing. Fix it to return 'UNKNOWN' for missing keys instead of crashing.
def country_code(name):
CODES = {"United States": "US", "Canada": "CA"}
return CODES[name] # bug: KeyError on missing key
Use dict.get(key, default) instead of dict[key].
The default should be 'UNKNOWN'.
def country_code(name):
CODES = {"United States": "US", "Canada": "CA"}
return CODES.get(name, "UNKNOWN")
Which situation is BEST suited for a simple rule rather than machine learning?
Unit conversion has a known, exact formula. The other tasks involve complex patterns that are not easily encoded as rules.
Write tool_recommendation(has_known_formula, needs_explanation) that returns 'rule' if has_known_formula is True, returns 'ml' if has_known_formula is False and needs_explanation is False, and returns 'hybrid' otherwise.
def tool_recommendation(has_known_formula, needs_explanation):
# return 'rule', 'ml', or 'hybrid'
pass
Check has_known_formula first.
If no formula and explanation is needed, return 'hybrid'.
def tool_recommendation(has_known_formula, needs_explanation):
if has_known_formula:
return 'rule'
if not needs_explanation:
return 'ml'
return 'hybrid'
Recap
- Machine learning is powerful, but it is not the right tool for every problem.
- When the relationship is known, a rule or formula is deterministic, cheap, and explainable.
- ML carries hidden costs: data collection, training, debugging, and drift over time.
- Explainability is mandatory in high-stakes domains like medicine, finance, and aviation.
- The senior instinct is to reach for the simplest tool that solves the problem reliably.
Next you will see where classical machine learning fits in the age of large language models and generative AI.