Make a Racy BankAccount Deposit Thread-Safe Without Editing the Class, in Python and Go

Quick Overview

A concurrency question built around a BankAccount class whose deposit method reads the balance, sleeps, then writes it back. Candidates find every race, make deposits thread-safe through a subclass or wrapper without editing or copying the class, and carry the fix over to Go, which has no inheritance.

Make a Racy BankAccount Deposit Thread-Safe Without Editing the Class, in Python and Go

Company: xAI

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Onsite

During a short screening call, the interviewer pastes the following class and tells you that several threads call `deposit` on the same account object at the same time: ```python import time class BankAccount: def __init__(self, balance=0): self.balance = balance def deposit(self, amount): current = self.balance # read time.sleep(0.1) # simulated processing delay self.balance = current + amount # write ``` Find every concurrency bug in it, make deposits thread-safe without editing this class, and then explain how you would do the same in Go. ### Constraints and Clarifications - You may not modify `BankAccount` itself. Copying its body into a new class and editing the copy is also rejected: the thread-safe version must reuse the existing `deposit` through a subclass or a wrapper. - The `time.sleep(0.1)` stands in for real work between the read and the write. Treat it as part of the method you cannot change. - All threads run in one process and share the same account object. ### Clarifying Questions - Does the class have other methods, such as a withdrawal or a balance read, that must also be safe, or only `deposit`? - Should each account be protected separately, or is one lock shared by all accounts acceptable? - Is the concurrency limited to threads in one process, or can several processes or hosts update the same balance? - Can `amount` be zero or negative, and should the safe version validate it? - May the safe version extend the public interface, for example with a method that returns the balance? ### Part 1 — Find the bugs Name every concurrency defect in `deposit` and its root cause. Then describe a concrete run with two threads, starting from a balance of `0`, that ends with a wrong balance, and state the expected and actual results. ```hint Interleave two calls Write out two concurrent deposits one step per line, and note what each thread reads before the other one writes. ``` #### What This Part Should Cover - The root cause, kept separate from the factor that only makes the failure more likely - A concrete two-thread interleaving with the expected and the actual final balance - Whether deleting the delay would make the method correct, and why ### Part 2 — Make it thread-safe without editing the class Write the thread-safe version under the constraints above. Show where the synchronization state lives, when it is created, and exactly which statements it protects. Explain how a caller should read the balance, and how you would demonstrate that the fix works. ```hint Where the lock lives Decide which object should own the synchronization state, when it should be created, and how many accounts it should serialize. ``` #### What This Part Should Cover - Reuse of the original method through a subclass or a wrapper rather than a copy - Ownership of the synchronization state and the scope of the critical section - Safe reads of the balance - A test that fails reliably before the fix and passes after it ### Part 3 — The Go version The same account in Go: ```go type BankAccount struct { Balance int } func (a *BankAccount) Deposit(amount int) { current := a.Balance time.Sleep(100 * time.Millisecond) a.Balance = current + amount } ``` Go has no inheritance. Build the thread-safe equivalent without editing `BankAccount`, and explain what can go wrong silently. ```hint Follow each call For every method a caller can invoke on your new type, trace which implementation actually runs and whether that path goes through your synchronization. ``` #### What This Part Should Cover - The Go mechanism used in place of subclassing, and its method-resolution rules - Correct use of `sync.Mutex`: receiver types, copying, and unlocking on every path - Which paths could still bypass the lock, and how to close them ### What a Strong Answer Covers - A diagnosis that separates the root cause from the symptom amplifier - A fix that satisfies the no-edit, no-copy constraint in both languages - The cost of holding a lock across a slow call, and re-entrancy (`Lock` versus `RLock`) - Tests that expose the lost update deterministically instead of relying on luck ### Follow-up Questions - Add `transfer(source, target, amount)` between two thread-safe accounts. How do you avoid a deadlock when two transfers run in opposite directions at the same time? - A teammate argues that once the sleep is removed, the GIL makes `self.balance += amount` atomic, so no lock is needed. How do you respond? - A new method `deposit_many(amounts)` on your safe class takes the lock and then calls `self.deposit` in a loop. What happens with `threading.Lock`, and what would you change? - The balance moves into a database that several service instances update. Which of your techniques still apply, and what replaces the in-process lock?

Overview: A concurrency question built around a BankAccount class whose deposit method reads the balance, sleeps, then writes it back. Candidates find every race, make deposits thread-safe through a subclass or wrapper without editing or copying the class, and carry the fix over to Go, which has no inheritance.

|Home/Software Engineering Fundamentals/xAI
xAI logo
xAI
Sep 5, 2026
mediumSoftware EngineerOnsiteSoftware Engineering Fundamentals
0
0

During a short screening call, the interviewer pastes the following class and tells you that several threads call deposit on the same account object at the same time:

import time

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

    def deposit(self, amount):
        current = self.balance            # read
        time.sleep(0.1)                   # simulated processing delay
        self.balance = current + amount   # write

Find every concurrency bug in it, make deposits thread-safe without editing this class, and then explain how you would do the same in Go.

Constraints and Clarifications

  • You may not modify BankAccount itself. Copying its body into a new class and editing the copy is also rejected: the thread-safe version must reuse the existing deposit through a subclass or a wrapper.
  • The time.sleep(0.1) stands in for real work between the read and the write. Treat it as part of the method you cannot change.
  • All threads run in one process and share the same account object.

Clarifying Questions Guidance

  • Does the class have other methods, such as a withdrawal or a balance read, that must also be safe, or only deposit ?
  • Should each account be protected separately, or is one lock shared by all accounts acceptable?
  • Is the concurrency limited to threads in one process, or can several processes or hosts update the same balance?
  • Can amount be zero or negative, and should the safe version validate it?
  • May the safe version extend the public interface, for example with a method that returns the balance?

Part 1 — Find the bugs

Name every concurrency defect in deposit and its root cause. Then describe a concrete run with two threads, starting from a balance of 0, that ends with a wrong balance, and state the expected and actual results.

What This Part Should Cover Guidance

  • The root cause, kept separate from the factor that only makes the failure more likely
  • A concrete two-thread interleaving with the expected and the actual final balance
  • Whether deleting the delay would make the method correct, and why

Part 2 — Make it thread-safe without editing the class

Write the thread-safe version under the constraints above. Show where the synchronization state lives, when it is created, and exactly which statements it protects. Explain how a caller should read the balance, and how you would demonstrate that the fix works.

What This Part Should Cover Guidance

  • Reuse of the original method through a subclass or a wrapper rather than a copy
  • Ownership of the synchronization state and the scope of the critical section
  • Safe reads of the balance
  • A test that fails reliably before the fix and passes after it

Part 3 — The Go version

The same account in Go:

type BankAccount struct {
    Balance int
}

func (a *BankAccount) Deposit(amount int) {
    current := a.Balance
    time.Sleep(100 * time.Millisecond)
    a.Balance = current + amount
}

Go has no inheritance. Build the thread-safe equivalent without editing BankAccount, and explain what can go wrong silently.

What This Part Should Cover Guidance

  • The Go mechanism used in place of subclassing, and its method-resolution rules
  • Correct use of sync.Mutex : receiver types, copying, and unlocking on every path
  • Which paths could still bypass the lock, and how to close them

What a Strong Answer Covers Guidance

  • A diagnosis that separates the root cause from the symptom amplifier
  • A fix that satisfies the no-edit, no-copy constraint in both languages
  • The cost of holding a lock across a slow call, and re-entrancy ( Lock versus RLock )
  • Tests that expose the lost update deterministically instead of relying on luck

Follow-up Questions Guidance

  • Add transfer(source, target, amount) between two thread-safe accounts. How do you avoid a deadlock when two transfers run in opposite directions at the same time?
  • A teammate argues that once the sleep is removed, the GIL makes self.balance += amount atomic, so no lock is needed. How do you respond?
  • A new method deposit_many(amounts) on your safe class takes the lock and then calls self.deposit in a loop. What happens with threading.Lock , and what would you change?
  • The balance moves into a database that several service instances update. Which of your techniques still apply, and what replaces the in-process lock?
Loading comments...