# Push Down Field

> Move a superclass field into the subclasses that use it, so other subclasses stop carrying state they never touch.

- kind: technique
- category: dealing-with-generalization
- canonical: https://aipatterns.dev/refactoring/techniques/push-down-field
- inspired-by: https://refactoring.guru/push-down-field

## Problem

A field is declared in the superclass, but only one subclass reads or writes it. The other subclasses carry it around as `null` or a default, and readers cannot tell which subclasses it applies to.

## Solution

**Declare the field in the subclasses that use it and remove it from the superclass.**

## Before

```ts
abstract class Shipment {
  trackingNumber: string | null = null;
  pickupLocationId: string | null = null; // only used for click-and-collect
  constructor(readonly orderId: string) {}
}

class CourierShipment extends Shipment {
  dispatch(courier: Courier) {
    this.trackingNumber = courier.book(this.orderId);
  }
}

class StorePickup extends Shipment {
  assign(locationId: string) {
    this.pickupLocationId = locationId;
  }
}
```

## After

```ts
abstract class Shipment {
  constructor(readonly orderId: string) {}
}

class CourierShipment extends Shipment {
  trackingNumber: string | null = null;
  dispatch(courier: Courier) {
    this.trackingNumber = courier.book(this.orderId);
  }
}

class StorePickup extends Shipment {
  pickupLocationId: string | null = null;
  assign(locationId: string) {
    this.pickupLocationId = locationId;
  }
}
```

## Why

- Each class declares only the state it owns, which documents its purpose.
- Couriers no longer carry a pickup location that is always `null`.
- Code that reads the field must now know the subclass, which makes the dependency visible.

## Steps

1. Find every read and write of the field. Confirm they all happen on a specific subclass.
2. Declare the field in each subclass that uses it. Run the tests.
3. Remove the field from the superclass. Fix compiler errors where callers reach it through the superclass type. Run the tests.
4. Check serialization, database mappings and API responses that may emit the field for every subclass.

## Watch out

- Do not push down a field that generic code reads through the superclass, such as a reporting job that lists every shipment's tracking number. You would add type checks there.
- Single-table ORM mappings may still require the column on the base entity. Moving the field in code may need a mapping change too.
- Removing a field from an API response that always included it (even as `null`) can break clients.
- The inverse technique is [Pull Up Field](https://aipatterns.dev/refactoring/techniques/pull-up-field.md).

## Related

- Inverse: [Pull Up Field](https://aipatterns.dev/refactoring/techniques/pull-up-field.md).
- Fixes [Refused Bequest](https://aipatterns.dev/refactoring/smells/refused-bequest.md) and helps with [Temporary Field](https://aipatterns.dev/refactoring/smells/temporary-field.md).
- Usually done alongside [Push Down Method](https://aipatterns.dev/refactoring/techniques/push-down-method.md).

## Agent checklist

- [ ] Every access to the field is made on a specific subclass.
- [ ] No generic code reads it through the superclass type.
- [ ] ORM mappings and serialized output were checked.
- [ ] Each subclass that receives the field actually uses it.
