November 7, 2026•5 min read

Python Classes: the History Every Account Shared

Two accounts, and each history lists the other account's deposits. The list sat in the class body, append never writes to the object, so both accounts appended into the same one.

Read in Indonesian

Your bank-account report prints Ani's deposit inside Budi's history. The balances are right and nothing raised an error, so nothing pointed at the class body. That is where the bug lives: a mutable class attribute that every account shares.

Here is the output from a script that builds Account("Ani", 100) and Account("Budi", 0), then calls ani.deposit(50) and budi.deposit(20):

TEXT
ani.balance 150
budi.balance 20
ani.history ['Ani deposit 50', 'Budi deposit 20']
budi.history ['Ani deposit 50', 'Budi deposit 20']
same list: True
Account.history ['Ani deposit 50', 'Budi deposit 20']
ani.__dict__ {'owner': 'Ani', 'balance': 150}

Read the last line. Ani's __dict__ holds owner and balance, and no history. The attribute was never set on the object, so the lookup found the class's list.

Reading self.history looks on the object first, then on the class. self.history.append(x) is a method call on whatever list that lookup returned. It is not an assignment, so nothing gets written to the object. The Python tutorial on classes covers this lookup order.

Key takeaways

  • A list assigned in the class body belongs to the class, so every object reads that same list until the object gets one of its own.
  • self.history.append(x) reads the class attribute and mutates it; it never writes an attribute on the object, which is why history is absent from ani.__dict__.
  • self.balance = ... looked correct because assignment always writes on the object that holds the name.
  • Build mutable state in __init__, and leave a class attribute for values no object should be able to change.

Two files, one command

Start with the broken version. The list sits in the class body, above __init__:

PYTHON
# account.py
class Account:
    history = []

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

    def deposit(self, amount):
        self.balance = self.balance + amount
        self.history.append(f"{self.owner} deposit {amount}")

Next, a checker. It builds two accounts, makes one deposit in each, and asks four questions:

PYTHON
# check_account.py
import account

passed = 0
total = 0


def check(name, ok, detail=""):
    global passed, total
    total += 1
    if ok:
        passed += 1
        print(f"PASS  {name}")
    else:
        print(f"FAIL  {name}  <- {detail}")


ani = account.Account("Ani", 100)
budi = account.Account("Budi", 0)

ani.deposit(50)
budi.deposit(20)

check("a deposit lands in the account that made it", ani.balance == 150, f"balance is {ani.balance}")
check("the other account's balance does not move", budi.balance == 20, f"balance is {budi.balance}")
check(
    "each account has one line of history",
    len(ani.history) == 1 and len(budi.history) == 1,
    f"ani has {len(ani.history)}, budi has {len(budi.history)}",
)
check(
    "the two history lists are different lists",
    ani.history is not budi.history,
    "both accounts point at the same list",
)

print()
print(f"{passed} of {total} checks passed")
raise SystemExit(0 if passed == total else 1)

Run python3 check_account.py with the starter in place. It exits with code 1:

TEXT
PASS  a deposit lands in the account that made it
PASS  the other account's balance does not move
FAIL  each account has one line of history  <- ani has 2, budi has 2
FAIL  the two history lists are different lists  <- both accounts point at the same list

2 of 4 checks passed

Two checks already pass, and that is the trap. self.balance = ... is an assignment, and assignment always writes on the object, so each account gets its own balance. self.history.append(...) never assigns anything. Balances are the first thing you would test, and they look fine.

Now move the list into __init__, so each object creates its own:

PYTHON
# account.py
class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance
        self.history = []

    def deposit(self, amount):
        self.balance = self.balance + amount
        self.history.append(f"{self.owner} deposit {amount}")

Run the same command again. This time it exits with code 0:

TEXT
PASS  a deposit lands in the account that made it
PASS  the other account's balance does not move
PASS  each account has one line of history
PASS  the two history lists are different lists

4 of 4 checks passed

I ran all of this on Python 3.14.9 on Linux. I did not try other Python versions, so this is the evidence from one machine.

What this does not do

A class attribute is still the right home for a value nobody mutates. The checker cannot tell a shared constant from a shared list, and it should not. Sharing was never the problem. Mutating something shared was.

An empty history is also not proof that you fixed it. Every account starts with an empty list in both versions. The bug only shows once a second object exists, which is exactly what the checker builds.

Break it on purpose

Put history = [] back in the class body. Then add one line to __init__:

PYTHON
self.history = Account.history

It looks like each account now gets its own list. Run the checker and two checks fail again:

TEXT
PASS  a deposit lands in the account that made it
PASS  the other account's balance does not move
FAIL  each account has one line of history  <- ani has 2, budi has 2
FAIL  the two history lists are different lists  <- both accounts point at the same list

2 of 4 checks passed

The new line copies the same list object into both objects, so the objects still share it.

Before you put a list, dict, or set in a class body, ask one question: should a second object see this change? If yes, share it on purpose. If no, build it in __init__.

Get better at shipping code.

Join the other developers getting tech updates, tips, and relaxed tutorials in their inbox every week.

Stay in the loop

Tech updates, coding tips, and relaxed insights straight to your inbox every week.

No spam. Unsubscribe anytime.

Read next

See all
Struktur DataWhat a Linked List Actually Is

A linked list is a linear chain of nodes, each holding data and a pointer to the next node. Here is what that chain costs next to an array, plus a minimal implementation you can run.

For developers who learn by struggling

© 2026 Inva.dev.