Skip to content

Classes and objects: data and actions in one place

What a class and an object are, how to read and write a small Python class, and why data and the actions on it belong together.

  • Lesson 9 of 169
  • 21 min read
  • Core
  • Free to read · No account needed
On this page
  1. Why interviewers ask this
  2. The idea in plain words
  3. Pause and predict
  4. Worked example
  5. Trade-offs
  6. Common misconceptions
  7. How to say it in the interview
  8. Check your understanding
  9. Answers
  10. Summary card: classes and objects
  11. Sources

01Why interviewers ask this

Every coding round starts the same way. You open an empty editor and must decide what to type first. In most problems the answer is: a class. A class is a named piece of code that keeps some data together with the actions that use that data.

A weak answer keeps the data in loose values and writes many separate pieces of code that take those values as input. It works for the first version. When a rule changes, you search the whole file for every place that touches the data. A strong answer gives each thing in the problem its own small class. The data and the actions on it sit in one place, so a change has one place to go.

At SDE-1 level you should write a small class without help and run it. At SDE-2 level you should also say which class owns which data and why. The scoring of the round (lesson 7) gives 25 points to object design, so this is the base of everything that follows.

02The idea in plain words

Think of a prepaid card for a college canteen. You load money on the card, and you tap it at the counter to pay. The card itself knows whose it is and how much is left. Loading money and paying are things you do with the card. Now think of the other way: a notebook at the counter with all names and balances, and a separate sheet of rules. The cashier must find the right line and apply the right rule. A wrong line is easy to pick.

This lesson assumes you know if, for loops, print and +=. The new ideas are class and object.

Code can be written in both ways. Here is the notebook way for two cards. A dictionary is a collection of named values: here "holder" and "balance". A function is a named block of code that takes inputs.

python
card_a = {"holder": "Asha", "balance": 100}
card_b = {"holder": "Ravi", "balance": 40}

def pay(card, amount):
    card["balance"] -= amount

pay(card_a, 30)
print(card_a["balance"], card_b["balance"])

This prints 70 40. It works, but nothing connects the function to the data. You can call pay with any dictionary, including one that is not a card. A typo such as "balence" fails only when that line runs. Nobody knows which functions work on a card, unless they read the whole file.

Class, object, field, method

A class is a blueprint: it says what a thing keeps and what it can do. An object is one thing made from the blueprint. One class, CanteenCard, can make many objects: Asha's card, Ravi's card. A field is a named piece of data that an object keeps, such as its balance. A method is an action that the object can do. It is a function that lives inside the class.

python
class CanteenCard:
    def __init__(self, card_id, holder, balance):
        self.card_id = card_id
        self.holder = holder
        self.balance = balance

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

    def pay(self, amount):
        self.balance -= amount

Read it slowly:

  • class CanteenCard: starts the blueprint. The name is a noun, with a capital letter for every word.
  • __init__ is the constructor: the method that runs once, when an object is made. It sets the first value of each field.
  • self means "this object". Python passes it as the first input of every method. self.balance is the balance of this card, and no other card.
  • To make an object, call the class like a function: asha = CanteenCard("C101", "Asha", 100). You do not write self; Python fills it in.
  • To use an object, write the object, a dot and the name. asha.pay(30) runs pay on Asha's card. asha.balance reads the field.
  • Code outside the class can also set a field with =, for example asha.balance = 500. Python does not check the new value.

Each object has its own fields. asha.pay(30) changes Asha's balance and leaves Ravi's card alone. The wrong card cannot be hit, because the method belongs to the card.

State, and values that are the same for every card

The state of an object is the current value of all its fields. It changes over time. top_up and pay change the state. Other methods only look at the state and return an answer. Name them so a reader can tell: pay is a verb that changes things, and is_low is a question.

Some values are the same for every card, such as the amount below which we call a balance low. Put it in the class, outside any method, in capital letters. This is a class-level constant. A field set in __init__ is different: every object has its own copy. Add these lines inside the class, after pay:

python
    LOW_BALANCE = 20

    def is_low(self):
        return self.balance < CanteenCard.LOW_BALANCE

A warning: never put a value that changes, such as an empty list, at class level. All objects then share one list. The Python tutorial warns about this. If you want a list for each object, create it in __init__ with self.

Seeing an object while you debug

If you print an object, Python shows a code such as 0x7f3a that means nothing to you. The method __repr__ fixes this. It returns the text that Python shows for the object. Add it inside the class too:

python
    def __repr__(self):
        return f"CanteenCard({self.card_id}, {self.holder}, Rs {self.balance})"

The text starting with f" is a text with {...} holes. Python puts the value of each hole into the text. Now print(asha) shows CanteenCard(C101, Asha, Rs 70). This helps a lot when you debug.

Same object, or equal objects

Two objects can hold the same data and still be two objects. a is b asks "is it the same object?" a == b asks "are they equal?" For a plain class like this one, == also means "the same object". So two cards with the same id, holder and balance are not equal. To compare by meaning, compare a field: a.card_id == b.card_id.

None: no value

None is the Python value for "no value here". Use x is None to ask whether x is that value. It is the right way to check for "nothing", because is asks about the same object.

Objects in lists and dictionaries

An object is a normal value. You can put objects in a list or in a dictionary. A common shape in a round is a registry. It is a dictionary that maps an id to an object, so you can find a card from its id. The loop for card in [asha, ravi]: registry[card.card_id] = card stores each card under its id. Then registry.get("C102") gives the object, or None if the id is not in the registry.

Names and size

Use a noun for a class (CanteenCard, Receipt). Use a verb for a method that does something (pay, top_up) and a question for a method that only looks (is_low). Keep a class small, with one clear job. If you cannot describe it in one sentence without the word "and", split it.

When a function is better

If a piece of code keeps no state between calls, a plain function is enough. A function that adds GST (a sales tax) to a price needs no class. Do not make a class only to hold one function.

Reading the Python

If you write Java, JavaScript or C++, the ideas are the same. Only the words change.

PythonJavaJavaScriptC++
class CanteenCard:class CanteenCardclass CanteenCardclass CanteenCard
__init__ (constructor)a method with the class nameconstructora function with the class name
self.balancethis.balancethis.balancethis->balance
listArrayListarraystd::vector
dictionaryHashMapMapstd::unordered_map
Nonenullnullnullptr

Want to check your understanding? Try explaining this idea in your own words. You can continue reading first, or ask the AI tutor a question. Sign up free to ask the tutor

03Pause and predict

Pause. Guess first.

Asha's card has Rs 100 and Ravi's card has Rs 40. The code runs asha.pay(30). What are the two balances now? Then someone runs asha.balance = -500. Does Python stop this?

Write your guess before you read on. The answer is in the worked example.

04Worked example

Run with Python 3.11, with the CanteenCard class above (the first listing plus the two added pieces). Assumption: Asha's card has Rs 100, Ravi's card has Rs 40, and a balance below Rs 20 counts as low.

python
asha = CanteenCard("C101", "Asha", 100)
ravi = CanteenCard("C102", "Ravi", 40)
asha.pay(30)
print(asha.balance, ravi.balance)
print(asha)

It prints 70 40 and then CanteenCard(C101, Asha, Rs 70). The arithmetic: 100 - 30 = 70. Ravi's card did not move: it is still 40. The two objects are independent.

The class CanteenCard, with the fields card_id, holder and balance and the methods top_up, pay and is_low, makes two objects.
One class makes many objects, and each object keeps its own balance.
The class CanteenCard, with the fields card_id, holder and balance and the methods top_up, pay and is_low, makes two objects. The card of Asha, C101, has a balance of 70 after the payment of 30. The card of Ravi, C102, has a balance of 40. Each object keeps its own balance.

Now put both in a registry and use it.

python
registry = {}
for card in [asha, ravi]:
    registry[card.card_id] = card
print(registry["C102"].holder)
print(registry.get("C999"))
ravi.top_up(50)
print(registry["C102"].balance)
asha.pay(55)
print(asha.balance, asha.is_low())

The output is Ravi, None, 90, and 15 True. Step by step: the id C102 finds Ravi's card. The id C999 has no card, so get gives None. Ravi's top-up gives 40 + 50 = 90. Asha pays 55 more: 70 - 55 = 15. Fifteen is below 20, so is_low() is True. The registry holds the same objects, not copies, so registry["C102"].balance shows the top-up.

Two last checks, on equality and on setting a field from outside.

python
a = CanteenCard("C1", "Asha", 100)
b = CanteenCard("C1", "Asha", 100)
print(a == b, a is b, a is a, a.card_id == b.card_id)
asha.balance = -500
print(asha.balance)

It prints False False True True, and then -500. This answers the predict box. After asha.pay(30) the balances are 70 and 40. And Python does not stop asha.balance = -500. Any code can set a field from outside. A canteen card with a balance of minus Rs 500 is wrong, but the class as we wrote it allows it. This class keeps data and actions together, but it does not protect its data.

05Trade-offs

A class solves the problem of loose data. The data and its actions are in one place. Each object has its own state. The right method is easy to find. It costs more lines than a dictionary, and you must decide what each class owns. A class also does not guard its fields by itself: as the example shows, outside code can set any value. I would change to a plain function when there is no state. I would change to a plain dictionary when you only carry a few values and no rule belongs to them.

06Common misconceptions

"A class is only another name for a dictionary." A dictionary holds data and nothing else. A class also holds the actions, and each method belongs to its object. This is why asha.pay(30) cannot change Ravi's card.

"Two objects with the same data are equal." For a plain class they are not. In the example, a == b is False even though every field is the same. Compare ids or fields when you mean "the same card".

"A list written at class level belongs to each object." It belongs to the class, and all objects share it. Create a list for each object inside __init__.

07How to say it in the interview

Say it like this

I put each thing in the problem in a small class: a noun for the class, its data as fields, its actions as methods. Each object keeps its own state, so a rule has one place to live and two objects never mix.

08Check your understanding

  1. What does self mean in a method, and when does __init__ run?
  2. A new card CanteenCard("C103", "Meera", 100) gets top_up(50), then pay(30), then pay(45). What is the balance? Is is_low() true?
  3. A teammate writes class Cart: with the line items = [] at class level. The method add runs self.items.append(x), and append adds one item at the end of a list. Two carts are made, and one item is added to the first. What does the second cart show, and what do you change?

09Answers

Show the answers
  1. self means "this object", the one the method was called on. __init__ runs once, when the object is made, and sets the first value of the fields.
  2. 100 + 50 = 150. Then 150 - 30 = 120. Then 120 - 45 = 75. The balance is Rs 75. It is not below 20, so is_low() is False.
  3. The second cart shows the same item, because items belongs to the class and one list is shared. Move the line into __init__ as self.items = [], so each cart gets its own list.

10Summary card: classes and objects

One-page summary

  • Idea. A class is a blueprint. An object is one thing made from it. Data (fields) and actions (methods) live together.
  • Words. Constructor __init__ runs once. self is this object. A registry is a dictionary from id to object. None means no value.
  • Rules. Each object has its own fields. A class-level constant is shared by all. Never put a changing list at class level.
  • Compare. a is b asks if it is the same object. == on a plain class says the same.
  • Names. Nouns for classes, verbs for actions, questions for methods that only look.
  • Trade-off. It solves loose data. It costs more lines. It does not guard its fields.
  • Trap. Setting card.balance = -500 from outside is allowed.

11Sources

Try what you just learned

Put this idea into practice with a short exercise or a relevant tool. Free to try. No sign-up needed.

More practice. This needs a free account.

Continue learning with the lessons that build on this one.

Practise this idea

Have a question about this lesson?

Ask the AI tutor to explain a difficult idea, walk through an example or check your understanding. It uses the lesson to help you work through the topic.

A free account includes 10 AI tutor questions a day. The lesson remains free to read without an account.

All lessons in Module 2: Low-level design and machine coding

Prefer another topic? Browse all 169 lessons.