Build a staged banking service: ATM cash, statements, transfers, fees, slow transfers
ValonSoftware Engineer · OnsiteLast updated
A staged, roughly two-hour coding exercise to build a banking service: ATM balance, deposits and withdrawals, per-account statements that include failed transactions, instant transfers, external transfers that charge a fee, and slow transfers that hold funds until they arrive. It tests exact money handling, ledger design and extensible code.
Build a banking service in five parts, adding one capability per part. Each part comes with a sample input file of actions and the expected output, and your code should produce that output. Only the service class is evaluated; a small file-handling class exists to feed it actions and write statements.
The starting skeleton:
class BankService:
"""Code to be evaluated."""
def get_current_balance(...):
pass
def deposit_cash(...):
pass
def withdraw_cash(...):
pass
class FileInterface:
"""Used for debugging and testing only. This class is not evaluated;
you may even hardcode the file paths."""
def perform_bank_actions():
# reads in the input file
# calls BankService methods
pass
def write_bank_statements():
# calls BankService methods
# writes the output file (its format depends on the current part)
pass
FileInterface.perform_bank_actions()
FileInterface.write_bank_statements()
The parameter lists are left for you to choose.
Constraints and Clarifications
- You get about two hours to implement: roughly the first 30 minutes with an engineer, reviewing the problem, discussing the design and starting to code, then about 90 minutes coding alone. In a final 30 minutes a second engineer joins to walk through your code and discuss your design decisions.
- Finishing all five parts is not required, but completing at least Part 3 or Part 4 appears to be expected.
- The input file format is not reproduced here. Assume each line describes one timestamped action (its time, its kind, the account or accounts involved, and the amount), and choose a concrete format.
- Amounts are dollars and cents, printed with a dollar sign and two decimal places as in the samples. Times print as in the samples, with the hour not zero-padded.
Clarifying Questions
- Are actions in the input file sorted by time, and must the service handle them out of order?
- Do accounts have to be opened explicitly, or does an account exist from its first action?
- Can a balance go negative, or does every debit that exceeds the balance fail?
- Is a malformed action, such as a zero or negative amount, a FAILED transaction in the statement or an input error?
- Should the output contain one statement per account, or one file for all accounts?
Part 1 — ATM balance, deposits and withdrawals
Support ATM operations: view an account's balance, deposit cash, and withdraw cash. After processing the input actions, write each account's balance.
Sample output:
account amount
PEACH1111 $5.00
PEACH2222 $10.00
PEACH3333 $15.00
What This Part Should Cover
- An exact representation of money, and formatting that reproduces the sample
- Defined behavior for a withdrawal larger than the balance and for an account never seen before
- Service logic kept separate from file reading and writing
Part 2 — Transaction history per account, including failures
Display the transactions of each account. Track every transaction, including those that failed. Each statement row has these columns: time, account, credit, debit, status, type, and the balance after the transaction.
Sample statement:
time account credit debit status type balance
2020-08-01 1:00:00 PEACH1111 $10.00 SUCCESS ATM $10.00
2020-08-01 7:00:00 PEACH1111 $5.00 SUCCESS ATM $5.00
2020-08-01 5:00:00 PEACH2222 $20.00 SUCCESS ATM $20.00
2020-08-01 6:00:00 PEACH2222 $25.00 FAILED ATM $20.00
The last row is a withdrawal larger than the balance: it is recorded as FAILED and leaves the balance unchanged. Rows are grouped by account and ordered by time within an account.
What This Part Should Cover
- A transaction record that carries every statement column
- Failed attempts recorded without changing the balance
- A deterministic order within an account, including rows that share a timestamp
Part 3 — Instant transfers between accounts
Add a function that transfers funds from one account to another:
def instant_transfer(...):
pass
A sample statement row for one side of a transfer, with the credit and debit columns collapsed into a single amount column:
time account amount status type balance
2020-08-01 6:00:00 PEACH1111 $2.00 SUCCESS INSTANT TRANSFER $7.00
Clarifying Questions for this Part
- Should the transfer appear on both accounts' statements, as a debit for the sender and a credit for the receiver?
- What happens when the destination account does not exist, or is the same as the source?
What This Part Should Cover
- An all-or-nothing transfer: no state in which money has left one account without reaching the other
- Failure cases (insufficient funds, invalid destination) recorded consistently with Part 2
- Reuse of the debit and credit logic from earlier parts instead of a parallel code path
Part 4 — Transfers to and from an external bank, with a fee
Support transfers to and from accounts at an external bank. Each external transfer incurs a fee, recorded as its own statement row. A sample, again showing one account's side with a single amount column:
time account amount status type balance
2020-08-01 7:00:00 PEACH2222 $5.00 SUCCESS INSTANT TRANSFER $13.00
2020-08-01 7:00:00 PEACH2222 $0.01 SUCCESS FEE $12.99
The fee row reduces the balance, so it is a debit.
Clarifying Questions for this Part
- Is the fee a flat amount or a percentage of the transfer, and how is a fraction of a cent rounded?
- Is the fee charged on incoming external transfers as well as outgoing ones, and always to the account at this bank?
- If the balance covers the transfer but not the transfer plus the fee, does the whole transfer fail?
What This Part Should Cover
- The fee rule isolated so that it can change without touching the transfer logic
- A funds check that includes the fee, and consistent recording of the transfer and fee rows
- Distinct handling of outgoing and incoming external transfers
Part 5 — Slow transfers
Add a function that supports slow fund transfers. Once a slow transfer is initiated, the funds are no longer usable until they reach the destination account. A sample row:
time account amount status type balance
2020-08-01 6:00:00 PEACH1111 $2.00 SUCCESS SLOW TRANSFER $7.00
Clarifying Questions for this Part
- How is the arrival time determined: given in the input, a fixed delay, or an explicit settlement action?
- Does the displayed balance include funds in transit, and should a statement show a pending state?
- Can a slow transfer be cancelled or fail after initiation, and what happens to the held funds?
- Does a slow transfer to an external bank also carry the Part 4 fee?
What This Part Should Cover
- A clear split between funds that are usable and funds that are committed but not yet delivered
- A time-driven settlement mechanism that processes transfers in arrival order
- Statement rows at initiation and at arrival, and later balance checks that respect the held funds
Hints
Hint 1
Money first: Decide how amounts are represented before writing any method. The output prints exact cents, and every later part does arithmetic on them.
Hint 2
Record it when it happens: Every column, including the balance after the action, is known at the moment the action is processed. Decide what to store at that moment rather than reconstructing it later.
Hint 3
Two accounts, one action: A transfer changes two balances. Work out what must be checked before either one changes, so that a failure cannot leave money half-moved.
Hint 4
The account you do not hold: One side of an external transfer has no balance in your system. Decide how that side is represented, and where in the flow the fee is computed and charged.
Hint 5
Money in between: Between initiation and arrival the amount is usable by neither account. Decide where it lives during that time, and what event moves it to the destination.
Follow-up questions
- Two threads run opposite transfers between the same two accounts at the same time. How do you keep balances correct without deadlocking?
- The service crashes halfway through the input file and the file is replayed. How do you avoid applying actions twice?
- How would you support cancelling a slow transfer, or reversing one that the external bank rejects?
- Accounts grow to millions of transactions. How would you answer "what was this balance at a given time" efficiently?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.