Java Guild course · Lesson 0009

Lesson 0009 · retrieval practice

The Interview Viva

Everything in this course, interleaved and out of order, the way it will actually be asked.

A tech test is rarely marked in isolation. Someone will sit down with your submission open and ask you about it — and the questions will not arrive in the order you did the work. This lesson mixes them deliberately.

How to use this lesson

Answer out loud before you click. Recognising the right option is far easier than producing the answer, and only producing it builds the retrieval strength you need under pressure. Say your whole answer as a sentence, then check.

Come back to this page three days after you finish it, then again the day before the interview. Spacing is what turns this from fluency into retention.

The five questions you are near-certain to be asked

Rehearse these until they are two sentences each, not paragraphs.

QuestionThe shape of a strong answer
“Talk me through your approach.”The reframe. The README describes a blank page, but the repo had a working implementation, so I treated it as legacy code: golden master, then unit tests, then test-driven fixes.
“Why custom exceptions rather than returning false?”They were in the repo unused, and the brief names exactly those three scenarios. A boolean cannot distinguish not found from already borrowed — which is the bug the original code had.
“Checked or unchecked, and why?”Checked, because the caller has a real recovery. Oracle’s rule of thumb. And name the counterargument before they do.
“I see Mockito in the pom — why didn’t you use it?”Mocks are for collaborators that are slow, non-deterministic, or hard to drive. Streams are none of those. I’d have mocked the repository to test a disk failure.
“What would you do next?”The SQLite repository, because the interface is already there. Then a Copy/Loan model, because the title is currently the identity. Have a third ready.

Interleaved drill

“Your Library has no equals on Book. Does that matter?”

  1. No, because Book is mutable and mutable types must never define equality
  2. No, because the collection is a List and lists do not consult equals at all
  3. Yes, because contains, remove and assertEquals all silently use identity
  4. Yes, because the compiler emits a warning when hashCode is left unoverridden

Mutability makes equality hazardous in hashed collections — a real point — but plenty of mutable types define equals sensibly. Note that title and author here are already final; only the borrowed flag moves.

List.contains, indexOf and remove(Object) all use equals. This is exactly backwards.

Right, and the honest answer names the consequence: two Book objects with identical title and author are currently unequal, so assertEquals on books compares references and any future remove would fail. Whether to add it is a judgement call — but not noticing is not.

Javac emits no such warning. Some static analysers do; the compiler does not.

“Why didn’t you make Book a record?”

  1. The pom targets Java 11 and records need 16, plus the borrowed flag mutates
  2. Records cannot declare a custom toString, which this class needs to override
  3. Records are unsuitable for any domain entity, being intended for use as DTOs
  4. Records would prevent the class from being serialised to the flat state file

Both halves, and give both. The build constraint is decisive on its own — javac --release 11 rejects records outright — and even on Java 21 the mutable isBorrowed flag makes Book a poor record. Mentioning that you checked the compiler target is the detail that lands.

Records can override toString freely. Not a limitation.

Too strong, and it is a claim you would have to defend. Records model immutable values well, whatever layer they sit in.

Nothing about records affects the hand-written text format, and they serialise normally besides.

“Two people borrow the same book at the same instant. What happens?”

  1. The second caller receives an AlreadyBorrowedException, as the flag is set
  2. The ArrayList throws ConcurrentModificationException and both borrows fail
  3. Nothing goes wrong, since each borrow runs inside its own separate thread
  4. Both may succeed: the check and the set are not atomic and nothing guards them

That is what happens when the calls are sequential. Interleave them and both threads can read isBorrowed == false before either writes.

That is raised by structural modification during iteration, not by mutating a field on a book already in the list.

Separate threads are the cause of the problem, not a defence against it. The shared Book is the contended state.

Correct, and it is a check-then-act race on a non-volatile field. The right follow-up is scope: “the brief describes a single-user console app, so I didn’t build for concurrency — but if this became a service I’d need to make borrow atomic, and I’d note the flag isn’t even safely published across threads.” Knowing it and consciously descoping it is the answer.

“Your golden master approved a crash as correct behaviour. Isn’t that a bad test?”

  1. Yes, so any scenario that crashes should be excluded from the approved file
  2. No, it records behaviour rather than correctness, and the crash is behaviour
  3. Yes, but it is acceptable temporarily while the code is still being changed
  4. No, because the crash was expected to be fixed within that very same commit

Excluding them removes the most valuable scenarios — the crashes are precisely the behaviour you set out to change, so they are the hunks that make the final diff persuasive.

This is the answer, and it distinguishes the two kinds of test cleanly. A characterisation test asserts what is; the unit tests assert what should be. Conceding the premise here would be conceding you do not know what a golden master is for.

It is not a temporary compromise. Approving current behaviour, including bad behaviour, is the technique working exactly as intended.

The baseline was taken several commits before the fix, which is the point — and if it had been fixed in the same commit there would be no diff to show.

“What is the worst bug you found in the code we sent you?”

  1. The InputMismatchException, since a single keypress terminates the program
  2. The IOException type, since it misdescribes what category of failure occurred
  3. The unescaped separator, since it destroys a book’s author with no signal
  4. The unused exceptions package, since dead code misleads every future reader

A strong second, and worth naming — it also loses the whole session’s work, since state is only saved on a clean exit. But it announces itself, so it gets fixed.

The most conspicuous defect and the one you fixed first, but it is a design flaw. It confuses maintainers; it does not lose anybody’s data.

The best answer, because the reasoning is about severity rather than about which bug is most obvious. Silent data corruption outranks a crash: the crash is found in minutes, the corruption is found months later, if ever. Saying so shows you rank defects the way someone who has run software in production does.

Not a bug at all — it was the hint that told you where to start. Calling it the worst bug would be misreading the exercise.

“You deviated from the method signatures in our brief. Justify that.”

  1. With exceptions signalling failure the boolean is unreachable, and I said so
  2. The brief lists signatures as guidance only, so no justification is required
  3. Returning void is the modern convention and boolean returns are discouraged
  4. Changing it was needed to make the golden master and unit tests both compile

Two things in one sentence: the technical reason, and the fact that you documented it rather than smuggling it. The second half is what turns a deviation into evidence of judgement.

The brief also says “visibility at candidate discretion”, which invites judgement — but answering a request for justification with “none is required” is exactly the wrong instinct to display.

No such convention exists, and appealing to fashion rather than reasoning is a weak move in front of anyone senior.

Untrue — both would compile against a boolean-returning method. Inventing a technical necessity that does not exist is the worst option here.

“How would you add the ability to hold three copies of the same title?”

  1. Store a count on Book and decrement it each time a copy is borrowed out
  2. Permit duplicate Book entries in the list and borrow whichever is first free
  3. Key the collection by title and hold a list of Book objects against each key
  4. Separate the work: a Book describes a title, a Copy is the thing borrowed

A count tells you how many are out but not which, so you can never model a loan, a due date or a borrower. It defers the modelling rather than doing it.

This is roughly what the current code allows, and the reason the bug exists: searchBook returns the first match, so the others are unreachable.

Better, and a reasonable intermediate step — but it still leaves Book carrying a borrowed flag, so a Book is still simultaneously a description and a physical object.

The real answer, and the vocabulary matters: Book becomes an immutable description (title, author), Copy carries the borrowable state. The current model conflates them, which is why a title can only ever be lent once. Add that you would not have built this for the test, because the brief does not ask for it.

“Did you do TDD?”

  1. Yes, every test in the submission was written before its production code
  2. Partly: existing behaviour was characterised, new behaviour was driven test-first
  3. No, since the implementation already existed there was nothing left to drive
  4. Yes, and the golden master was the first failing test of the whole exercise

Overclaiming, and easily checked. The Book tests went green the moment they were written, because Book already worked.

The honest and the strongest answer — and being able to draw the line is itself the evidence. Point at the commits: the golden master and unit tests characterise, the exception and persistence commits each began with a failing test.

Underclaiming, and untrue. The three domain exceptions did not exist in any code path, and were driven by a test that failed with “expected AlreadyBorrowedException but was IOException”.

A golden master is generated from existing behaviour, so it is green on arrival by construction. Calling it a failing test to someone who knows the technique is the worst available answer.

The question to ask them

Turn it around

When they ask whether you have questions, one that works well here:

“The starter had three unused exception classes in it. Was that deliberate — do you use the test partly to see whether candidates read the code they are given, rather than just the brief?”

It is a genuine question, it demonstrates the observation without boasting about it, and the answer tells you something real about how they assess.

Read this

Primary source

Your own submission. The night before, read your diff from the baseline commit — git diff HEAD~12 --stat, then the full diff — and for every hunk ask “why did I do that, and what did I reject?” Anything you cannot answer in one sentence is a question you are about to be asked.

Carry on

Ask me to run a mock viva: I will play a sceptical interviewer and push back on your answers, including where I think you are wrong. That is a much better use of an hour than re-reading these pages.