Most backend interview practice asks you to write code.
Let's do the opposite.
You're reviewing a pull request for a Spring Boot application.
The endpoint transfers money between two accounts.
The code compiles. It looks reasonable.
But there are 3 important bugs hiding in it.
Can you find all three before reading the answers?
@PostMapping("/transfer")
public ResponseEntity<?> transfer(@RequestBody TransferRequest request) {
Account sender = accountRepository
.findById(request.getFromAccountId())
.orElseThrow();
Account receiver = accountRepository
.findById(request.getToAccountId())
.orElseThrow();
if (sender.getBalance() < request.getAmount()) {
return ResponseEntity
.badRequest()
.body("Insufficient funds");
}
sender.setBalance(
sender.getBalance() - request.getAmount()
);
receiver.setBalance(
receiver.getBalance() + request.getAmount()
);
accountRepository.save(sender);
accountRepository.save(receiver);
return ResponseEntity.ok("Transfer completed");
}
Assume:
-
balanceandamountaredouble - both accounts are stored in a relational database
- multiple transfer requests can happen concurrently
- there is no additional validation or transaction logic elsewhere
Before scrolling further, write down the 3 problems you'd flag in the PR.
Bug #1: The amount isn't validated
Nothing prevents:
amount = -500
Now look at the calculations:
sender.getBalance() - (-500)
receiver.getBalance() + (-500)
The sender gains 500 and the receiver loses 500.
A financial operation should reject zero or negative transfer amounts before touching either account.
Bug #2: double is being used for money
Floating-point numbers cannot precisely represent many decimal values.
Something as ordinary as repeated operations involving values such as 0.1 can introduce rounding errors.
Money should normally use a decimal representation such as Java's BigDecimal, with an explicit rounding policy where necessary.
Bug #3: The balance check and update aren't concurrency-safe
This one is easier to miss.
Imagine the sender has $100.
Two requests arrive at almost the same time:
Transfer A: $80
Transfer B: $80
Both requests can read the original $100 balance.
Both pass:
if (sender.getBalance() < request.getAmount())
Then both continue with the transfer.
Depending on the database behavior and update strategy, you can get lost updates, inconsistent balances, or allow operations that should never have passed together.
The check and mutation need to be protected as one consistent operation, for example with appropriate transaction boundaries and locking/atomic database semantics.
Catching these bugs after someone explains them is easy.
The interesting question is:
Would you have caught them while reviewing the PR yourself?
That's the skill I've been experimenting with in ReviewIQ.
You choose your role, programming language and seniority and get an unfamiliar pull request containing 3 planted bugs.
You review it without seeing the answers first.
Then your review gets graded on what you caught, what you missed and how you explained it.
First 5 reviews are free. No card:
https://reviewiq-ruby.vercel.app/
If you try one, I'm curious what score you get.
Top comments (0)