First exams 2027 · HL only

B3.2.1 · Fundamentals of OOP for multiple classes (HL)

Inheritance and code reusability

Writing the shared part once, then letting specialised classes take it and build on top of it.

HL only Typically 4–6 marks · AO2/AO3 Command terms: explain, apply

Explainer

Ravensworth Climbing Centre logs every route on its walls. A boulder problem and a roped route are genuinely different things: one records the depth of matting underneath it, the other records how many bolts it has and whether it runs over more than one pitch. But both have a route code, a grade, the name of whoever set it, and a count of how many times it has been climbed. Writing two separate classes means writing that shared half twice, and fixing every future bug in it twice.

Df

Inheritance lets a new class start from an existing one, automatically receiving its attributes and methods, so that only the differences have to be written. The shared code lives in one place and is reused rather than repeated.

Df

The class being inherited from is the parent class (also called the superclass or base class). The class doing the inheriting is the child class (also called the subclass or derived class). The relationship is hierarchical: children sit below their parent, and a child can itself be the parent of another class further down.

The test for whether inheritance is the right tool is the is-a question. A boulder problem is a route, so BoulderRoute inheriting from Route is sound. A climbing wall is not a route, it has routes, so that relationship is not inheritance at all.

Worked example

One parent, two children

Everything in the parent is available to both children without being rewritten. The greyed italic rows below are not typed out again in the child classes, they arrive automatically.

The hollow triangle always points at the parent.

Route
+ code : string
+ grade : string
# timesClimbed : int
− inspectionLog : string
+ logClimb()
+ describe() : string
BoulderRoute
+ code, grade
# timesClimbed
+ matDepth : int
+ logClimb()
+ describe() : string
RopeRoute
+ code, grade
# timesClimbed
+ bolts : int
+ logClimb()
+ describe() : string
+ public # protected − private italic grey inherited, not rewritten

Notice what is not italic in the children: matDepth and bolts are the genuinely new parts, and describe() appears in both children in normal type because each one replaces the parent's version with its own. That replacement is called overriding.

Explainer

Reusing, and then extending

Inheritance is not only about taking a copy. A child normally does three things: it accepts what it wants unchanged, it adds what is new, and it replaces what needs to behave differently.

# the shared half, written once
class Route:
    def __init__(self, code, grade, setter):
        self.code = code
        self.grade = grade
        self._times_climbed = 0          # protected by convention
        self.__inspection_log = "clear"   # private to Route

    def log_climb(self):
        self._times_climbed += 1

    def describe(self):
        return f"{self.code} ({self.grade})"


class BoulderRoute(Route):          # inherits everything above
    def __init__(self, code, grade, setter, mat_depth):
        super().__init__(code, grade, setter)   # run the parent's setup first
        self.mat_depth = mat_depth               # ADD what is new

    def describe(self):                       # REPLACE the parent's version
        return super().describe() + f", {self.mat_depth}cm matting"
// the shared half, written once
public class Route {
    public String code;
    public String grade;
    protected int timesClimbed;        // reachable by subclasses
    private String inspectionLog;      // NOT reachable by subclasses

    public void logClimb() {
        timesClimbed++;
    }

    public String describe() {
        return code + " (" + grade + ")";
    }
}

public class BoulderRoute extends Route {   // inherits everything above
    public int matDepth;

    public BoulderRoute(String code, String grade, int matDepth) {
        super(code, grade);            // run the parent's constructor first
        this.matDepth = matDepth;      // ADD what is new
    }

    @Override
    public String describe() {         // REPLACE the parent's version
        return super.describe() + ", " + matDepth + "cm matting";
    }
}

Python marks inheritance with parentheses after the class name; Java uses the extends keyword. Both call the parent's constructor first through super, because the parent's own setup has to finish before the child adds anything of its own.

The reusability point examiners want

log_climb() is written once, in Route. Both children get it, and a third route type added next year gets it too, with no new code. If the counting rule ever changes, it changes in exactly one place rather than in every route class that exists.

Worked example

Where a method is actually found

Calling boulder.log_climb() works even though BoulderRoute never defines it. Step through what the interpreter does to find it.

Following the chain upwards Step 1 of 4

Step 1. The call is made on a child object. boulder was built from BoulderRoute, so that is where the search starts.

boulder.log_climb()

Step 2. Look on the child class first. BoulderRoute defines __init__ and describe, but no log_climb. The search does not stop here, it moves up.

BoulderRoute: __init__ describe no log_climb

Step 3. Move up to the parent. Route does define log_climb, so the search stops here. This is the whole mechanism behind reuse: nothing was copied into the child, the lookup simply continues upwards.

BoulderRoute: __init__ describe no log_climb Route: log_climb describe found

Step 4. Run it, with self still the child object. The method lives on Route, but it operates on the boulder object, so it is that object's own counter that goes up. Had BoulderRoute defined its own log_climb, the search would have stopped at Step 2 and the parent's version would never have run: that is what overriding means.

Route.log_climb(self = the BoulderRoute object) self._times_climbed += 1 # the boulder's own count, now 1

Explainer

What a child can and cannot reach

Inheriting a class does not mean inheriting permission to touch all of it. Each access modifier answers the question differently, and this is the part of the statement most often examined. Click through all four.

The child class

Unrelated outside code

Java:
Python:
The one that catches people out

Private members are inherited, but not accessible. A BoulderRoute object really does carry the parent's private inspection log inside it, and the parent's own public methods can still read it. What the child class cannot do is write code that touches it directly. Inherited and reachable are two different things, and saying "private members are not inherited" is the version that loses the mark.

Try it yourself

Test the boundary yourself

The Ravensworth classes, live and editable. The last line is commented out on purpose: uncomment it to watch a child class get refused access to its own parent's private attribute.

main.py
Loading Python engine…
Output
Press Run to see the output here.

Running in offline fallback mode. The full Python engine couldn't load (blocked school network, or no internet), so this console switched automatically to a small hand-written engine. It runs this page's scenario, including inheritance, super() and the private-versus-protected boundary, but it isn't full Python.

Check your understanding

Route declares inspectionLog as private and timesClimbed as protected. Inside BoulderRoute, which of these is true?

Common mistake

Markers reported this pattern for B3.2.1

RecurringCandidates state that private members are not inherited. They are: the child object genuinely contains them, and the parent's own public methods can still operate on them. What the child class cannot do is refer to them directly in its own code. Answers that confuse inherited with accessible lose the mark even when the surrounding explanation is sound.

The second recurring error is on reusability questions: describing inheritance as "it saves typing." The creditworthy version names the maintenance consequence, that shared behaviour is defined once so a change to it happens in one place.

Answer frame that scores: name the modifier → say whether the child may access it directly → say what still happens to the member regardless.

✕

What a weak answer looks like: “Private variables are not inherited by the subclass, so BoulderRoute does not have inspectionLog.” The second half is simply untrue, and it is the half that decides the mark.

✓

What it should say: “inspectionLog is private, so although every BoulderRoute object contains it, code written inside BoulderRoute cannot access it directly and must go through a public method of Route. timesClimbed is protected, so BoulderRoute can use it directly.” Modifier named, access stated, and the distinction between holding and reaching made explicit.

Revision questions

Past-paper style. Write an answer, then reveal the marking points and mark yourself.

Question 1 · 2 marks

[2]Identify the parent class and one child class in the Ravensworth hierarchy, and state one attribute the child receives without declaring it.

Question 2 · 4 marks

[4]The centre adds indoor speed lanes, which have a route code, a grade and a climb count like every other route, plus a recorded fastest time. Construct the class, using inheritance.

Question 3 · 3 marks

[3]RopeRoute needs to read inspectionLog, which is private in Route. Explain why it cannot do so directly, and outline one way to give it proper access.

Flashcards

Active recall for this statement’s key terms.