Skip to main content
CleanCodeMastery

Pull Up Constructor Body: One Shared Morning Routine, Then Your Own First Period

Learn the Pull Up Constructor Body refactoring with a school morning-assembly story — move duplicated initialization from subclass constructors into a superclass constructor called via super/base, with safe steps in TypeScript and C#.

26 min read Updated June 11, 2026beginner
refactoringpull up constructor bodyconstructorssuperbaseinheritancetypescriptcsharp

🌅 Every section sings the same prayer

One more morning at Sunrise Public School. The bell rings at 8 a.m., and watch what happens in the three sections of Class 7.

In 7A: class teacher Mrs. Iyer begins with the school prayer, then marks attendance, then writes the date on the board. After that, 7A starts its first period — Mathematics.

In 7B: Mr. Bose leads the school prayer, marks attendance, writes the date. Then 7B starts its first period — English.

In 7C: Ms. Fernandes does the prayer, attendance, date. Then 7C heads out for its first period — Swimming (7C is the sports section, remember).

Every section's morning begins identically and ends differently. The prayer, the attendance, the date — that opening routine is the same everywhere, because it is how any class in this school starts its day. Only the first period is each section's own.

Now, for years, each class teacher kept a personal "morning routine card" describing all the steps — and the cards drifted, as copies always do at this school. One year, a substitute teacher in 7B inherited a card that skipped attendance. For a whole week, 7B had no attendance record, and nobody noticed until a parent called head clerk Mr. Sharma about an absent child the school had marked as nothing at all. Mr. Sharma searched the registers, found a blank week, and walked straight to the Headmistress's office with the evidence.

Headmistress Dr. Meena Kulkarni — by now you know her style from the Pull Up Field and Pull Up Method stories — fixed it the usual way. She wrote one "School Morning Opening" procedure for the office: prayer, attendance, date. Each section's card was reduced to two lines: "1. Do the School Morning Opening. 2. Begin your section's first period." The shared opening is defined once. Each section adds only its own ending.

Here is why this story gets its own post. In code, the "morning routine" of an object is its constructor — the procedure that runs when the object is born and sets it up for the day. And constructors are special: you cannot simply apply Pull Up Method to them, because constructors are not inherited — they are chained. The refactoring that handles this special case is Pull Up Constructor Body.

A teacher's experience of the change, as a journey:

Figure 1: Maintaining the morning routine cards, before and after the shared opening

📌 What is Pull Up Constructor Body?

Pull Up Constructor Body is Fowler's answer to a very specific obstacle. You look at the constructors of sibling subclasses, and their bodies are nearly identical: each one assigns the same shared fields from the same parameters, then does a little subclass-specific work. Clear Duplicate Code — but the usual cure does not fit.

Why not? Two reasons, both about how languages treat constructors:

  1. Constructors are not inherited. If Section has a constructor, SectionSevenA does not automatically get a usable SectionSevenA(...) from it the way it inherits applyForLeave(). Each subclass that needs parameters must declare its own constructor, full stop. So "move it up and delete the copies" — the plain Pull Up Method recipe — is impossible; the subclass constructors must continue to exist.
  2. Constructors are chained. When you write new SectionSevenA(...), the language runs the parent's constructor first (explicitly via super(...)/base(...), or implicitly), and only then the child's body. Construction flows down the hierarchy like the school day: the whole-school opening first, then the section's own first period.

So Fowler defines a constructor-shaped variant:

When subclass constructors share common initialization code, create a superclass constructor containing that common code, and have each subclass constructor call it — via super(...) in TypeScript/Java or : base(...) in C# — before doing its own specific work.

The shared opening moves upstairs and is written once. Each subclass constructor shrinks to: call the shared opening, then do my own thing. Exactly the Headmistress's two-line card.

💡

One-line summary: Pull Up Constructor Body moves the duplicated opening of sibling constructors into a superclass constructor that every subclass calls via super()/base() — like one shared "prayer, attendance, date" routine, after which each section starts its own first period.

Why does this matter more than ordinary duplication? Because construction is where invariants are born. An invariant is a promise the object keeps for its whole life — "every section always has a class teacher recorded", "attendance register is never missing". If each subclass constructor establishes those promises separately, the copies can drift, and then some objects are born keeping the promise and others are born broken — like 7B's week without attendance. Objects that start life inconsistently are among the most maddening bugs to chase, because the crash happens far from the birthplace. Centralizing the shared setup guarantees every object in the hierarchy starts from the same correct baseline.

College corner: the language feature underneath this refactoring is constructor chaining. In Java and TypeScript, super(...) must be the first statement (TypeScript will not even let you read this before it); in C#, the : base(...) clause sits in the signature itself, so child-statements cannot sneak ahead of it; in Python, you call super().__init__(...) explicitly and the language trusts you with the ordering. The common idea: an object is built base-first, layer by layer, like a building going up floor by floor. Pull Up Constructor Body is simply the refactoring that uses chaining the way it was designed to be used — put each floor's construction work on its own floor.

Where this move sits in the family:

Figure 2: Constructor body is the special middle child of the pull-up family

🔍 When do we need it?

The signals, in school order:

  • Sibling constructors begin with the same assignments. Each one copies the same parameters into the same fields: this.name = name; this.grade = grade; repeated in every subclass. That repeated opening is Duplicate Code hiding in the one place people rarely look for it — everyone audits methods, few audit constructors.
  • You just finished Pull Up Field. This is the natural moment. The fields now live in the parent (Pull Up Field put them there), but every subclass constructor still initializes them individually. The data moved up; its birth ritual did not. Pull Up Constructor Body completes the move.
  • A new shared field means editing every constructor. If adding admissionDate to the hierarchy forced you to touch three subclass constructors with the same new line, the shared opening should have lived upstairs.
  • Validation of common parameters is copy-pasted. Each constructor checks if (!name) throw ... — shared invariants enforced in N places, each free to drift.

And the cautions:

  • Only the genuinely shared part moves up. 7C's swimming period stays on 7C's card. If a piece of setup belongs to one subclass, hoisting it burdens the others — the constructor version of Refused Bequest.
  • Order can block you. In TypeScript, Java, and C#, the parent-constructor call must come first — TypeScript will not even let you touch this before super() returns. If the shared logic genuinely must run after some subclass setup, plain chaining cannot express it; we will meet the standard fallback (a protected init method) in the steps.
  • The shared body must not call overridable methods. This is the classic constructor trap, and it gets its own warning below.

The shared opening lines sit squarely in the "pull up now" zone of our placement map — every subclass repeats them, and they currently live downstairs:

Figure 3: The shared constructor opening is used by all subclasses but written in each — pull it up

👀 Before and after at a glance

Here are the sections in TypeScript, each constructor repeating the same opening. (The fields are already in the parent — Pull Up Field came first, as always.)

// BEFORE: every constructor repeats the same opening ritual
abstract class Section {
  protected classTeacher!: string;
  protected attendanceRegister!: string[];
  protected dateOnBoard!: string;
}
 
class SectionSevenA extends Section {
  private firstPeriod: string;
  constructor(teacher: string, today: string) {
    super();
    this.classTeacher = teacher;            // shared opening...
    this.attendanceRegister = [];           // ...copied here...
    this.dateOnBoard = today;               // ...line by line
    this.firstPeriod = "Mathematics";       // 7A's own part
  }
}
 
class SectionSevenB extends Section {
  private firstPeriod: string;
  constructor(teacher: string, today: string) {
    super();
    this.classTeacher = teacher;            // the same opening again
    this.attendanceRegister = [];
    this.dateOnBoard = today;
    this.firstPeriod = "English";           // 7B's own part
  }
}
 
class SectionSevenC extends Section {
  private firstPeriod: string;
  private poolLane: number;
  constructor(teacher: string, today: string, lane: number) {
    super();
    this.classTeacher = teacher;            // and again
    this.attendanceRegister = [];
    this.dateOnBoard = today;
    this.firstPeriod = "Swimming";          // 7C's own part
    this.poolLane = lane;                   // sports section extra
  }
}

Three constructors, three copies of the three-line opening — nine duplicated lines establishing the school's most basic invariants. The duplication is spread perfectly evenly, which is why no single class teacher ever felt it was their problem:

Figure 4: Each section constructor carries one copy of the duplicated opening lines

After Pull Up Constructor Body:

// AFTER: shared opening defined once, called via super()
abstract class Section {
  protected classTeacher: string;
  protected attendanceRegister: string[];
  protected dateOnBoard: string;
 
  protected constructor(teacher: string, today: string) {
    this.classTeacher = teacher;            // the School Morning Opening,
    this.attendanceRegister = [];           // written exactly once
    this.dateOnBoard = today;
  }
}
 
class SectionSevenA extends Section {
  private firstPeriod: string;
  constructor(teacher: string, today: string) {
    super(teacher, today);                  // 1. do the shared opening
    this.firstPeriod = "Mathematics";       // 2. start my own first period
  }
}
 
class SectionSevenB extends Section {
  private firstPeriod: string;
  constructor(teacher: string, today: string) {
    super(teacher, today);
    this.firstPeriod = "English";
  }
}
 
class SectionSevenC extends Section {
  private firstPeriod: string;
  private poolLane: number;
  constructor(teacher: string, today: string, lane: number) {
    super(teacher, today);
    this.firstPeriod = "Swimming";
    this.poolLane = lane;                   // only the sports extras remain
  }
}

Each subclass constructor is now the Headmistress's two-line card: shared opening, then my own part. A new shared field next term — say admissionYear — will be one new parameter and one new line, in one constructor.

Figure 5: After the refactoring, the shared opening lives in the base constructor and subclasses delegate to it

It also helps to see the runtime order, because chaining is what makes constructors special. When new SectionSevenA(...) runs, the messages flow like this:

Figure 6: Construction is parent-first — the shared opening runs before the section adds its own part

This sequence is the school morning: the whole-school opening completes first, and only then does each section begin its own first period. The language enforces the assembly order that Dr. Kulkarni enforces with her two-line card.

🪜 Step-by-step, the safe way

The bird's-eye flow, then the details:

Figure 7: The shared opening lines from all three constructors converge into one base constructor
  1. Make sure the shared fields already live in the superclass. If classTeacher is still declared in each subclass, stop and apply Pull Up Field first. You cannot move an assignment upstairs while its field lives downstairs. The standard cleanup order for a hierarchy is: fields up, then constructor bodies up, then methods up.

  2. Line up the shared statements at the top of each constructor. The statements you want to move must form a prefix — the opening lines — because the super() call that will replace them must come first. If a shared line sits after a subclass-specific line, check whether they can be safely reordered (they usually can when they touch different fields). This is the intermediate state to reach:

    // INTERMEDIATE: shared lines gathered at the top, nothing moved yet
    constructor(teacher: string, today: string, lane: number) {
      super();
      this.classTeacher = teacher;        // shared block,
      this.attendanceRegister = [];       // now contiguous,
      this.dateOnBoard = today;           // at the very top
      this.firstPeriod = "Swimming";      // own part below
      this.poolLane = lane;
    }
  3. Create the superclass constructor. Give it parameters for exactly what the shared block needs — here teacher and today. Mark it protected if the parent is abstract; nobody should construct a bare Section anyway.

  4. In one subclass, replace the shared block with a super(...) call. Delete the three opening lines, pass the parameters up: super(teacher, today);. Compile and run the tests.

  5. Repeat for each remaining subclass, one at a time, testing after each. Same drill as the previous posts: one small step, one green test run, then the next.

  6. If the order genuinely cannot work, use the fallback. Sometimes the shared logic must run after subclass setup — say, a computed registration code that needs subclass fields. Constructor chaining cannot express "child first, parent second". The standard escape is a protected ordinary method on the parent:

    // FALLBACK: when super-first order doesn't fit
    abstract class Section {
      protected completeMorningOpening(teacher: string, today: string): void {
        this.classTeacher = teacher;
        this.attendanceRegister = [];
        this.dateOnBoard = today;
      }
    }
    // subclass constructor: own pre-setup first, then
    // this.completeMorningOpening(teacher, today);

    This trades a little safety (nothing forces every subclass to call it) for flexibility, so prefer real constructor chaining whenever the order allows.

The lifecycle through these steps, as a state machine — note the extra Aligned state that is unique to constructor work, because the shared lines must form a prefix before they can move:

Figure 8: The constructor opening's journey — aligned as a prefix, then hoisted, then verified
⚠️

Two traps deserve double care here. First, run the tests after every single constructor you convert — constructor bugs surface at object birth, and a missed parameter in one super() call can silently default a field for every instance of that subclass. Second, never let the pulled-up constructor call an overridable (virtual) method. The parent constructor runs before the subclass's fields are initialized; if it calls a method the subclass overrides, the override executes against a half-built object and reads undefined values. Keep constructor bodies plain: assign fields, validate parameters, nothing polymorphic.

🏗️ A bigger real-life example

Let us watch the full school scenario with validation — because validation is where duplicated constructors hurt most. Before:

// BEFORE: shared validation + assignment, copied and already drifting
abstract class Section {
  protected classTeacher!: string;
  protected roomNumber!: number;
  protected attendanceRegister!: string[];
}
 
class SectionSevenA extends Section {
  constructor(teacher: string, room: number) {
    super();
    if (teacher.trim() === "") throw new Error("Teacher name required");
    if (room < 1) throw new Error("Invalid room");
    this.classTeacher = teacher;
    this.roomNumber = room;
    this.attendanceRegister = [];
  }
}
 
class SectionSevenB extends Section {
  constructor(teacher: string, room: number) {
    super();
    if (teacher.trim() === "") throw new Error("Teacher name required");
    // room validation forgotten here — drift!
    this.classTeacher = teacher;
    this.roomNumber = room;
    this.attendanceRegister = [];
  }
}
 
class SectionSevenC extends Section {
  private poolLane: number;
  constructor(teacher: string, room: number, lane: number) {
    super();
    if (teacher.trim() === "") throw new Error("Teacher name required");
    if (room < 1) throw new Error("Invalid room");
    this.classTeacher = teacher;
    this.roomNumber = room;
    this.attendanceRegister = [];
    if (lane < 1 || lane > 8) throw new Error("Pool lane must be 1-8");
    this.poolLane = lane;
  }
}

Spot the drift: 7B forgot the room check, so new SectionSevenB("Mr. Bose", -3) happily creates a section in room minus-three. The invariant "every section has a valid room" is kept by some objects and broken by others — the worst kind of inconsistency, born at birth. After the refactoring:

// AFTER: invariants established once, for every object in the hierarchy
abstract class Section {
  protected classTeacher: string;
  protected roomNumber: number;
  protected attendanceRegister: string[];
 
  protected constructor(teacher: string, room: number) {
    if (teacher.trim() === "") throw new Error("Teacher name required");
    if (room < 1) throw new Error("Invalid room");
    this.classTeacher = teacher;
    this.roomNumber = room;
    this.attendanceRegister = [];
  }
}
 
class SectionSevenA extends Section {
  constructor(teacher: string, room: number) {
    super(teacher, room);                   // opening + validation, guaranteed
  }
}
 
class SectionSevenB extends Section {
  constructor(teacher: string, room: number) {
    super(teacher, room);                   // 7B can no longer forget the check
  }
}
 
class SectionSevenC extends Section {
  private poolLane: number;
  constructor(teacher: string, room: number, lane: number) {
    super(teacher, room);
    if (lane < 1 || lane > 8) throw new Error("Pool lane must be 1-8");
    this.poolLane = lane;                   // only sports-section concerns remain
  }
}

Now no subclass can forget the room check — the language itself forces every construction through the one shared opening. That is the deep benefit: the invariant moved from "a convention each constructor must remember" to "a rule the compiler enforces". And look at 7C: its constructor now contains only sports-section concerns. A reader instantly sees what makes 7C special, because everything ordinary has moved out of the way.

The duplicated-line arithmetic, in one chart. The shared opening was about five lines (two checks plus three assignments) in each of three constructors; after the move it is five lines in one place:

Figure 9: Copies of the shared opening to maintain, before and after the pull-up

College corner: what we just built is the textbook way of guaranteeing class invariants in a hierarchy. Formally, a constructor's postcondition should establish the invariant; every public method assumes it on entry and preserves it on exit. When initialization is duplicated, you effectively have N independent postconditions that claim to establish the same invariant — and the 7B example shows what happens when one quietly does not. Centralizing it in the base constructor turns the invariant into a theorem with a single proof. This is also why dependency-injection frameworks and ORMs that bypass constructors (deserializers, proxies) deserve suspicion in code review: they can create objects that skipped the morning assembly entirely.

🐍 The same idea in Python

Python chains constructors with an explicit super().__init__(...) call — the language does not force it to be first, which makes the discipline your job:

# AFTER, in Python: shared opening once, chained explicitly
class Section:
    def __init__(self, teacher: str, room: int) -> None:
        if not teacher.strip():
            raise ValueError("Teacher name required")
        if room < 1:
            raise ValueError("Invalid room")
        self.class_teacher = teacher
        self.room_number = room
        self.attendance_register: list[str] = []
 
 
class SectionSevenC(Section):
    def __init__(self, teacher: str, room: int, lane: int) -> None:
        super().__init__(teacher, room)   # shared opening first — by convention
        if not 1 <= lane <= 8:
            raise ValueError("Pool lane must be 1-8")
        self.pool_lane = lane             # sports-section extras only

Two Python notes. First, because the language will not stop you from putting statements before super().__init__(), teams usually adopt the Java-style rule by convention: chain first, always — it keeps the half-built-object trap away. Second, if you forget the super().__init__(...) call entirely, Python raises no error at construction; the object is simply born without the parent's fields, and the crash comes later, far from the birthplace — exactly the 7B blank-register week, in interpreter form. A linter rule (or attrs/dataclasses, which generate correct chaining for you) is the Mr. Sharma of a Python codebase.

💼 The same refactoring in C#

C# spells the chaining differently — and rather elegantly. The parent call is not a statement inside the body but a : base(...) clause in the constructor's signature, which makes the "shared opening first" rule visible right in the declaration. Before:

// BEFORE: duplicated initialization in every subclass constructor
abstract class Section
{
    protected string _classTeacher;
    protected int _roomNumber;
    protected List<string> _attendanceRegister;
}
 
class SectionSevenA : Section
{
    public SectionSevenA(string teacher, int room)
    {
        if (string.IsNullOrWhiteSpace(teacher))
            throw new ArgumentException("Teacher name required");
        _classTeacher = teacher;           // shared opening, copy 1
        _roomNumber = room;
        _attendanceRegister = new List<string>();
    }
}
 
class SectionSevenB : Section
{
    public SectionSevenB(string teacher, int room)
    {
        if (string.IsNullOrWhiteSpace(teacher))
            throw new ArgumentException("Teacher name required");
        _classTeacher = teacher;           // shared opening, copy 2
        _roomNumber = room;
        _attendanceRegister = new List<string>();
    }
}

After — note the protected base constructor and the : base(...) clauses:

// AFTER: protected base constructor + base(...) chaining
abstract class Section
{
    protected string _classTeacher;
    protected int _roomNumber;
    protected List<string> _attendanceRegister;
 
    protected Section(string teacher, int room)   // shared opening, once
    {
        if (string.IsNullOrWhiteSpace(teacher))
            throw new ArgumentException("Teacher name required");
        if (room < 1) throw new ArgumentException("Invalid room");
        _classTeacher = teacher;
        _roomNumber = room;
        _attendanceRegister = new List<string>();
    }
}
 
class SectionSevenA : Section
{
    public SectionSevenA(string teacher, int room)
        : base(teacher, room) { }                 // nothing extra to do
}
 
class SectionSevenC : Section
{
    private readonly int _poolLane;
 
    public SectionSevenC(string teacher, int room, int lane)
        : base(teacher, room)                     // shared opening first, by syntax
    {
        if (lane is < 1 or > 8)
            throw new ArgumentException("Pool lane must be 1-8");
        _poolLane = lane;                         // sports section's own part
    }
}

C#-specific notes:

  • protected Section(...) is the idiomatic visibility for an abstract base's constructor: subclasses can chain to it, outsiders cannot call it. (A public constructor on an abstract class is misleading — it can never be called directly anyway.)
  • : base(teacher, room) sits in the signature, before the body's opening brace. The language guarantees it runs first; you cannot sneak subclass statements ahead of it.
  • If you omit : base(...), C# silently calls the parent's parameterless constructor — and if the parent has none (ours requires parameters), you get a compile error. That error is your friend: it makes forgetting the shared opening impossible.
  • The virtual-call warning applies with full force in C#: a virtual method invoked from Section's constructor would dispatch to the subclass override before the subclass's own constructor body (and field initializers' dependent logic) has run. Analyzer rule CA2214 ("Do not call overridable methods in constructors") exists precisely for this trap.

🛠️ IDE support

Tooling here is good but a bit less push-button than for plain members, because constructors are special in every language:

  • IntelliJ IDEA / Rider (JetBrains): the Refactor → Pull Members Up… dialog handles fields and methods directly; for constructor bodies, the usual workflow is two steps — use Extract Method on the shared opening lines, then pull that method up, or create the superclass constructor and let the IDE's super() generation and parameter hints do the wiring. IntelliJ also auto-suggests adding the super(...) call when you write a subclass of a parent whose constructor requires arguments.
  • Visual Studio (C#): Ctrl+. Quick Actions help with the surrounding moves — "Pull members up to base type" for the fields, "Generate constructor" on the base class, and "Add parameter to constructor" refactorings to thread new parameters through base(...) calls. The compiler error on a missing base(...) call then guides you through each subclass — a rare case where a compile error is the checklist.
  • ReSharper: combines well here — Extract Method on the shared block, Pull Members Up on the result, then inline the call sites; plus inspection warnings ("virtual member call in constructor") that police the trap from our warning box.
  • Eclipse (Java): Refactor → Pull Up… can move methods; the constructor-body move itself is typically manual, guided by the compiler's "must call super first" rules.

In every IDE, the safety net is the same: the compiler forces correct chaining, and your tests confirm behaviour. Lean on both.

⚖️ Benefits and risks

BenefitsRisks / costs
Shared initialization written once — N copies of the opening collapse into onesuper()/base() must run first; logic that needs child-first order cannot use plain chaining (use the protected-init fallback)
Invariants enforced by the language, not by convention — no subclass can skip validationA virtual call inside the pulled-up constructor can run subclass code on a half-built object
Every object in the hierarchy is born from the same correct baselineBase constructor's parameter list grows; threading a new shared parameter touches every subclass signature once
Subclass constructors shrink to their honest, subclass-only concernsPulling up setup only one subclass needs burdens the rest — Refused Bequest, constructor edition
Completes the Pull Up Field story — data and its birth ritual live togetherSlightly more ceremony to read for beginners unfamiliar with chaining

A quick comparison of the three pull-up refactorings, since this post completes the upward set:

Pull Up FieldPull Up Constructor BodyPull Up Method
What movesA data declarationThe shared opening of constructorsA whole behaviour
Why it is specialVisibility usually widens to protectedConstructors chain instead of inherit; super()/base() must be firstBodies must be unified before moving
Usual orderFirstSecondThird
School pictureThe address on the office boardThe morning opening before first periodThe leave-application sheet

On directions: Pull Up Constructor Body belongs to the same upward family as Pull Up Field and Pull Up Method, and its mirror situation is handled by the push-down family — if a parent constructor is doing setup that only one child actually needs (filling poolLane for everyone, say), that setup should move down into the child, just as Push Down Field moves misplaced data down. The compass never changes: shared by all → upstairs, once; needed by some → downstairs, where it is used. Constructors simply add one extra rule to the compass: whatever goes upstairs must be able to run first.

🩺 Which smells does it cure?

SmellHow Pull Up Constructor Body helps
Duplicate CodeThe cure for its sneakiest hiding place — initialization logic repeated across sibling constructors
Shotgun SurgeryAdding or changing a shared field's setup now touches one constructor, not every subclass
Inconsistent state bugsNot a catalog smell but the real disease: objects born with drifted invariants (7B's unvalidated room) become impossible
Refused BequestIndirect: keeping subclass-only setup out of the shared constructor prevents forcing it on siblings

📦 Quick revision box

+--------------------------------------------------------------+
|             PULL UP CONSTRUCTOR BODY — REVISION              |
+--------------------------------------------------------------+
| Story    : Every section opens with prayer + attendance +    |
|            date, then its OWN first period. Drifted cards    |
|            -> 7B lost a week of attendance.                  |
|            Fix: ONE shared "Morning Opening", sections add   |
|            only their own part after it.                     |
|                                                              |
| Move     : Shared opening of sibling constructors            |
|            ---> superclass constructor, called by            |
|            super(...) / base(...) as the FIRST step          |
|                                                              |
| Why special: constructors are NOT inherited, they are        |
|              CHAINED -> plain Pull Up Method cannot work     |
|                                                              |
| Safe steps : Pull Up Field first -> gather shared lines at   |
|              the TOP -> create protected parent ctor ->      |
|              replace block with super(...) one subclass at   |
|              a time, testing each                            |
|                                                              |
| Traps      : super/base must be first; NEVER call virtual    |
|              methods from a constructor (half-built object)  |
| Fallback   : protected init() method when order won't fit    |
| Cures      : Duplicate Code in initialization; drift-born    |
|              invariant bugs                                  |
+--------------------------------------------------------------+

✏️ Practice exercise

The school's transport software needs your help. Three bus-route classes were written by three different people:

abstract class BusRoute {
  protected driverName!: string;
  protected busNumber!: string;
  protected stops!: string[];
}
 
class MorningRoute extends BusRoute {
  private departureTime: string;
  constructor(driver: string, bus: string) {
    super();
    if (driver === "") throw new Error("Driver required");
    this.driverName = driver;
    this.busNumber = bus;
    this.stops = [];
    this.departureTime = "07:00";
  }
}
 
class EveningRoute extends BusRoute {
  private departureTime: string;
  constructor(driver: string, bus: string) {
    super();
    this.driverName = driver;      // validation missing — drift!
    this.busNumber = bus;
    this.stops = [];
    this.departureTime = "15:30";
  }
}
 
class ExcursionRoute extends BusRoute {
  private destination: string;
  constructor(driver: string, bus: string, destination: string) {
    super();
    if (driver === "") throw new Error("Driver required");
    this.driverName = driver;
    this.busNumber = bus;
    this.stops = [];
    this.destination = destination;
  }
}

Your tasks:

  1. Underline the shared opening in each constructor. Which lines appear in all three? Which line drifted out of one copy, and what bug does that allow today?
  2. Design the superclass constructor: what parameters does it need, and what visibility should it have?
  3. Perform the refactoring in the safe order: create the parent constructor, then convert one route class to super(driver, bus), "run the tests", and only then convert the next.
  4. departureTime appears in MorningRoute and EveningRoute but not ExcursionRoute. Should it move into the shared constructor? Use the compass from this post to defend your answer. (Hint: shared by all, or only by some?)
  5. Bonus trap-check: a teammate suggests the base constructor should call this.announceRoute(), a method each subclass overrides to print its details. Explain, using the half-built-object trap, why every ExcursionRoute announcement would print undefined as the destination.

When every bus route is born through one validated opening, and each subclass constructor contains only its own personality, you have completed the upward journey: fields, then constructor bodies, then methods. Next post, we turn the compass around and learn when members should travel down instead — starting with Push Down Field.

Frequently asked questions

Why can't I just use Pull Up Method on a constructor?
Because constructors are not inherited the way ordinary methods are — they are chained. A subclass cannot simply inherit the parent's constructor and stop declaring its own; each subclass constructor must exist and must call the parent's constructor. So instead of moving the whole constructor up, we move only its shared body into a superclass constructor and call it with super() or base().
Why must super() or base() be the first thing in the subclass constructor?
Languages like Java, C#, and TypeScript require the parent to be fully initialized before the child adds its own setup, so the parent constructor runs first. TypeScript additionally forbids touching 'this' before super() returns. This rule guarantees the child never builds on top of an uninitialized parent.
What if the shared code must run after some subclass-specific setup?
Then pure constructor chaining cannot express it, because super()/base() must come first. The standard fallback is to extract the shared logic into a protected method on the parent — for example a protected init() or Initialize() — and let each subclass constructor call it at the right moment after its own pre-setup.
Why is calling a virtual or overridable method inside a constructor dangerous?
Because the subclass override can run before the subclass's own fields are initialized. The parent constructor executes first, calls the virtual method, and the subclass version runs against a half-built object, often reading undefined or default values. Keep constructor bodies free of overridable calls, or make those methods non-virtual.
Which refactoring should I do before Pull Up Constructor Body?
Pull Up Field. The fields being assigned in the shared initialization must already live in the superclass before their assignment can move there. The usual order in a hierarchy cleanup is: Pull Up Field, then Pull Up Constructor Body, then Pull Up Method.

Further reading

Related Lessons