The reply says six, but what type is six
A supplier integration receives a replenishment draft: {"sku": "vanilla", "quantity": "6"}. A person reading that JSON sees the number six. Python sees a string. If a downstream function multiplies quantity by two expecting an integer, it gets "66", not 12, because string repetition and integer multiplication share an operator but not a meaning. The bug is not that six was ambiguous. The bug is that nobody decided, in code, what type of six the program was willing to accept.
This is a decision every application boundary has to make, and it gets harder, not easier, once an LLM is producing the input. A model can be told in a prompt to return an integer. The receiving program still needs to check the returned value, and the field that receives the model's output still has to answer a narrower question: given this specific value, in this specific shape, does it satisfy the contract this field represents? That question is answered by runtime validation, not by hoping the sender behaved.
Why isinstance is not enough
Python's own type system contains a trap that makes hand-rolled validation unreliable. bool is a subclass of int. This is a real, longstanding feature of the language: True behaves as 1 and False behaves as 0 in arithmetic, and isinstance(True, int) evaluates to True. For most everyday code that is harmless. For a validation function that checks isinstance(value, int) before accepting a quantity, it means a Boolean sails through the same door as an integer.
Consider a field called quantity on an order line, with a business rule that it must represent a countable number of tubs, from 1 to 50. If a model, a form, or an upstream system sends True for that field, an isinstance check alone will say yes. But True is not a quantity. Nobody orders True tubs of vanilla. The type system's internal convenience, useful for arithmetic, is the wrong lens for a business contract about physical inventory.
A useful specification for this field says which representations are accepted before any stock calculation runs. For example: accept a Python integer from 1 through 50; reject Boolean values, floats and strings. A reviewer can now check the implementation against an explicit list rather than interpret what the author meant by "number."
What strict mode actually changes
Pydantic, the widely used Python data-validation library, offers a mode that closes this gap directly. Pydantic's strict-mode documentation describes reduced coercion and type-dependent differences between Python and JSON input. That distinction between Python input and JSON input matters more than it first appears, and the worked example below treats it as a separate question rather than folding it into the integer discussion.
In Pydantic's default, non-strict mode, a field typed quantity: int will happily convert a numeric string like "6" into the integer 6. That conversion can be exactly what you want at some boundaries, such as a web form where every field arrives as text. Strict mode turns that convenience off. A strict integer field accepts only an actual integer. It does not accept "6", does not accept 6.0, and does not accept True, The float and string can represent six; the Boolean represents a truth value.
An existing course lesson on runtime contracts works through this precisely: a strict integer quantity contract rejects Boolean, float, and numeric-string alternatives, and the lesson treats this as a demonstration of a policy choice rather than a new production measurement (From Prompt Restrictions to Runtime Contracts with Pydantic). That framing is the one worth carrying forward here: strict mode is not a universal correctness switch. It is a decision about which conversions this particular field is allowed to perform, made explicit and enforced consistently instead of left to whichever code path happens to touch the value first.
Working the six candidate inputs all the way through
Take a strict integer field with a valid range of 1 through 50, representing a tub quantity. Before running anything, predict what each of the following Python values should do against that field.
| Python value | Type | Expected result under strict mode | Reason |
|---|---|---|---|
6 | int | Accept | An integer within range |
True | bool | Reject | Subclass of int, but not a counted quantity |
6.0 | float | Reject | A float, even one with no fractional part, is not an integer type |
'6' | str | Reject | A numeric string is text, not a number, until something converts it |
0 | int | Reject | Below the field's stated minimum of 1 |
51 | int | Reject | Above the field's stated maximum of 50 |
Five of the six candidate values are rejected. Only 6 satisfies both requirements: it has the accepted type and falls inside the declared bounds. Nothing here is a claim about a new execution or a fresh benchmark. It is a table you can check by hand against Pydantic's own documented behavior for strict integer fields, and against the existing course example that walks through the same six values (From Prompt Restrictions to Runtime Contracts with Pydantic).
The second half of this worked case is easy to skip past, and skipping it is where mistakes happen. JSON has no native concept of a Python bool subclassing int, and it represents numbers, strings, and booleans as distinct token types at the syntax level. That does not mean every field's behavior is identical once you serialize these same six values to JSON text and validate from that JSON rather than from a live Python dictionary. For this strict integer field, the six cases should have the same accept/reject outcomes through either input path. Other types need their own checks. The instructive move is not to assume the Python-input table above transfers unchanged to a JSON-input path. It is to treat that as a separate, second check against the specific field and installed library version, rather than a generalization from one type to every type.

| Recheck item | Recorded basis | What to verify locally |
|---|---|---|
| Strict integer conversion rules | Pydantic documentation checked 10 September 2026 | Installed version and field configuration |
| Python versus JSON input | The six-value fixture above | Both entry points used by your application |
| Business range | Hypothetical inventory contract | Your actual minimum, maximum and ownership of changes |
Deciding the policy before writing the field
The practical task is not memorizing that Booleans get rejected. It is deciding, for each field in your application, what its contract actually represents, and then encoding that decision so it does not depend on which code path happens to touch the value first. A tub quantity is a countable, positive whole number with an upper bound tied to some operational limit, such as truck capacity or shelf space. That is a specific claim about the business, not a fact about Python's numeric types, and writing the bound down, as an explicit ge=1, le=50 style constraint, makes the decision inspectable by the next person who reads the code.
Write down, in a sentence a non-programmer could check, what the field represents: a countable quantity, a currency amount, an identifier. Do this before picking a Pydantic type.
Decide whether this specific field should accept a numeric string or a Boolean-shaped value as a convenience, or whether that conversion papers over a client that is sending the wrong shape. Strict and coercing modes answer different questions; pick deliberately rather than by default.
Confirm the field rejects the values it is supposed to reject. A field that only ever sees valid input in testing has not been tested against the argument it exists to make.
A validation failure should produce a message a client, or a client's developer, can act on. Silently coercing a legacy input is a different choice than rejecting it with a clear reason, and the two have different consequences for anyone still sending the old shape.
Turning on strict validation for an integer field tells you nothing about whether a date field, a currency field, or a nested list behaves the same way. Pydantic's documented exceptions for date and time types mean each field type needs its own check against the current version, not an assumption carried over from the integer case.
The migration question strict mode forces
Turning on strict validation for an existing field is not free. If a client has been sending quantity as the string "6" for months, and that string has been silently coerced into the integer 6 the whole time, flipping the field to strict mode will start rejecting every request from that client, immediately. This is exactly the situation that separates a good validation change from a disruptive one: the fix that closes the Boolean-quantity gap is the same fix that breaks a legitimate but loosely typed caller.
Measure that compatibility change before enabling it. Before enabling strict rejection in a live path, replay a sample of real historical-shaped payloads through both the old, permissive parser and the new, strict one, in an observation-only mode that does not affect any live decision. Keep the original payload, which parser version processed it, and which rejection code the strict parser would have produced. The check that matters here is narrow: valid integers like 6 must keep passing under the new parser exactly as they did under the old one. If that check fails, something in the new configuration is wrong, and the rollout should not proceed. A numeric string like "6" failing under the new, strict parser is the expected and intended difference, not a bug, since that was the entire point of tightening the field.
Once the comparison confirms the expected split, the operational decision belongs to whoever owns the API contract, not to the validation library. Legacy clients still sending numeric strings need a specific answer: are they migrated to send true integers, or do they receive a clear rejection message pointing at the field and the expected type? Silently coercing them forever means the strict field was never actually strict, just strict-looking. Routing them to an explicit migration notice, naming the affected client versions, the exact error response they will see, and who owns the cutover, turns a type-system detail into a manageable operational change instead of a surprise outage.
Check the decision
What would show this reasoning is wrong
This account rests on a specific, checkable claim: that Pydantic's strict mode, applied to an integer field, rejects Boolean, float, and numeric-string values while accepting integers within a declared range. If you configure a strict integer field with the current stable release of Pydantic and find that a Boolean value is accepted without error, that would contradict the behavior described here and in the existing course example, and would mean either the library's default configuration changed or the field was not actually configured as strict. Matching outcomes for this integer field are expected. They do not contradict the documented exceptions for other types. Add a separate date-field test if your model also accepts dates, using the exact Python and JSON entry points your application calls. Any of these results would mean rereading the current documentation and retesting against the specific field and version in front of you, since a validation library's coercion rules are the kind of detail that can shift between releases.
The usable next step is not to trust this table from memory. Take the field you are actually building, write its business contract in one sentence, choose strict or coercing mode for a stated reason, and run the six-value check yourself before wiring it to anything that spends money or moves inventory.
Continue with the course lesson that builds this same strict-integer contract into a nested draft object and connects it to inventory checks beyond schema validation alone.
