# Pull Up Field

> Move a field that every subclass declares in the same way into their shared superclass so it is defined once.

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

## Problem

Two or more subclasses declare the same field with the same meaning, often with different names. Code that uses the field is duplicated in each subclass because the superclass does not know it exists.

## Solution

**Declare the field once in the superclass and delete it from the subclasses.**

## Before

```ts
abstract class PaymentMethod {
  constructor(readonly customerId: string) {}
  abstract charge(amountCents: number): Promise<ChargeResult>;
}

class CardPayment extends PaymentMethod {
  protected lastUsedAt: Date | null = null;
  async charge(amountCents: number): Promise<ChargeResult> {
    this.lastUsedAt = new Date();
    return stripe.charge(this.customerId, amountCents);
  }
}

class BankDebit extends PaymentMethod {
  protected lastChargedOn: Date | null = null;
  async charge(amountCents: number): Promise<ChargeResult> {
    this.lastChargedOn = new Date();
    return sepa.debit(this.customerId, amountCents);
  }
}
```

## After

```ts
abstract class PaymentMethod {
  protected lastUsedAt: Date | null = null;
  constructor(readonly customerId: string) {}
  abstract charge(amountCents: number): Promise<ChargeResult>;
}

class CardPayment extends PaymentMethod {
  async charge(amountCents: number): Promise<ChargeResult> {
    this.lastUsedAt = new Date();
    return stripe.charge(this.customerId, amountCents);
  }
}

class BankDebit extends PaymentMethod {
  async charge(amountCents: number): Promise<ChargeResult> {
    this.lastUsedAt = new Date();
    return sepa.debit(this.customerId, amountCents);
  }
}
```

## Why

- One declaration replaces several, with one name.
- Behavior that uses the field (for example, an `isDormant()` check) can now move up with [Pull Up Method](https://aipatterns.dev/refactoring/techniques/pull-up-method.md).

## Steps

1. Confirm each subclass field has the same type and the same meaning, not only a similar name.
2. If the names differ, rename them in each subclass to one common name using your editor's safe rename. Run the tests.
3. Declare the field in the superclass with the narrowest visibility the subclasses need (`protected`, or `private` with accessors). Run the tests.
4. Delete the field from each subclass. Run the tests.
5. Check for serialized names (JSON, database columns) that used the old subclass field names.

## Watch out

- Do not pull up a field that only some subclasses use. The others inherit dead state. Use an intermediate superclass, or leave it down.
- Fields that look alike may have different invariants (one nullable, one always set). Pulling up forces a single rule.
- Exposing a field as `protected` couples all subclasses to it. Prefer a `private` field with methods if subclasses only need limited access.
- Persistence mappings can break when a field moves classes, especially in table-per-class ORMs.
- The inverse technique is [Push Down Field](https://aipatterns.dev/refactoring/techniques/push-down-field.md).

## Related

- Inverse: [Push Down Field](https://aipatterns.dev/refactoring/techniques/push-down-field.md).
- Fixes [Duplicate Code](https://aipatterns.dev/refactoring/smells/duplicate-code.md) across subclasses.
- Usually followed by [Pull Up Method](https://aipatterns.dev/refactoring/techniques/pull-up-method.md); part of [Extract Superclass](https://aipatterns.dev/refactoring/techniques/extract-superclass.md).

## Agent checklist

- [ ] Every subclass declares the field with the same type and meaning.
- [ ] Names were unified before the move.
- [ ] Visibility is as narrow as subclasses allow.
- [ ] Serialized field names and ORM mappings still match.
