Skip to content

Pull Up & Push Down

Pull Up takes a method or field that is identical across two or more subclasses and lifts it into their common superclass, so the logic lives in exactly one place. Push Down does the reverse: a member sitting in the superclass that only some subclasses actually use is moved down into just those subclasses, so the parent stops promising behaviour most of its children do not want.

Pull Up is the cure for Duplicated Code spread across siblings — the same annualCost body copy-pasted into Salaried and Contractor, drifting subtly out of sync over time. Push Down attacks the opposite smell: a superclass cluttered with a member that is Refused Bequest — half the subclasses inherit a commission field they never touch, so the field misleads every reader of the parent. In both cases the hierarchy is making a claim that does not match reality.

Two employee subclasses each carry an identical annualCost calculation. We pull it up. (A commission field that only contractors need is pushed down in the same spirit.)

// Before — annualCost duplicated in both subclasses
abstract class Employee {
constructor(public monthlyPay: number) {}
}
class Salaried extends Employee {
annualCost(): number {
return this.monthlyPay * 12;
}
}
class Contractor extends Employee {
annualCost(): number {
return this.monthlyPay * 12;
}
}
// After — pulled up once; commission pushed down to Contractor only
abstract class Employee {
constructor(public monthlyPay: number) {}
annualCost(): number {
return this.monthlyPay * 12;
}
}
class Salaried extends Employee {}
class Contractor extends Employee {
constructor(monthlyPay: number, public commission: number) {
super(monthlyPay);
}
annualCost(): number {
return super.annualCost() + this.commission;
}
}
classDiagram
  class Employee {
    +annualCost() number
  }
  class Salaried
  class Contractor
  Employee <|-- Salaried
  Employee <|-- Contractor
  note for Employee "annualCost pulled up here once"
annualCost lifted into the shared Employee parent
  1. Confirm the members really are identical (Pull Up) or really are used by only a subset of subclasses (Push Down). If two copies differ slightly, unify them first with smaller refactorings until they match.
  2. For Pull Up: create the member on the superclass (or embedded base / trait default in Go and Rust). Copy one subclass’s body into it.
  3. Delete the member from each subclass, one at a time, running your tests after each deletion.
  4. For Push Down: copy the member into each subclass that needs it, then remove it from the superclass.
  5. Adjust any subclass that still needs custom behaviour to call up to the shared version and extend it.
  6. Run your tests. Behaviour must be unchanged at every step.

Pull Up whenever you catch the same method or field duplicated across siblings — it is one of the most satisfying ways to delete code. Push Down whenever the superclass advertises a member that most subclasses ignore or override away; the parent’s interface should reflect what all its children genuinely share.

The trade-off is coupling: pulling up binds the subclasses to a shared definition, so a later divergence forces you to push it back down or override. In Go and Rust the same intent is expressed through embedding and trait defaults rather than inheritance — which keeps the shared logic in one place without claiming a false “is-a” relationship.

When is Pull Up the right move?
Push Down addresses which smell?
How do Go and Rust express "pull up" without inheritance?
What is the risk of pulling up two members that are only nearly identical?