Class variables are shared among all instances of a class, while instance variables are unique to each instance.

Let’s see this in action.

class Dog:
    # Class variable
    species = "Canis familiaris"

    def __init__(self, name, breed):
        # Instance variables
        self.name = name
        self.breed = breed

# Creating instances
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Lucy", "Labrador")

print(f"Dog 1: {dog1.name}, Breed: {dog1.breed}, Species: {dog1.species}")
print(f"Dog 2: {dog2.name}, Breed: {dog2.breed}, Species: {dog2.species}")

# Modifying a class variable through the class
Dog.species = "Domestic Dog"

print(f"Dog 1 after class variable change: Species: {dog1.species}")
print(f"Dog 2 after class variable change: Species: {dog2.species}")

# Modifying a class variable through an instance (this creates an instance variable!)
dog1.species = "Golden Retriever Dog"

print(f"Dog 1 after instance variable assignment: Species: {dog1.species}")
print(f"Dog 2 after instance variable assignment: Species: {dog2.species}") # dog2 is unaffected
print(f"Dog class species after instance assignment: {Dog.species}") # The class variable itself is unaffected

This code demonstrates the core difference: species is defined at the class level, meaning all Dog objects initially share the same species value. When we change Dog.species directly, the change propagates to all instances that haven’t shadowed it. However, when we assign dog1.species = "Golden Retriever Dog", Python creates a new instance variable named species specifically for dog1. This new instance variable then hides the class variable for dog1, but dog2 and the Dog class itself continue to use the original (or modified class-level) species.

The problem this solves is managing shared state versus unique state for objects. You want species to be a global characteristic of all dogs in your system, but name and breed are specific to each individual dog. Using class variables for shared data like this is memory-efficient; you only store the data once, regardless of how many objects you create. Instance variables are for data that makes each object distinct.

Internally, when you access instance.variable, Python first checks if variable exists as an instance attribute on instance. If it doesn’t, it then looks for variable as a class attribute on instance.__class__. If found there, it returns that value. If not found on the class, it continues up the inheritance chain. This lookup order is crucial.

The common bug arises when you intend to modify a class variable but accidentally create an instance variable with the same name. This happens if you write self.class_variable = new_value inside a method, rather than ClassName.class_variable = new_value. The self. prefix tells Python to assign the value to the instance being operated on, effectively masking the class variable for that specific instance. The fix is to always use the class name when you intend to change the shared value: Dog.species = "Canis familiaris domesticus".

Another subtle point is when you iterate over dictionaries that are class variables. If you modify the dictionary in place through an instance, you’re still modifying the single shared dictionary object. For example, if Dog.food_preferences = {"kibble": 100} and you do dog1.food_preferences["kibble"] = 50, all dogs will see that change because they are all referencing the same dictionary object. If you wanted to give dog1 unique food preferences, you’d need to assign a new dictionary to dog1.food_preferences, like dog1.food_preferences = {"kibble": 50, "treats": 10}.

The next common pitfall is when dealing with mutable class variables, like lists or dictionaries. If you append to a list that is a class variable via an instance (self.my_list.append(item)), you are indeed modifying the shared list, which can lead to unexpected side effects across all instances. To avoid this, always create a new instance variable if you need unique mutable state.

The distinction between class and instance variables is fundamental to object-oriented programming in Python, enabling both shared behaviors and unique states within your object models. Understanding the lookup order and the effect of assignment is key to avoiding subtle bugs.

Want structured learning?

Take the full Python course →