| name | type-safety-patterns |
| description | Type safety — Python type hints with mypy/pyright, TypeScript strict mode, branded types, Zod validation, runtime checks |
| triggers | ["type safety","type hints python","mypy pyright","typescript strict","branded types","zod schema","runtime type checking","type narrowing","no any typescript","type annotations"] |
| do_not_use_for | ["full validation schemas — use pydantic for complex input validation","error handling — use error-handling-patterns","general AI anti-patterns — use ai-code-maintainability"] |
| see_also | ["ai-code-maintainability","error-handling-patterns","pydantic-ai"] |
Type Safety Patterns
Python: Strict Type Hints
from __future__ import annotations
from typing import Optional, Union, Literal, TypeVar, Generic
from collections.abc import Callable, Sequence
from dataclasses import dataclass
def find_user(user_id: str) -> User | None: ...
type Event = ClickEvent | SubmitEvent | ErrorEvent
Status = Literal["active", "inactive", "pending"]
def update_status(user_id: str, status: Status) -> None: ...
T = TypeVar("T")
def first_or_default(items: Sequence[T], default: T) -> T:
return items[0] if items else default
Python: Mypy / Pyright Config
[tool.pyright]
pythonVersion = "3.11"
typeCheckingMode = "strict"
reportMissingImports = true
reportMissingTypeStubs = false
reportUnknownMemberType = false
[tool.mypy]
python_version = "3.11"
strict = true
ignore_missing_imports = true
warn_return_any = true
warn_unused_ignores = true
Python: Branded/Newtype
from typing import NewType, Annotated
UserId = NewType("UserId", str)
OrderId = NewType("OrderId", str)
def get_user(user_id: UserId) -> User: ...
def get_order(order_id: OrderId) -> Order: ...
user_id = UserId("u-123")
order_id = OrderId("o-456")
from annotated_types import Gt, Le
from pydantic import TypeAdapter
PositiveInt = Annotated[int, Gt(0)]
Percentage = Annotated[float, Gt(0.0), Le(100.0)]
adapter = TypeAdapter(Percentage)
adapter.validate_python(50.0)
adapter.validate_python(150.0)
TypeScript: Strict Mode
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true
}
}
TypeScript: Branded Types
declare const __brand: unique symbol;
type Brand<T, B> = T & { [__brand]: B };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
function createUserId(raw: string): UserId {
return raw as UserId;
}
function getUser(id: UserId): Promise<User> { ... }
const userId = createUserId("u-123");
const orderId = "o-456" as OrderId;
getUser(userId);
getUser(orderId);
TypeScript: Zod Runtime Validation
import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150),
role: z.enum(["admin", "user", "guest"]),
email: z.string().email(),
createdAt: z.coerce.date(),
});
type User = z.infer<typeof UserSchema>;
function parseUser(raw: unknown): User {
return UserSchema.parse(raw);
}
result = .(raw);
(!result.) {
logger.(, { : result.. });
;
}
user = result.;
Type Narrowing
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.side ** 2;
case "triangle": return (shape.base * shape.height) / 2;
default:
const _exhaustive: never = shape;
throw new Error(`Unknown shape: ${_exhaustive}`);
}
}
Anti-Fake-Pass Checks
from __future__ import annotations is needed for forward references in Python 3.9
NewType only creates distinct types at check time — isinstance() won't detect them at runtime
noUncheckedIndexedAccess: true makes arr[0] type T | undefined — handle the undefined case
- Zod
parse() throws ZodError — use safeParse() in user-facing code
strict: true in tsconfig enables 8 flags at once — add each flag individually if migrating legacy code
- Branded types require a
createXxx() factory that does the cast — don't cast everywhere