Skip to the solution
HardBackend14 min read

How do TypeAdapter, strict mode, and computed fields work in Pydantic v2?

763 views

The problem

Question presented to candidate: "In Pydantic v2, how would you validate a plain list[int] or a TypedDict that is not a BaseModel? What does strict mode change, and where can you turn it on? And how do you get a derived value like total into model_dump() output?"

What a strong answer should cover:

  • TypeAdapter(T) builds a pydantic-core validator and serializer for any type (list[int], TypedDict, unions, Annotated constraints, dataclasses): validate_python, validate_json, dump_python / dump_json, json_schema; build it once and reuse it because building is the expensive part
  • Lax mode (default) coerces sensible inputs ("3" to 3, 3.0 to 3) but still refuses lossy ones (2.9 for an int); strict mode accepts only the exact type for Python input
  • Strict at three levels: ConfigDict(strict=True) on the model, Field(strict=True) / StrictInt per field, strict=True per validation call; JSON input is looser even in strict mode (a JSON string can be a date)
  • @computed_field on a @property (or cached_property) puts a derived value into model_dump, model_dump_json and the serialization JSON schema, but not into validation input
  • Trade-offs: strict mode is for trusted typed producers (internal services); lax for human / HTTP input; computed fields run on every dump unless cached

Clarifying questions expected:

  • Is the data coming from JSON (an HTTP body, a queue) or from Python objects in the same process?
  • Should a string like "42" be accepted for an integer field, or is that a bug in the producer we want to catch?

Code / implementation expected: Yes โ€” a reused TypeAdapter, one strict and one lax validation of the same input, and a model with a @computed_field shown in model_dump().

  • pydantic
  • typeadapter
  • strict

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

14 min read ยท 10 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

PythonThe problems these features solve, in plain Python (runs in the page)
PythonTypeAdapter: validate, parse JSON, dump and get a schema for any type
# Pydantic v2 TypeAdapter: validate any type, no BaseModel needed
# (needs: pip install pydantic; verified with pydantic 2.13.5)
from datetime import date
from typing import Annotated, Literal, TypedDict

from pydantic import Field, TypeAdapter, ValidationError

# 1. Build the adapter ONCE (it compiles a validator), reuse it many times
ints = TypeAdapter(list[int])
print(ints.validate_python(["1", 2, 3.0]))
print(ints.validate_json('[4, "5"]'))
print(ints.dump_json([1, 2]))
print(ints.json_schema())

# 2. Errors come back as structured data, one per bad item
try:
    ints.validate_python(["1", 2.5, "x"])
except ValidationError as e:
    print(e.error_count(), "errors")
    for err in e.errors(include_url=False):
        print(" ", err["loc"], err["type"])

# 3. Works for TypedDicts, unions and constrained types too
class Event(TypedDict):
    kind: Literal["click", "view"]
    at: date
    score: Annotated[int, Field(ge=0, le=100)]

events = TypeAdapter(list[Event])
print(events.validate_json('[{"kind": "click", "at": "2026-01-31", "score": "7"}]'))
try:
    events.validate_python([{"kind": "buy", "at": "2026-02-30", "score": 101}])
except ValidationError as e:
    for err in e.errors(include_url=False):
        print(" ", err["loc"], err["type"])

# 4. Dump goes the other way: Python objects -> JSON-safe data
print(events.dump_python([{"kind": "view", "at": date(2026, 3, 1), "score": 1}], mode="json"))
PythonStrict mode at model, field and call level, plus computed fields
# Pydantic v2 strict mode (three levels) and computed fields
# (needs: pip install pydantic; verified with pydantic 2.13.5)
from datetime import date
from functools import cached_property

from pydantic import BaseModel, ConfigDict, Field, StrictInt, ValidationError, computed_field

def check(fn):
    try:
        print("  ok:", fn())
    except ValidationError as e:
        print("  error:", [err["type"] for err in e.errors(include_url=False)])

# Lax (the default) coerces; strict refuses
class Lax(BaseModel):
    qty: int
    day: date

print("lax model")
check(lambda: Lax(qty="3", day="2026-01-31"))

# Level 1: the whole model is strict
class Strict(BaseModel):
    model_config = ConfigDict(strict=True)
    qty: int
    day: date

print("strict model, Python input")
check(lambda: Strict(qty="3", day="2026-01-31"))
check(lambda: Strict(qty=True, day=date(2026, 1, 31)))
print("strict model, JSON input")
check(lambda: Strict.model_validate_json('{"qty": 3, "day": "2026-01-31"}'))
check(lambda: Strict.model_validate_json('{"qty": "3", "day": "2026-01-31"}'))

# Level 2: one strict field
class Mixed(BaseModel):
    qty: StrictInt
    price: float = Field(strict=False)
    code: str = Field(strict=True)

print("strict fields")
check(lambda: Mixed(qty=3, price="9.5", code="A1"))
check(lambda: Mixed(qty="3", price="9.5", code=1))

# Level 3: per call
print("strict per call")
check(lambda: Lax.model_validate({"qty": "3", "day": "2026-01-31"}, strict=True))

# Computed fields: derived values that are serialized
class Line(BaseModel):
    unit_price: float
    qty: int

    @computed_field
    @property
    def total(self) -> float:
        return round(self.unit_price * self.qty, 2)

    @computed_field
    @cached_property
    def label(self) -> str:
        print("  (label computed)")
        return f"{self.qty} x {self.unit_price}"

line = Line(unit_price=2.5, qty=4)
print(line.total, line.label, line.label)
print(line.model_dump())
print(line.model_dump_json())
print(sorted(Line.model_json_schema(mode="serialization")["properties"]))
print(sorted(Line.model_json_schema(mode="validation")["properties"]))
print(list(Line.model_fields), list(Line.model_computed_fields))
# Input for a computed field is ignored, not validated
print(Line(unit_price=1, qty=1, total=999).total)

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 124 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
Up nextHard

What is a weak reference in Python?

Continue