First exams 2027 · SL & HL

B3.1.3 · Fundamentals of OOP for a single class

Static and non-static variables and methods

Some data belongs to one object. Some data belongs to the whole class. Telling them apart is the entire statement.

Core for SL & HL Typically 2–4 marks · AO1/AO2 Command term: distinguish

Explainer

Kestrel Cycles rents out forty bikes. Every bike has its own ID sticker and its own condition, worn brakes on one, brand new tyres on another. But every bike is rented at the same hourly rate. If that rate were stored separately inside all forty bike objects, raising the price would mean updating forty copies and hoping none were missed. Storing it once, at the level of the class itself, means one change updates every bike at once.

Df

An instance variable (non-static) belongs to one individual object. Every object created from the class gets its own separate copy, and changing one object's copy leaves every other object untouched.

Df

A class variable (static) belongs to the class itself, not to any object. Only one copy exists no matter how many objects are created, and every object sees that same single copy.

The words static and class-level mean the same thing here, as do non-static and instance-level. Java writes the keyword static explicitly; Python marks the distinction by where the variable is written, directly inside the class body for a class variable, or on self for an instance variable.

# Kestrel Cycles: what belongs to the class, and what belongs to each bike
class Bike:
    hourly_rate = 5.00      # CLASS variable: one copy, shared by every bike
    total_bikes = 0         # CLASS variable: counts every Bike ever made

    def __init__(self, bike_id):
        self.bike_id = bike_id       # INSTANCE variable: this bike only
        self.condition = "good"    # INSTANCE variable: this bike only
        Bike.total_bikes += 1      # updates the ONE shared copy

The highlighted lines sit directly in the class body, outside any method, which is exactly what makes them class variables. The two self. lines run once per object, giving each bike its own copy.

Explainer

The same split applies to methods

Methods divide the same way, and for the same reason: does this behaviour need to know which particular object it is working on?

Df

An instance method (non-static) receives the object it was called on as its first parameter, self, so it can read and change that specific object's data. It must be called on an object: bike_a.rental_cost(3).

Df

A static method receives no self, so it cannot touch any individual object's data. It is called on the class itself: Bike.raise_rate(6.50). It exists inside the class because it belongs there conceptually, not because it needs an object.

# both live in the Bike class, but only one of them needs a specific bike
class Bike:
    hourly_rate = 5.00

    def rental_cost(self, hours):      # INSTANCE method: takes self
        return hours * Bike.hourly_rate

    @staticmethod
    def raise_rate(new_rate):         # STATIC method: no self at all
        Bike.hourly_rate = new_rate

raise_rate() changes the shared rate for the whole fleet, so asking "which bike?" would make no sense. rental_cost() is written as an instance method because in a fuller version it would apply a discount based on that bike's own condition.

Explainer

Usage and scope: what each one can reach

Scope is where the syllabus puts real weight, and it is not symmetrical. An instance can see everything; the class level cannot see any individual object.

Can it reach…Instance variable / methodClass variable / static method
Its own object's dataYes, through selfNo, there is no object to look at
The shared class dataYes, through the class nameYes, that is what it works on
How many copies existOne per object createdExactly one, always
How it is normally calledOn an object: bike_a.method()On the class: Bike.method()
When memory is set asideEach time an object is createdOnce, when the class is first loaded
The asymmetry worth memorising

An instance method can freely use class variables, because the class exists whenever an object does. A static method cannot use instance variables, because it has no idea which object you would even mean. That one-way rule explains most static-related errors.

Worked example

What memory actually holds, step by step

Follow the same Kestrel Cycles class as objects get created and changed. Watch which box changes at each step: the single shared one, or one bike's own.

Tracing the shared and separate copies Step 1 of 5

Step 1. The class is defined. No objects exist yet, but the class variables already do, they were set aside the moment the class loaded.

class Bike: ...
Bike (class level, one copy only)
hourly_rate = 5.00
total_bikes = 0
No objects created yet

Step 2. The first bike is created. It gets its own box holding its own bike_id and condition. The shared counter goes up, but notice hourly_rate is not copied into the bike's box.

bike_a = Bike("KC-01")
Bike (class level, one copy only)
hourly_rate = 5.00
total_bikes = 1
bike_a (object)
bike_id = "KC-01"
condition = "good"
no hourly_rate of its own

Step 3. A second bike is created. A second, completely separate object box. The counter rises again, on the same single shared copy.

bike_b = Bike("KC-02")
Bike (class level)
hourly_rate = 5.00
total_bikes = 2
bike_a
bike_id = "KC-01"
condition = "good"
bike_b
bike_id = "KC-02"
condition = "good"

Step 4. Change one bike's instance variable. Only that bike's box changes. This is the behaviour students expect, and it is the easy half.

bike_a.condition = "worn"
Bike (class level)
hourly_rate = 5.00
total_bikes = 2
bike_a
bike_id = "KC-01"
condition = "worn"
bike_b
bike_id = "KC-02"
condition = "good"
completely unaffected

Step 5. Change the class variable. One assignment, and every bike in the fleet now quotes the new price, because no bike ever had its own copy to fall back on. This is the half that catches people out.

Bike.hourly_rate = 6.50
Bike (class level)
hourly_rate = 6.50
total_bikes = 2
bike_a
bike_id = "KC-01"
rental_cost(3) is now 19.50
bike_b
bike_id = "KC-02"
rental_cost(3) is now 19.50

Explainer

Choosing between them

The syllabus asks specifically when to use an instance variable rather than a class variable. The deciding question is short: would every object always agree on this value?

Instance variable (non-static)

Real example: self.condition on each Kestrel bike, since one bike's brakes wearing out says nothing about the other thirty-nine.

Advantage

  • Each object keeps its own independent state (objects cannot corrupt each other, so a bug in handling one bike cannot silently change the rest of the fleet)

Disadvantage

  • Genuinely shared values get duplicated (storing the rate on all forty bikes means forty places to update, and any missed one becomes a pricing bug that is hard to trace)
Class variable (static)

Real example: Bike.hourly_rate, one price the whole fleet is rented at, and Bike.total_bikes, a count that belongs to no single bike.

Advantage

  • One value, updated in exactly one place (a price change is a single assignment that every existing and future object picks up automatically)

Disadvantage

  • Every object is affected by any change (code anywhere in the program can change it, and a value that later needs to differ per object requires redesigning the class)

The rule of thumb: use an instance variable by default, and reach for a class variable only when the value genuinely describes the class as a whole, a shared rate, a running count of objects, or a fixed constant. A value that might one day differ per object should start as an instance variable.

Try it yourself

Run the Kestrel Cycles class

The same class from the stepper, live and editable. Try changing bike_a.hourly_rate = 9.00 instead of Bike.hourly_rate, that single character difference creates a brand new instance variable that shadows the shared one for that bike alone, which is exactly the trap in the mistake callout below.

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 class variables and static methods, but it isn't full Python: one visible difference is that whole-number floats print as 5 here rather than Python's 5.0.

Check your understanding

Two Bike objects exist. A program runs bike_a.hourly_rate = 9.00, then prints bike_b.hourly_rate. What is printed, and why?

Common mistake

Markers reported this pattern for B3.1.3

RecurringCandidates describe a class variable as being "shared" without stating the point that earns the mark: only one copy exists in memory, so there is nothing per-object to update. Answers that stop at "all objects can use it" describe access, not storage, and read as vague.

The second recurring error is writing to a class variable through an object. In Python this quietly creates a new instance variable that hides the shared one for that object, so the program keeps running and produces wrong values with no error message at all.

Answer frame that scores: say how many copies exist → say who they belong to → state the consequence of changing one.

✕

What a weak answer looks like: “A static variable is shared between all the objects of the class.” True but thin: it never says only one copy exists, and never says what happens when it changes.

✓

What it should say: “A static variable has exactly one copy, stored with the class rather than with any object, so all forty bikes read that same copy. Changing Bike.hourly_rate once changes the price every bike reports, whereas each bike's condition is an instance variable with its own copy per object.” Copies counted, owner named, consequence stated.

Revision questions

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

Question 1 · 2 marks

[2]Distinguish between a static variable and an instance variable.

Question 2 · 3 marks

[3]A Bike class stores hourly_rate as a class variable. Explain one advantage and one disadvantage of this decision.

Question 3 · 3 marks

[3]A developer adds a static method to Bike and tries to read self.condition inside it. Explain why this cannot work.

Flashcards

Active recall for this statement’s key terms.