SQLAlchemy Cascades: delete-orphan and ON DELETE addresses a recurring problem in Python projects: Express ownership of related-object lifecycles and decide which layer performs deletion. This guide explains the mechanism, provides an executable example, and identifies the boundaries that keep an implementation reliable.

Concept and use case

ORM cascade propagates Session operations; ON DELETE belongs to a database foreign key. These distinct layers must agree.

For the related fundamentals, also read the SQLAlchemy relationships guide. Integration stays simpler when functions receive dependencies and data explicitly instead of relying on global state.

Practical example

class Order(Base):
    items: Mapped[list["Item"]] = relationship(
        back_populates="order",
        cascade="all, delete-orphan",
        passive_deletes=True,
    )

class Item(Base):
    order_id: Mapped[int] = mapped_column(
        ForeignKey("orders.id", ondelete="CASCADE")
    )

Use delete-orphan only when a child cannot exist without one parent. passive_deletes lets the database handle unloaded collections.

Important decisions

The correct choice depends on the public contract, expected volume, and failure behavior.

Consider concurrency, empty inputs, and partial failures. Document every limit that affects consumers and choose names that express intent.

Common mistakes

A minimal example does not replace bounds, error handling, and observability. Configuring delete on both sides of many-to-many can traverse and erase a whole graph. Bulk deletes bypass per-instance ORM cascades.

Avoid catching exceptions without context or returning partial output as if it were complete. An explicit failure is usually safer than silently incorrect data.

How to validate

Validate behavior, not only the happy path. Test parent deletion, child deassociation, and bulk deletion, always checking which rows remain.

The official documentation, accessed July 28, 2026, details the API and should remain the reference for future changes.