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_configinstead ofConfigclass, different error format,BaseModel.model_validate()/.model_dump(). - v1:
validatordecorators, innerConfigclass,.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
OptionalandNone
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.