Learn When to Use Classes in Python: Absolute Beginners Tutorial

Classes are one of the features Python beginners hear about early and put off learning for as long as possible. This tutorial explains what a class actually is, shows you the signals that tell you one is the right tool for a given job, and walks you through building one from the ground up.

Python lets you write code in several styles. You can write plain functions, use dictionaries to group data, or reach for a class. None of those choices is universally correct. The question is always: which one fits the problem in front of you right now? By the end of this tutorial you will have a clear answer for the class side of that question.

What a Class Actually Is

A class is a blueprint. When you write a class, you are describing a category of thing — not one specific thing, but a pattern from which individual things can be created. Those individual things are called instances.

Think of a class named Dog. The class itself is not a dog. It describes what every dog made from this blueprint will have: a name, a breed, and an age. Each time you create a new dog from that blueprint, you get one specific dog with its own name, breed, and age. That dog is an instance.

python
class Dog:
    def __init__(self, name, breed, age):
        self.name  = name
        self.breed = breed
        self.age   = age

    def describe(self):
        return f"{self.name} is a {self.age}-year-old {self.breed}."

# Create two separate instances from the same blueprint
dog_one = Dog("Biscuit", "Beagle", 3)
dog_two = Dog("Pepper", "Husky", 5)

print(dog_one.describe())  # Biscuit is a 3-year-old Beagle.
print(dog_two.describe())  # Pepper is a 5-year-old Husky.

A few things to notice in that code. The class keyword starts the definition. __init__ is a special method that runs automatically every time you create a new instance — it sets up the starting values. self is a reference to the specific instance being created or used. And describe is a regular method — a function that lives inside the class and has access to the instance's data through self.

Note

__init__ is sometimes called a constructor in other languages. In Python the official term is initializer. Python calls it for you — you never call __init__ directly.

What self does

New learners frequently find self confusing. Here is the plain explanation: when Python runs dog_one.describe(), it automatically passes dog_one as the first argument to describe. The parameter that receives it is named self by convention. That is how the method knows whose data to use. You could technically name it anything, but self is the universal Python convention and you should use it.

code builder click a token to place it

Build the correct first line of an __init__ method for a class called Car that accepts make and year:

your code will appear here...
make, class __init__( def year): self, return
Why: A method definition always starts with def, followed by the method name. __init__( opens the parameter list. self, must come first because Python passes the instance automatically. Then come the caller-supplied parameters make, and year):. The colon ends the signature and starts the method body. class and return do not belong here.

Classes vs. Functions vs. Dictionaries

Before deciding to write a class, it helps to know what the alternatives look like for the same problem. Consider a bank account with a balance, an owner name, and the ability to deposit and withdraw money.

The dictionary approach

python
# Dictionary approach — data and logic are separate
account = {"owner": "Maya", "balance": 500}

def deposit(account, amount):
    account["balance"] += amount

def withdraw(account, amount):
    if amount > account["balance"]:
        print("Insufficient funds.")
        return
    account["balance"] -= amount

deposit(account, 200)
withdraw(account, 100)
print(account["balance"])  # 600

This works fine for one account. But notice that every function must receive the account dictionary as an argument. The data and the logic that operates on it are kept in separate places. For a single account in a short script, that is acceptable.

The class approach

python
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner   = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            print("Insufficient funds.")
            return
        self.balance -= amount

    def statement(self):
        return f"{self.owner}'s balance: ${self.balance}"

# Each account is independent
account_a = BankAccount("Maya", 500)
account_b = BankAccount("Leo")

account_a.deposit(200)
account_a.withdraw(100)
account_b.deposit(1000)

print(account_a.statement())  # Maya's balance: $600
print(account_b.statement())  # Leo's balance: $1000

Now the data and the logic live together. Creating a second account does not require copying any functions — you just create a second instance. Each instance tracks its own balance independently.

Pro Tip

The bundling of data and related behavior into one unit is called encapsulation. It is one of the core ideas behind object-oriented programming, and it is the main reason classes exist.

Best when
You have a single transformation or calculation with no state to carry between calls
Limitation
Must receive all context as arguments every time; does not remember anything between calls
Best when
You need to group a small set of related values and will not add behavior to them
Limitation
Logic that operates on the dictionary lives elsewhere; does not scale cleanly when you need multiple copies
Best when
You need state that persists, multiple independent copies of the same structure, or behavior bundled together with data
Limitation
Adds more structure than a short script may need; can feel like overkill for simple one-off tasks
spot the bug click the line that contains the bug

The Counter class below is supposed to track a running total, but something is wrong. Click the line you think is the problem, then hit check.

1 class Counter:
2 def __init__(self):
3 self.count = 0
4 def increment(self):
5 count += 1
6 def value(self):
7 return self.count
The fix: Change count += 1 to self.count += 1. Without self., Python treats count as a local variable that does not exist yet inside the method, which raises an UnboundLocalError. The self. prefix is what connects a method back to the instance's stored data.

The Three Signals That Tell You to Use a Class

Learning to recognize when a class is the right tool is a practical skill. Here are three clear signals to look for.

Signal 1: You need to track state

State is information that needs to be remembered and potentially updated across multiple operations. If you find yourself passing the same variable into function after function just to keep it alive, that variable wants to live inside a class. The Counter example from the Spot the Bug exercise is the simplest version of this: a running total is state.

python
# State lives inside the instance — no need to pass it around
class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1

    def reset(self):
        self.count = 0

    def value(self):
        return self.count

c = Counter()
c.increment()
c.increment()
c.increment()
print(c.value())  # 3
c.reset()
print(c.value())  # 0

Signal 2: Several pieces of data naturally belong together

When you have a cluster of related variables that always travel together — say, a name, an email address, and a role — those variables are describing one thing. A class gives that thing a proper home and a name. It also means any function that works with a user just receives one user argument instead of three separate variables.

python
class User:
    def __init__(self, name, email, role="viewer"):
        self.name  = name
        self.email = email
        self.role  = role

    def is_admin(self):
        return self.role == "admin"

    def summary(self):
        return f"{self.name} ({self.email}) — {self.role}"

admin = User("Priya", "priya@example.com", "admin")
guest = User("Sam",   "sam@example.com")

print(admin.summary())    # Priya (priya@example.com) — admin
print(admin.is_admin())   # True
print(guest.is_admin())   # False

Signal 3: You need multiple independent copies

If your program needs several separate versions of the same kind of thing — multiple players in a game, multiple sensors reporting data, multiple shopping carts — a class makes creating and managing each one straightforward. Each instance maintains its own data without any interference from the others.

python
class Player:
    def __init__(self, name):
        self.name   = name
        self.health = 100
        self.score  = 0

    def take_damage(self, amount):
        self.health = max(0, self.health - amount)

    def earn_points(self, points):
        self.score += points

    def status(self):
        return f"{self.name} — HP: {self.health}  Score: {self.score}"

player1 = Player("Reza")
player2 = Player("Nadia")

player1.take_damage(30)
player1.earn_points(50)
player2.earn_points(80)

print(player1.status())  # Reza — HP: 70  Score: 50
print(player2.status())  # Nadia — HP: 100  Score: 80

Notice that player1.take_damage(30) only affected player1. player2's health is untouched. That independence is exactly what instances give you.

"Classes provide a means of bundling data and functionality together." — Python Software Foundation, Python 3 Documentation

How to Decide When to Use a Class in Python

Run through these five steps whenever you are unsure whether a class is the right choice for a piece of code you are writing.

  1. Check whether you need to track state

    Ask yourself whether the data you are working with needs to remember information between operations. If a function must repeatedly receive the same values as arguments just to carry them forward, a class is likely the better fit. State that changes over time — a balance, a score, a connection status — belongs inside an instance.

  2. Check whether you have multiple related data fields

    If the same cluster of variables travels through several functions together, those variables probably belong together inside a class as instance attributes. A name, email, and role always describing the same user is a clear example. Grouping them in a class makes your function signatures shorter and your code easier to read.

  3. Check whether you need multiple independent copies

    If your program needs several separate versions of the same thing — multiple players, multiple accounts, multiple tasks — a class gives you a clean way to create and manage each one independently. Without a class, you end up with parallel lists of variables that drift out of sync.

  4. Define the class and write __init__

    Start with the class keyword and a name in CapWords style. Write an __init__ method that accepts self plus every value the instance needs at creation time. Assign each value to self.attribute_name inside the body. These assignments are what create the instance attributes.

  5. Add methods that operate on the instance data

    Write a method for every action that needs to read or modify the instance's attributes. Each method takes self as its first parameter. Access instance attributes with self.attribute_name inside the method body. Keep each method focused on one responsibility so the class stays easy to reason about.

Python Learning Summary Points

  1. A class is a blueprint for creating objects called instances. Each instance holds its own copy of the class's data, called attributes, and can call the class's methods on that data.
  2. The __init__ method runs automatically when a new instance is created and sets up its starting attribute values. self is a reference to the specific instance being worked with; it must be the first parameter of every instance method.
  3. Reach for a class when you need to track state over time, when several related data fields travel together, or when your program needs multiple independent copies of the same structure. Plain functions and dictionaries remain the right choice when none of those conditions apply.

The most common mistake beginners make is treating classes as the default tool for everything. A function that does one thing cleanly is better than a class that does one thing verbosely. Use the three-signal test — state, grouped data, multiple copies — and you will pick the right tool far more consistently.

check your understanding question 1 of 5

Frequently Asked Questions

A class in Python is a template for creating objects. It bundles together data (called attributes) and the functions that work on that data (called methods) into a single reusable structure. You define a class once and then create as many instances from it as you need.

Use a class when you need to track state across multiple calls, when you have several related pieces of data that naturally belong together, or when you need to create multiple copies of the same kind of thing — like multiple users or multiple game characters. A plain function is the right tool when you just need a single transformation with no ongoing state.

__init__ is the initializer method that Python calls automatically whenever you create a new instance of a class. It sets up the starting values for all the instance's attributes. You never call it directly — writing Dog("Biscuit", "Beagle", 3) is enough to trigger it.

self refers to the specific instance of the class that a method is being called on. When you call dog_one.describe(), Python passes dog_one as the first argument to describe, and that argument is received as self. It lets each instance keep track of its own data separately from all other instances.

No. A plain function or a dictionary is often the right tool. Classes shine when you have state that changes over time, multiple related data fields, and operations that belong logically together with that data. If none of those three apply, a simpler structure is likely the better choice.

An instance is a single object created from a class. If a class is a blueprint, the instance is the actual thing built from that blueprint. Each instance has its own copy of the class's attributes. Creating dog_one = Dog("Biscuit", "Beagle", 3) produces one instance; creating dog_two = Dog("Pepper", "Husky", 5) produces a second, completely independent one.

A class attribute is defined directly in the class body and is shared by all instances. An instance attribute is set inside __init__ using self and is unique to each individual instance. As a beginner, you will spend most of your time with instance attributes — they are what most descriptions of classes are talking about.

Yes. A class can have as many methods as needed. Each method is a function defined inside the class body and receives self as its first parameter so it can access the instance's data. The BankAccount example in this tutorial has three separate methods: deposit, withdraw, and statement.