First exams 2027 · SL & HL

B3.1.1 · Fundamentals of OOP for a single class

The fundamentals of object-oriented programming

Five concepts, one recurring pattern: model the real world as things that hold their own data and know how to act on it.

Core for SL & HL Typically 3–5 marks · AO2/AO3 Command term: evaluate

Explainer

A smart-home app has to track a front-door lock, a thermostat, and a hallway light. All three are switched on or off. All three log a battery level. But locking a door and dimming a light are completely different actions. Writing one giant function full of if device_type == "lock" checks to handle every device would work, but it would grow unmanageable the moment a fourth device type showed up. Object-oriented programming (OOP) solves this by building each device as its own self-contained unit that knows how to behave.

Df

Object-oriented programming is a programming paradigm that models a system as a collection of interacting objects. Each object holds its own state, and the only code allowed to change that state lives inside the object itself, rather than in one long sequence of procedures all reaching into shared data.

The whole paradigm rests on five ideas that keep showing up together: classes and objects (the blueprint and the real things built from it), inheritance (sharing a blueprint between related things), encapsulation (each thing guarding its own data), and polymorphism (related things responding to the same instruction in their own way).

Worked example

Five concepts, one smart-home app

Every concept below is inspected through the same running scenario: a Device blueprint, and three real devices built from variations on it. Click through them in any order.

Class: the blueprint

A class defines what every device of that kind will have and be able to do, without describing any one real device yet. Nothing has been switched on or off until an actual object exists.

# the blueprint: no real device exists yet, just the shape every Light will have
class Light:
    def __init__(self, device_id):
        self.device_id = device_id
        self.is_on = False
        self.brightness = 0

    def toggle(self):
        self.is_on = not self.is_on

In this app: writing class Light: once defines every light the app will ever create, from one blueprint.

Object: one real thing built from the blueprint

An object (an instance of the class) is one concrete device with its own state. Creating two objects from the same class gives two independent things, changing one never touches the other.

# two separate Light OBJECTS, each with its own independent state
hallway_light = Light("L01")
kitchen_light = Light("L02")

hallway_light.toggle()  # hallway_light.is_on becomes True
# kitchen_light.is_on is still False, it is a completely separate object

In this app: hallway_light and kitchen_light share the same class, but toggling one never affects the other.

Inheritance: sharing a blueprint between related things

A lock, a thermostat, and a light are all still “devices”: each has a device_id, a battery level, and can be toggled. Rather than writing that shared behaviour three times, a general Device class defines it once, and Light, Thermostat, and Lock each inherit from it, adding only what makes them different.

# Device holds what every device has in common
class Device:
    def __init__(self, device_id):
        self.device_id = device_id
        self.is_on = False
        self.battery_level = 100

    def toggle(self):
        self.is_on = not self.is_on

# Light INHERITS device_id, is_on, battery_level and toggle() for free
class Light(Device):
    def __init__(self, device_id):
        super().__init__(device_id)
        self.brightness = 0

In this app: Light(Device) means every light already has an ID, a battery level, and can toggle, none of that had to be rewritten.

Encapsulation: each device guards its own data

The battery level is stored inside the device itself, not as a value floating around the rest of the program. Reading or draining it happens through the device's own methods, so the device can enforce its own rule: it never reports a battery level below zero.

# battery_level is only ever touched through the device's own methods
class Device:
    def __init__(self, device_id):
        self.device_id = device_id
        self.__battery_level = 100  # hidden

    def drain_battery(self, amount):
        self.__battery_level = max(0, self.__battery_level - amount)

    def get_battery_level(self):
        return self.__battery_level

In this app: no code outside Device can set __battery_level to a negative number, since nothing outside the class can touch it directly at all.

Polymorphism: the same instruction, a different response

Every device understands get_status(), but each kind of device answers it in its own way: a light reports brightness, a thermostat reports temperature, a lock reports whether it's locked. The caller sends the same message to any device without needing to know which kind it is.

# same method NAME, three different device-specific IMPLEMENTATIONS
class Light(Device):
    def get_status(self):
        return f"brightness {self.brightness}%"

class Thermostat(Device):
    def get_status(self):
        return f"set to {self.target_temp} deg"

class Lock(Device):
    def get_status(self):
        return "locked" if self.is_locked else "unlocked"

In this app: calling device.get_status() works identically for any device, but which version actually runs depends on which kind of device it is.

Worked example

Why this matters: all five together

The real payoff shows up once several different device objects need to be handled the same way. This is the exact scenario from the explainer at the top of the page, solved properly.

# one loop, three different device TYPES, zero if/elif chains
devices = [Light("L01"), Thermostat("T01"), Lock("K01")]

for device in devices:
    print(device.device_id, ":", device.get_status())

# adding a fourth device type later needs no change to this loop at all

Each object (instantiated from a class that inherits from Device) hides its own state (encapsulation) and responds to get_status() in its own way (polymorphism). The loop never has to ask “what kind of device is this,” it just trusts every object to know how to answer.

The pattern to remember

Class defines the shape. Object is one real thing with that shape. Inheritance lets related shapes share what's common. Encapsulation keeps each object's data safe inside it. Polymorphism lets the same instruction get a different, correct answer from each kind of object.

Try it yourself

Edit the smart-home scenario and run it

This is the same Device/Light/Thermostat/Lock code from above, live and editable. Try adding a fourth device class (a Fan, say) without touching the loop, that's the whole payoff of polymorphism made concrete.

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 can still run this page's own scenario, classes, inheritance, self/super(), if/elif/else, loops over a list, but it isn't full Python.

Check your understanding

In the smart-home example, which OOP concept lets a single loop call device.get_status() on a Light, a Thermostat, and a Lock, and get the correct kind of answer from each, without the loop ever checking the device's type?

Common mistake

Markers reported this pattern for B3.1.1

RecurringCandidates frequently swap the words class and object, describing a class as “a single device” or an object as “the blueprint,” which loses marks even when the surrounding explanation shows real understanding of the mechanism.

Answer frame that scores: name the class as the definition/template → name the object as one specific instance created from it → give a concrete example of each from the scenario in the question.

✕

What a weak answer looks like: “Light is an object that stores the brightness of the hallway light.” This calls the class an object, and describes ONE specific light as if it were the general definition.

✓

What it should say: “Light is the class: it defines that every light has a brightness and can be toggled. hallway_light is an object, one specific instance of Light, with its own actual brightness value.” Class and object are named correctly and tied to a concrete example.

Explainer

Advantages and disadvantages of choosing OOP

“Evaluate” questions on this statement expect a real trade-off, not a one-sided list, and B3's own guiding question asks exactly this: is OOP the right paradigm here, or would a simpler approach do?

Procedural (no classes)

Real example: one handle_device(device_type, state) function with an if/elif chain for every device type.

Advantage

  • Fast to write for a small, fixed set of cases (no classes, no inheritance hierarchy to design first)

Disadvantage

  • Every new device type means editing the same shared function (a fourth device type forces a change to code that already works for the first three, risking a new bug in previously-tested logic)
Object-oriented

Real example: the Device/Light/Thermostat/Lock hierarchy above, called through one polymorphic loop.

Advantage

  • A new device type is a new class, added without touching existing ones (the loop that calls get_status() never changes, so working code for Light and Thermostat can't be broken by adding Lock)

Disadvantage

  • More design effort upfront (deciding what belongs in the shared Device class versus each subclass takes real planning before any device-specific code is written)

The trade-off is scale versus overhead: OOP earns its cost back once a system has multiple related, evolving entity types, which is exactly the kind of complex, growing problem B3's guiding question is asking about. A one-off script with a single fixed behaviour rarely needs it.

Linking question

How this connects to the rest of the syllabus

The IB syllabus is deliberately woven together, not a stack of isolated topics. Here's how OOP connects specifically to B2 (data structures and algorithms).

B2

Is OOP necessary for all programming, or only in modelling complex situations? (The smart-home example above shows the actual trade-off directly: a single fixed algorithm rarely benefits from classes at all, but a system of several related, evolving entity types is exactly where the extra design effort pays off.)

Revision questions

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

Question 1 · 2 marks

[2]Distinguish between a class and an object, using the Light class as an example.

Question 2 · 3 marks

[3]Outline how inheritance reduces duplicated code across the Light, Thermostat, and Lock classes.

Question 3 · 4 marks

[4]Evaluate whether an object-oriented approach is appropriate for a program that only ever needs to reverse a single list of numbers.

Flashcards

Active recall for this statement’s key terms.