First exams 2027 · HL only

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

Polymorphism and method overriding

One instruction, sent to many different kinds of object, and each one answers it in the way that is right for itself.

HL only Typically 4–6 marks · AO2/AO3 Command term: construct

Explainer

Kirkstall Print Works runs one queue for everything it prints: posters, stapled booklets, cut-out stickers. When the manager presses "quote this queue", every job has to produce a price, but no two job types are priced the same way. Posters go by sheet, booklets by page count times copies, stickers add a fixed die-cutting fee on top. The queue code should not have to know any of that. It should ask each job for its price and trust the job to answer correctly.

Df

Polymorphism (from the Greek for "many forms") is the ability of objects of different classes to respond to the same method call, each in the way appropriate to its own class. The caller uses one name; which code actually runs depends on the object.

This is what makes the queue loop flexible and the code reusable: one loop handles every job type that exists today, and keeps working unchanged for every job type added later. Without it, that loop would be a growing chain of type checks that has to be edited every time the business offers something new.

Explainer

Two forms, decided at two different times

The syllabus asks for both. They share the "one name, several behaviours" idea, but they differ in when the decision about which code to run gets made.

1

Dynamic (runtime) polymorphism is achieved by method overriding. A child class provides its own version of a method that already exists in its parent, with the same name and the same parameters. Which version runs is decided while the program is running, based on what the object actually is.

2

Static (compile-time) polymorphism is achieved by method overloading. One class provides several methods sharing a name but taking different parameter lists. Which version runs is decided before the program ever runs, by matching the arguments at the call site to a signature.

The one-line distinction to memorise

Overriding: same name, same parameters, different class, chosen at runtime. Overloading: same name, different parameters, same class, chosen at compile time. Mixing these two up is the single most common error on this statement.

Worked example

Dynamic polymorphism: overriding estimate_cost()

Each job type replaces the parent's pricing method with its own. Note that every version has an identical signature, which is exactly what makes it overriding rather than overloading.

# the shared shape, and a fallback price
class PrintJob:
    def __init__(self, ref, copies):
        self.ref = ref
        self.copies = copies

    def estimate_cost(self):
        return self.copies * 0.10


class PosterJob(PrintJob):
    def estimate_cost(self):              # SAME name, SAME parameters
        return self.copies * 2.50


class BookletJob(PrintJob):
    def __init__(self, ref, copies, pages):
        super().__init__(ref, copies)
        self.pages = pages

    def estimate_cost(self):              # SAME name, SAME parameters
        return self.copies * self.pages * 0.08
// the shared shape, and a fallback price
public class PrintJob {
    protected String ref;
    protected int copies;

    public double estimateCost() {
        return copies * 0.10;
    }
}

public class PosterJob extends PrintJob {
    @Override
    public double estimateCost() {        // SAME name, SAME parameters
        return copies * 2.50;
    }
}

public class BookletJob extends PrintJob {
    private int pages;

    @Override
    public double estimateCost() {        // SAME name, SAME parameters
        return copies * pages * 0.08;
    }
}

Java marks the intention with @Override. It is optional, but it asks the compiler to check that a method with this exact signature really does exist in the parent, which turns a silent typo into a caught error.

Worked example

One call, three different answers

The quoting loop is four lines long and mentions no job type at all.

# the queue holds three different classes, and the loop knows about none of them
queue = [PosterJob("KP-118", 6), BookletJob("KP-119", 25, 12), StickerJob("KP-120", 40)]

for job in queue:
    print(job.ref, job.estimate_cost())

Click a job in the queue to see which class’s method actually ran.

job.estimate_cost() ← the identical call, every time

Object’s class

Method that runs

Result

Why this is the reusability point

Adding a fourth job type next year means writing one new class. The quoting loop, the invoice printer, and every other place that walks the queue keep working untouched, because none of them ever asked what kind of job they were holding.

Explainer

Static polymorphism: overloading

The print works also wants two ways to ask for a quote: the plain one, and one that applies a rush surcharge. Same idea, same name, different information supplied.

In Java, this is real overloading

// three methods, one name, three different parameter lists
public class Quote {
    public double total(int copies) {
        return copies * 0.10;
    }

    public double total(int copies, boolean rush) {
        return copies * 0.10 * (rush ? 2 : 1);
    }

    public double total(int copies, int pages) {
        return copies * pages * 0.08;
    }
}

// the compiler picks the right one from the arguments, before the program runs
q.total(40);          // 4.00   picks version 1
q.total(40, true);    // 8.00   picks version 2
q.total(25, 12);      // 24.00  picks version 3

This is where the efficiency comes from: the decision costs nothing at runtime because it was already made during compilation, and callers get one obvious name to remember instead of totalPlain, totalRush and totalByPage.

In Python, it is not

Python does not support method overloading

Writing two methods with the same name does not give you two methods. The second definition simply replaces the first, silently, with no error. Python decides what to call by name alone, so a name can only ever refer to one thing at a time.

# this does NOT create two versions
class Quote:
    def total(self, copies):
        return copies * 0.10

    def total(self, copies, rush):        # this REPLACES the one above
        return copies * 0.10 * (2 if rush else 1)

q = Quote()
q.total(40, True)   # 8.00, this works
q.total(40)         # TypeError: the first version no longer exists

Python reaches the same goal a different way: one method with default parameter values, so the caller may supply the extra information or leave it out.

# one method, several ways to call it: Python's answer to overloading
class Quote:
    def total(self, copies, pages=1, rush=False):
        cost = copies * pages * 0.08 if pages > 1 else copies * 0.10
        return cost * 2 if rush else cost

q.total(40)                # 4.00
q.total(40, rush=True)     # 8.00
q.total(25, 12)            # 24.00

Same three call shapes, same three answers, one definition. If a question asks you to apply static polymorphism and you are writing Python, say plainly that Python has no true overloading and show the default-parameter equivalent: that earns the mark, whereas two identical method names does not.

Explainer

Comparing the two forms

Dynamic · overriding

Real example: PosterJob.estimate_cost() replacing PrintJob.estimate_cost(), chosen when the loop reaches that object.

Advantage

  • New types plug in without touching existing code (the quoting loop never changes, so adding a job type cannot break the jobs that already work)

Disadvantage

  • Harder to follow when reading (the line says estimate_cost(), but which of four versions runs is only knowable at runtime, so a debugger is often needed to be sure)
Static · overloading

Real example: total(copies) and total(copies, rush) in the same Java class, chosen by the compiler.

Advantage

  • No runtime cost and one name to learn (the choice is fixed during compilation, and callers avoid memorising three near-identical method names)

Disadvantage

  • Fixed at compile time, and not universal (it cannot adapt to what an object turns out to be, and Python does not offer it at all)

Try it yourself

Watch the right version get chosen

The Kirkstall classes, live and editable. Two experiments worth running: add a fourth job class to the queue without touching the loop, and uncomment the final block to watch Python quietly discard a method when you try to overload it.

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 overriding, dynamic dispatch and Python’s replace-not-overload behaviour, but it isn’t full Python.

Check your understanding

A class defines total(int copies) and total(int copies, boolean rush). A subclass then defines total(int copies) again. Which forms of polymorphism are present?

Common mistake

Markers reported this pattern for B3.2.2

RecurringOverriding and overloading are swapped constantly. Because the words look alike, candidates describe one and name the other, and the mark is lost on the label rather than on the understanding. The deciding detail is the parameter list: identical means overriding, different means overloading.

A second error appears in Python answers: writing two methods with the same name and different parameters, and calling it overloading. Python accepts the code, so it looks right, but only the second definition survives.

Answer frame that scores: name the mechanism → state whether the parameter lists match → say when the choice is made, at compile time or at runtime.

✕

What a weak answer looks like: “PosterJob overloads estimate_cost() so it can calculate its own price.” The mechanism described is right, the name given to it is wrong, and it is the name being examined.

✓

What it should say: “PosterJob overrides estimate_cost(): it declares the method with the same name and the same parameters as PrintJob, so at runtime the version belonging to the actual object is the one that executes.” Mechanism named, signature stated, timing given.

Revision questions

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

Question 1 · 2 marks

[2]Using the Kirkstall queue, distinguish between method overriding and method overloading.

Question 2 · 4 marks

[4]The print works adds laminated menus, priced at 1.20 per copy plus a fixed 6.00 setup charge. Construct the class so the existing quoting loop prices it without modification.

Question 3 · 3 marks

[3]A student writes two Python methods named total() with different parameter lists and states that this demonstrates static polymorphism. Explain why this is incorrect, and describe what Python does instead.

Flashcards

Active recall for this statement’s key terms.