backend / web frameworks / pydantic / 01_pydantic_interview.md

Pydantic — Common Interview Questions and Answers

3 interview angles 4 min read source

Pydantic — Common Interview Questions and Answers

1. What is Pydantic and what is it used for?

Pydantic is a data validation and settings library that uses Python type annotations. It is used to:

  • Validate and parse input data (e.g. API request bodies, env vars)
  • Serialize data to JSON/dict
  • Enforce types and constraints at runtime
  • Generate JSON Schema (e.g. for OpenAPI in FastAPI)

2. How do you define a basic Pydantic model?

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str
    is_active: bool = True

Instances are created from dicts or keyword args; validation runs automatically.


3. What is the difference between Pydantic v1 and Pydantic v2?

  • v2 (Pydantic 2.x): Rewritten in Rust for speed; new validation decorators (@field_validator), model_config instead of Config class, different error format, BaseModel.model_validate() / .model_dump().
  • v1: validator decorators, inner Config class, .dict() / .parse_obj().

New projects should use v2.


4. How do you make a field optional and set a default?

from typing import Optional

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    quantity: int = 0

Optional[str] = None allows None or missing; quantity: int = 0 gives a default value.


5. What are validators and how do you add custom validation?

Validators check or transform field values. In Pydantic v2 you use @field_validator or @model_validator:

from pydantic import BaseModel, field_validator

class User(BaseModel):
    email: str
    age: int

    @field_validator("email")
    @classmethod
    def email_must_contain_at(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError("Invalid email")
        return v.lower()

    @field_validator("age")
    @classmethod
    def age_in_range(cls, v: int) -> int:
        if not 0 <= v <= 150:
            raise ValueError("Age must be 0-150")
        return v

6. What is the difference between validator and field_validator in Pydantic v2?

In v2, @field_validator is for single fields. @model_validator runs on the whole model (e.g. cross-field checks). The old @validator from v1 was replaced by these.


7. How do you validate the whole model (e.g. cross-field validation)?

Use @model_validator with mode='after' to work with the built model:

from pydantic import BaseModel, model_validator

class Range(BaseModel):
    start: int
    end: int

    @model_validator(mode="after")
    def start_before_end(self):
        if self.start >= self.end:
            raise ValueError("start must be less than end")
        return self

8. How do you serialize a Pydantic model to dict or JSON?

In Pydantic v2:

  • .model_dump() — model to dict (Python types)
  • .model_dump_json() — model to JSON string

You can use model_dump(exclude_none=True) or by_alias=True to match your API schema.


9. What are Pydantic Field and ConfigDict used for?

Field() adds metadata and constraints to a field:

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    price: float = Field(gt=0, description="Price in USD")
    tags: list[str] = Field(default_factory=list, max_length=10)

ConfigDict (in v2) replaces the inner Config class for model-wide settings (e.g. str_strip_whitespace, validate_assignment, extra='forbid').


10. How do you handle nested models?

Use another Pydantic model as a type:

from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class User(BaseModel):
    name: str
    address: Address

Nested models are validated and serialized recursively.


11. What is the difference between __init__ and model construction in Pydantic?

Pydantic models don’t rely on a hand-written __init__. The generated constructor:

  • Validates and coerces types
  • Applies validators and defaults
  • Handles Optional and None

You can still add custom __init__ in v2 with care (e.g. calling super().__init__(**data)), but usually default construction is enough.


12. How do you allow or forbid extra fields?

In v2, use model_config:

from pydantic import BaseModel, ConfigDict

class Strict(BaseModel):
    model_config = ConfigDict(extra="forbid")  # no extra keys allowed

class AllowExtra(BaseModel):
    model_config = ConfigDict(extra="allow")  # extra keys stored

extra="ignore" (default in many cases) ignores extra keys without storing them.


13. What are Pydantic settings and how do you load them?

BaseSettings (in pydantic_settings) loads config from env vars and .env files:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "My App"
    debug: bool = False
    database_url: str

    class Config:
        env_file = ".env"

settings = Settings()

Use for app configuration and secrets (with care).


14. How do you use Pydantic with JSON/dict that uses different key names (aliases)?

Use Field(alias="...") or model_config with populate_by_name=True:

from pydantic import BaseModel, Field

class User(BaseModel):
    first_name: str = Field(alias="firstName")
    last_name: str = Field(alias="lastName")

Then User.model_validate({"firstName": "John", "lastName": "Doe"}) works. Use model_dump(by_alias=True) to serialize with alias keys.


15. How does FastAPI use Pydantic?

FastAPI uses Pydantic to:

  • Parse and validate request bodies (body → Pydantic model)
  • Validate query/path/header parameters when typed
  • Serialize response models to JSON
  • Generate OpenAPI schema from models and field metadata

Any BaseModel used as a body or response model is validated and documented automatically.

Interview angle

  • “What does Pydantic give you over a dataclass?” - runtime validation and coercion from the type annotations, plus serialisation, JSON Schema generation and rich error reporting. A dataclass annotates types but never checks them.
  • “What changed in v2?” - the validation core moved to Rust for a large speedup, and the API renamed: model_validate, model_dump, field_validator, model_config. Recognising the v1 names as deprecated matters when reading existing code.
  • “When is a dataclass the better choice?” - internal objects where the data is already trusted and validation is pure overhead. Validate at the boundary, then use plain types inside.