- Which additional precondition is necessary and sufficient to ensure that an implementation using
balance += amount can satisfy the contract without integer overflow? Answer: amount <= Integer.MAX_VALUE - balance must hold when the method is called.
When reasoning about integer overflow in Java, your job is to find the tightest condition that prevents the addition from exceeding Integer.MAX_VALUE (2,147,483,647) while also being provable before the operation runs.
For balance += amount to be safe, you need balance+amount≤Integer.MAX_VALUE, which rearranges to amount≤Integer.MAX_VALUE−balance. This is exactly what A states, and it's a precondition — something that must hold when the method is called, before any assignment occurs. It's also sufficient: if this holds, the addition is mathematically guaranteed not to overflow, regardless of the specific values of balance or amount.
B is wrong because capping only balance at Integer.MAX_VALUE - 1 doesn't prevent overflow — if amount is large (say, Integer.MAX_VALUE - 1 itself), the sum still wraps. It ignores the relationship between both variables.
C is wrong for a similar reason: amount < Integer.MAX_VALUE is an extremely loose bound. An amount of Integer.MAX_VALUE - 1 satisfies this condition yet can still cause overflow when added to any positive balance.
D is wrong because it checks the condition after the assignment — but in Java, overflow has already happened silently by then. A precondition must be verifiable before the risky operation executes.
As a study tip: whenever a question asks about overflow safety, immediately rearrange the overflow inequality (a + b <= MAX) to isolate what must be true before the computation. Preconditions always precede the operation they guard. - A caller invokes
put(null, "x"). Which conclusion follows from the stated contract alone? Answer: The method may return, throw, or change state because the caller violated the precondition.
When you see a question about method contracts and preconditions, focus on what the contract actually promises versus what it leaves unspecified. A contract defines obligations only when its preconditions are met — violations place the caller outside the contract's scope entirely.
Here, the contract for put(String key, String value) requires key != null. Because the contract makes no promises for precondition-violating calls, passing null releases the method from any obligation whatsoever. The method could throw an exception, silently return, corrupt state, or do anything else — all of it is contractually permissible. This is exactly what C captures: when a caller breaks a precondition, the contract offers no guarantees, so any behavior is possible.
A is wrong because it asserts two specific things — a NullPointerException and an unchanged map — neither of which the contract promises. Requiring a specific exception type and a specific state outcome goes far beyond what's stated. B is similarly too strong: it claims the method must throw some unchecked exception before touching the map. Again, the contract makes no such guarantee for precondition violations; you're inventing a promise the contract never made. D flips to the opposite extreme, claiming the method must store the value. That would apply the normal postcondition even though the precondition was violated — which is precisely what the absence of an exceptional postcondition does not support.
A useful rule of thumb: undefined behavior is not the same as forbidden behavior, and it's not the same as mandated behavior either. When a precondition is violated, treat the outcome as genuinely unknown — that's the contractually correct answer.
- Which constructor implementation establishes the invariant and satisfies the stated postcondition for every permitted input?
Answer: low = Math.min(a, b); high = Math.max(a, b); using ordered endpoints.
When a constructor must establish an invariant, your job is to ask: "Does this code guarantee the required relationship holds for every valid input?" For a Range, that means low <= high must be true no matter what two integers the caller passes in.
The reliable way to enforce low <= high is to assign the minimum of the two arguments to low and the maximum to high, which is exactly what B does with Math.min(a, b) and Math.max(a, b). Whether the caller passes (3, 7), (7, 3), or (5, 5), the assignments are always correct — the smaller value lands in low and the larger in high.
A fails immediately because it blindly assigns a to low and b to high. If the caller passes (7, 3), you get low = 7 and high = 3, which violates the invariant directly.
C uses Math.abs, which converts both values to their magnitudes. This is unrelated to ordering — passing (-5, -3) gives low = 5 and high = 3, still breaking the invariant. It also changes the meaning of the values entirely, which contradicts the postcondition.
D looks clever but contains a subtle bug: when a >= b, it sets high = a - 1 instead of a. So passing (5, 5) yields low = 5, high = 4 — still a violation.
Strategy tip: When evaluating constructor correctness, always test boundary cases mentally: equal arguments, reversed order, and negatives. A single counterexample is enough to disqualify an answer.
- Which return statement best satisfies the method contract, assuming the invariant holds on entry?
Answer: return List.copyOf(tags); because it produces an unmodifiable list whose contents are fixed at call time, independent of later field mutations.
When a method contract promises an independent snapshot — meaning the caller can't modify the returned data, and future internal changes won't affect it — you need two guarantees simultaneously: immutability and a copied backing store. Keep both requirements in mind as you evaluate each option.
List.copyOf(tags) satisfies both conditions perfectly. It creates a new list whose contents are captured at call time, so subsequent mutations to the internal tags field don't bleed through. Additionally, the returned list is unmodifiable, so callers can't corrupt the object's logical state through the return value. This makes D the correct answer.
A fails immediately: returning the field reference directly gives the caller a live handle to the private list, allowing them to add, remove, or clear elements — a direct invariant violation. B (new ArrayList<>(tags)) gets the "independence" requirement right (the copy is snapshot-accurate), but it hands the caller a mutable list. They can modify their copy freely, which technically doesn't break the object's internal state, but it violates the contract's promise of an unmodifiable return value. C (Collections.unmodifiableList(tags)) wraps the original list in a read-only view — mutation attempts throw exceptions, which sounds good. However, it holds a reference to the same backing list, so if the object later adds or removes tags, those changes are visible through the returned view. That breaks the snapshot guarantee.
A useful pattern to remember: whenever a contract says "independent and unmodifiable," look for List.copyOf() — it handles both in one call, unlike the two common near-misses (B handles independence, C handles immutability, but neither handles both).
- Which loop invariant is strong enough to establish the intended postcondition and is preserved at each loop iteration?
Answer: 0≤i≤a.length and total equals the sum of elements with indices from zero through i - 1.
When working with loop invariants, your goal is to find a statement that (1) holds before the loop begins, (2) is preserved after every iteration, and (3) directly implies the postcondition once the loop ends. Think of it as a "progress snapshot" — it should describe exactly what has been computed so far.
Answer A is correct. The invariant 0≤i≤a.length paired with "total equals the sum of elements from index 0 through i−1" satisfies all three criteria. Before the loop, i=0 and total = 0, which is the empty sum from 0 through -1 — trivially true. After each iteration, total absorbs a[i] and then i increments, so the invariant is restored. When the loop exits, i=a.length, so "total equals the sum from 0 through a.length−1" — that's the entire array, which is exactly the postcondition.
Answer B fails because i<a.length excludes the exit condition i=a.length, so it can never establish the postcondition. Also, after adding a[i] but before incrementing, the sum-through-i claim doesn't survive the increment step cleanly.
Answer C claims total always equals the sum of all elements, which is false at the start and during early iterations — it's too strong to be preserved initially.
Answer D uses 0<i, which fails before the first iteration when i=0, so the invariant isn't established at initialization.
Study tip: Always check a loop invariant at three checkpoints — initialization, maintenance, and termination. If any one fails, the invariant is invalid. - Which postcondition completely states the required result without incorrectly requiring the first duplicate occurrence?
Answer: The result is -1 iff no valid index contains key; otherwise it is a valid index containing key.
When writing or evaluating postconditions, you need to ask: does this statement capture exactly what the method must do — no more, no less? A postcondition that's too weak allows wrong implementations to pass; one that's too strong over-constrains a correct implementation.
The passage asks for a postcondition that correctly handles both the "found" and "not found" cases without imposing extra requirements like returning the first or best matching index. Answer B does this precisely: it states that -1 is returned if and only if no valid index contains key, and otherwise the result is some valid index containing key. The biconditional ("iff") locks down both directions — -1 means absent, a valid index means present — without dictating which valid index is returned when duplicates exist. That's a complete, tight specification.
Answer A is too weak. It says the result is either -1 or a valid matching index, but it doesn't require that -1 only appears when key is absent. An implementation that always returns -1 would technically satisfy A, even when key is present — that's a broken contract.
Answer C over-constrains the method by requiring the smallest (first) index. The passage explicitly says returning any matching index is acceptable, so demanding the minimum index is an unnecessary restriction that would reject valid implementations.
Answer D is completely off-base — it describes binary search insertion logic, not a search-and-return contract. It conflates two different operations entirely.
When evaluating postconditions on exams, check for three things: completeness (all cases covered), tightness (no false positives), and absence of extra constraints that the spec never asked for.
- Which change is required to make the method compatible with the invariant under possible listener reentry?
Answer: Set count to zero before invoking the listener, so reentrant calls observe a consistent state.
When an object exposes public methods that depend on an invariant, you must ensure that invariant holds at every point where external code can observe the object's state — not just at the final return. This question tests your understanding of object invariants under reentrant callbacks.
Here's the core problem: the method clears the list first, then calls the listener, then sets count to zero. But if the listener immediately calls another public method on the same object, it sees a cleared list (size() == 0) while count still holds its old value. The invariant count == list.size() is broken during the callback window, causing reentrant calls to operate on corrupted state.
Answer A is correct because setting count = 0 before invoking the listener ensures that when the listener re-enters any public method, both list.size() and count are already zero — the invariant holds throughout.
Answer B is the classic trap here. Invariants don't get a grace period until "final return." Any reentrant call that happens between the list clear and the count update will observe inconsistent state, causing real bugs.
Answer C is a threading solution to what is actually a sequencing problem. volatile ensures visibility across threads but does nothing to fix the order of updates within a single-threaded reentrant call chain.
Answer D is defensive documentation rather than a real fix — pushing responsibility onto callers instead of designing a robust API.
The study tip: whenever a method calls external code (listeners, callbacks, hooks) during a state transition, treat that call as a potential re-entry point and ensure all invariants hold before making that call.
- Which implementation satisfies both postconditions?
Answer: balance -= amount; return balance;
When evaluating method implementations against postconditions, you need to trace through each line of code carefully and check two things: (1) does the field end up with the right value, and (2) does the method return the right value? Here, the postconditions require that balance becomes balance - amount and that the returned value equals the new balance.
Option B — balance -= amount; return balance; — satisfies both conditions cleanly. After balance -= amount, the field holds the updated value. Returning balance at that point returns the new balance, matching both postconditions exactly.
Option A is a classic trap: it saves the old balance before updating, then returns that old value. The field update is correct, but the return value is the pre-withdrawal balance, not the new one — violating the second postcondition.
Option C is simply wrong on both counts. balance = amount replaces the balance with the amount rather than subtracting it, so neither the field update nor the return value is correct.
Option D updates balance correctly with balance -= amount, but then returns balance - amount, which subtracts amount a second time. If you withdrew $50 from a $200 balance, you'd return $100 instead of $150 — off by one subtraction.
The key strategy here is to mentally simulate the code with concrete numbers. Pick something like balance = 100 and amount = 30, then trace each option step by step. This immediately exposes whether the field and return value match the postconditions, and it's a reliable technique for any question testing method correctness.
- Which override contract is behaviorally substitutable for the superclass contract?
Answer: It accepts every int value and ensures that the returned value is positive.
When a subclass overrides a method, it must follow the Liskov Substitution Principle (LSP): any code written against the superclass should work correctly when given a subclass instance instead. This creates specific rules about how preconditions (requirements) and postconditions (guarantees) may change — preconditions can only be weakened (accept more), and postconditions can only be strengthened (guarantee more).
Option C is the correct answer because it weakens the precondition (accepting every int, not just x >= 0) and strengthens the postcondition (guaranteeing a positive return value, which is a subset of nonnegative). Any client expecting the superclass behavior — passing x >= 0 and receiving a nonnegative result — will still get exactly what it needs. Substitution holds perfectly.
Option A is wrong because it tightens the precondition to x > 0, excluding x = 0. A client that passes 0 (valid for the superclass) could now break — that's a substitution violation.
Option B is wrong because it weakens the postcondition: promising only "some int" instead of a nonnegative value. Clients relying on a nonnegative result could receive a negative number, violating the superclass contract.
Option D violates both rules simultaneously — it tightens the precondition (x >= 10) and, while returning a positive value strengthens the postcondition, the stricter precondition alone is enough to break substitutability.
A helpful memory device: think "accept more, promise more" — a safe override opens the door wider for inputs and delivers at least as much for outputs.
- After saving the selected item's old price and setting its new price to
oldPrice - amount, which update to total restores the invariant? Answer: total -= amount; because the selected item's contribution decreased by that amount.
When maintaining a cached value like total, the key question is: what changed? Rather than recomputing the entire sum, you only need to account for the difference introduced by the operation.
Here, applyDiscount reduces one item's price by amount. The rest of the items are untouched, so total was already correct for everything except that one item. The selected item's contribution to total shrinks by exactly amount, so the correct fix is simply total -= amount. This is answer D — clean, minimal, and precisely reflects what changed.
Answer A sets total equal to just the discounted item's price, which would be wildly wrong unless the cart had only one item. It confuses the cache of the entire sum with the price of a single item.
Answer B removes the item's original price from total but never adds back the new discounted price. You'd be undercounting by oldPrice - amount — essentially pretending the item disappeared from the cart entirely.
Answer C adds the new discounted price into total without removing the old price first. Since the old price was already baked into total, you'd be double-counting the item, inflating the total rather than reducing it.
A useful strategy: when updating a cached aggregate, think in terms of deltas rather than absolute values. Ask yourself, "What is the net change to the total?" Here, one price dropped by amount, so the total drops by amount. If you find yourself referencing the full old or new price, that's a signal you may be over- or under-counting.