This file summarizes interview-ready explanations, likely technical questions, and pair-programming exercises for this shopping cart project.
This project implements a simple shopping cart library.
Core capabilities:
- Add product by name and quantity
- Retrieve product price through
PriceStrategy - Aggregate quantity for the same product
- Calculate subtotal
- Apply cart-level percentage discount
- Calculate 12.5% tax
- Calculate total payable
- Handle invalid input and API failures
Main classes:
ShoppingCart: cart state and business logicPriceStrategy: abstraction for price retrievalApiPriceStrategy: retrieves prices from the Equal Experts price APITaxStrategy: abstraction for tax calculationDefaultTaxStrategy: calculates 12.5% taxProduct: product value objectCartItem: product plus quantityPriceRetrievalException: runtime exception for price lookup failure
Interview answer:
I kept the design intentionally simple.
ShoppingCartowns cart behavior and state. It does not know how prices are fetched or how tax is calculated. Those behaviors are injected throughPriceStrategyandTaxStrategy, which makes the cart easy to test and keeps API integration separate from business logic.
Question:
Why did you use
PriceStrategyandTaxStrategy?
Answer:
I used small strategy interfaces because price retrieval and tax calculation are behaviors that can vary. Price could come from an API, mock, file, or database. Tax rules could also change. Injecting these dependencies keeps
ShoppingCartfocused on cart behavior and improves testability.
Important note:
I avoided adding extra layers like repositories, controllers, services, persistence, or APIs because the assignment explicitly values simplicity.
Question:
Why use
BigDecimalinstead ofdouble?
Answer:
Money should not be represented with floating-point types because
doublecan introduce precision errors.BigDecimalgives exact decimal arithmetic and lets us explicitly control rounding.
Example:
new BigDecimal("2.52")Better than:
new BigDecimal(2.52)Because string construction avoids binary floating-point precision issues.
Question:
Why use
RoundingMode.HALF_UP?
Answer:
HALF_UPis common financial rounding and matches the sample result from the assignment. The sample tax is15.02 * 0.125 = 1.8775, which rounds to1.88.
Possible follow-up:
The requirement says "rounded up where required." If interpreted literally,
RoundingMode.CEILINGcould be used, but since the sample matches normal financial rounding, I usedHALF_UP. In a real project, I would clarify the exact rounding rule.
Without discount:
subtotal = sum(item price * quantity)
tax = subtotal * 12.5%
total = subtotal + tax
With discount:
discount = subtotal * discountPercentage / 100
discountedSubtotal = subtotal - discount
tax = discountedSubtotal * 12.5%
total = discountedSubtotal + tax
Interview answer:
I apply discount before tax because tax is usually calculated on the payable amount after discount. This is a business rule, so I would confirm it with the product owner if it were not specified.
Question:
Why are some fields final and some not?
Answer:
I use
finalfor fields whose reference should not change after construction. For example,priceStrategy,taxStrategy, anditemsshould always refer to the same dependencies or collection. Fields that represent changing state, likequantityordiscountPercentage, are not final.
Important detail:
private final Map<String, CartItem> items = new HashMap<>();This means the map reference cannot be replaced, but its contents can still change.
Question:
Is the cart immutable?
Answer:
No.
ShoppingCartis mutable because adding products changes cart state.Productis immutable because its fields are final and there are no setters.CartItemis partially mutable because quantity can increase.
Question:
Is
getItems()fully immutable?
Answer:
It returns an unmodifiable map, so callers cannot add or remove entries from the map. However, the
CartItemobjects inside are still objects. A stronger design would return a read-only snapshot or DTO, but that may be more complexity than needed for this assignment.
Question:
What does
ApiPriceStrategydo?
Answer:
It builds the product URL, calls the Equal Experts price API using Java
HttpClient, validates the HTTP status, parses the JSON response using Gson, and returns the price asBigDecimal.
Question:
What happens if API returns non-200?
Answer:
The code throws
PriceRetrievalException.
Question:
What happens if price is missing?
Answer:
The parsed response is validated. If the response is null or price is null,
PriceRetrievalExceptionis thrown.
Question:
Why wrap exceptions in
PriceRetrievalException?
Answer:
It gives callers a domain-specific failure instead of exposing low-level exceptions like
IOException,InterruptedException, JSON parsing errors, or invalid URI errors.
Question:
What types of tests are used?
Answer:
ShoppingCartTesttests cart behavior using fake price data.DefaultTaxStrategyTestdirectly tests tax calculations.ApiPriceStrategyTestvalidates API behavior. Mockito-based tests show how dependencies can be mocked for deterministic unit tests.
Question:
Why use Mockito for
ShoppingCart?
Answer:
ShoppingCartdepends on collaborators:PriceStrategyandTaxStrategy. Mockito lets me isolate cart behavior by controlling those dependencies and verifying interactions.
Question:
Why not use Mockito for
DefaultTaxStrategy?
Answer:
DefaultTaxStrategyhas no external dependency. It is a pure calculation, so direct input-output tests are clearer and more valuable.
Question:
Should
ApiPriceStrategybe tested with Mockito?
Answer:
It can be tested with Mockito by mocking
HttpClientandHttpResponse, but this is easier ifHttpClientis injected. Since the production class currently creates its ownHttpClient, reflection is needed in tests if we do not want to change production code. For cleaner design, I would prefer constructor injection.
Question:
Should unit tests call the real API?
Answer:
Usually no. Unit tests should be fast, deterministic, and offline. Real API tests can fail due to network, API downtime, or changed data. I would keep real API tests as integration tests, separated from normal unit tests.
Recommended setup:
Unit tests:
- ShoppingCart with mocked/fake PriceStrategy
- DefaultTaxStrategy direct calculation tests
- ApiPriceStrategy with mocked HttpClient or fake local server
Integration tests:
- Optional real Equal Experts API test, tagged separately
Question:
What is Test Driven Development?
Answer:
TDD is a development approach where I write a failing test first, then write the simplest production code to make it pass, and finally refactor while keeping tests green.
Cycle:
Red -> Green -> Refactor
Discount example:
First I wrote tests for default discount, percentage discount, and invalid discount. The tests failed because the methods did not exist. Then I added
applyDiscountandgetDiscount, updated tax and total calculation, and reran the full test suite.
Question:
How does discount work?
Answer:
Discount is cart-level percentage discount. It is applied to subtotal before tax. The discount percentage must be between 0 and 100.
Example:
Subtotal = 5.04
Discount = 10%
Discount amount = 0.50
Subtotal after discount = 4.54
Tax = 0.57
Total = 5.11
Question:
Why use percentage discount instead of flat discount?
Answer:
Percentage discount is simple and common. If requirements asked for flat discount, coupon codes, item-level discount, or buy-one-get-one offers, I would model discount as a separate strategy.
Question:
What validation is needed for discount?
Answer:
Null discount, negative discount, and discount greater than 100 should be rejected.
Answer:
The cart stores items in a map keyed by product name. If the product already exists, it increases the existing quantity instead of adding a duplicate entry.
Answer:
Currently yes. The API expects specific lowercase product names. If required, I would normalize input using
trim().toLowerCase()before lookup.
Answer:
It avoids unnecessary API calls and gives immediate feedback for invalid input.
Answer:
Zero or negative quantity does not make sense for adding a product. Invalid quantities should fail fast.
Answer:
Current behavior calls the price strategy each time before aggregation. Whether this is correct depends on business rules. If cart price should be fixed from first add, we can avoid repeated price lookup. If price should always be current, repeated lookup is acceptable.
Answer:
With the current implementation, the existing
CartItemkeeps the originalProductand price when quantity is aggregated. The second fetched price is not used if the item already exists. If price changes matter, we should define whether to update price, keep first price, or store line items separately.
Answer:
No. The cart uses mutable state and a
HashMap. The README assumes single-threaded usage. For concurrent use, I would add synchronization or use a concurrent structure, but only if required.
Answer:
It keeps the cart API simple. Price retrieval failure is usually not recoverable at the low-level cart operation. In a larger app, this could be handled at the boundary layer and mapped to user-facing error behavior.
Answer:
It is a lightweight JSON parser and enough for this simple API response.
Answer:
The assignment explicitly says not to submit an app or add unnecessary architectural layers. This is a library-style solution focused on the required behavior.
Answer:
It prevents external callers from directly changing the cart map. Cart state should be changed through cart methods.
Answer:
I would separate real API tests from unit tests, make
HttpClientinjectable for cleaner testing, possibly return immutable cart snapshots, and clarify rounding and discount business rules.
Answer:
It avoids duplicating tax calculation logic. For larger systems, a
CartSummaryobject could calculate subtotal, discount, tax, and total once to avoid repeated computation.
Answer:
Not for this small cart. The cart has few items and calculations are cheap. If performance became a concern, I would calculate a summary once or cache totals carefully.
Answer:
Currently that could lead to a later failure. A defensive improvement would validate returned price in
ShoppingCartor guarantee non-null price throughPriceStrategycontract.
Answer:
Current code does not validate negative price. Since prices come from trusted API, it may be acceptable for this assignment. A robust improvement would reject null or negative prices.
Answer:
Classes are grouped into
model,pricing,tax,service, andcommon, which keeps responsibilities discoverable without adding unnecessary layers.
Answer:
Gson can populate private fields reflectively. The production code only needs getters.
Answer:
It verifies API failure handling and ensures invalid or unsupported product names are converted into
PriceRetrievalException.
Answer:
Parameterized tests are useful when the same behavior needs to be verified for multiple inputs. For example, checking all known products and expected prices.
Requirement:
cart.removeProduct("cornflakes");Expected approach:
- Add test for removing existing product.
- Add test for removing missing product.
- Decide whether missing product should throw or do nothing.
- Implement in
ShoppingCart.
Possible answer:
I would choose no-op for missing product unless requirement says otherwise, because removing something absent can be treated as idempotent.
Requirement:
cart.updateQuantity("cornflakes", 3);Rules to clarify:
- Should quantity zero remove item?
- Should missing product throw?
- Should update fetch latest price?
Good interview response:
I would clarify whether update means replacing quantity or adding quantity. Current
addProductincrements quantity, soupdateQuantityshould probably set the quantity directly.
Requirement:
cart.applyFlatDiscount(new BigDecimal("5.00"));Clarify:
- Can discount exceed subtotal?
- Is tax calculated after discount?
- Can flat and percentage discounts both apply?
Requirement:
cart.applyDiscount("cornflakes", new BigDecimal("10"));Expected design:
If discount rules become complex, introduce a
DiscountStrategy. For one simple discount, keep it simple inside cart logic.
Requirement:
CartSummary summary = cart.getSummary();Fields:
items
subtotal
discount
tax
total
Why useful:
It avoids callers making multiple getter calls and gives one consistent snapshot of cart state.
Expected answer:
I would avoid real network calls in unit tests. I can mock
HttpClient, use a fake local HTTP server, or refactorApiPriceStrategyto accept a small HTTP abstraction.
Requirement:
Make
ApiPriceStrategyeasier to test.
Good solution:
public ApiPriceStrategy() {
this(HttpClient.newHttpClient());
}
ApiPriceStrategy(HttpClient httpClient) {
this.httpClient = httpClient;
}Answer:
Constructor injection improves testability without changing runtime behavior.
Requirement:
cart.addProduct(" Cornflakes ", 1);Expected behavior:
cornflakes
Implementation:
String normalizedName = name.trim().toLowerCase();Clarify:
I would confirm whether display name should preserve original casing.
Requirement:
Reject null or negative prices returned by
PriceStrategy.
Test first:
when(priceStrategy.getPrice("cornflakes")).thenReturn(null);
assertThrows(PriceRetrievalException.class, ...);Clarify exception type:
Could use
IllegalStateExceptionor a domain exception. Since price retrieval failed contract validation,PriceRetrievalExceptionis reasonable.
Expected Gradle idea:
@Tag("integration")Then configure normal test task to exclude integration tests.
Interview answer:
This keeps normal unit tests fast and reliable while still allowing real API verification when explicitly requested.
Question:
What concerns do you see in this code?
Answer:
Main concerns are real API tests in the normal test suite, hardcoded
HttpClientmaking API tests harder, returned cart items not being fully immutable, and unclear business rules around rounding and discount application.
Question:
Is there overengineering?
Answer:
The current strategy interfaces are acceptable because they improve testability and represent varying behavior. I would avoid adding more layers unless new requirements justify them.
Question:
Is the README good enough?
Answer:
It explains design, assumptions, AI usage, and test command. I would fix encoding artifacts and typos before final submission.
Example:
() -> priceStrategy.getPrice(productName)Answer:
It is a short implementation of a functional interface. In JUnit
assertThrows, the lambda represents executable code that JUnit runs and checks for an exception.
Answer:
An interface with exactly one abstract method. Lambdas can be used to implement functional interfaces.
Answer:
It runs the provided executable block and passes if the expected exception type is thrown.
Answer:
It defines behavior for a mock. When the mocked method is called with matching arguments, Mockito returns the configured value.
Answer:
It checks that a mock method was called as expected.
Answer:
It runs before each test method, usually to create fresh test objects or common setup.
Answer:
BigDecimal.equalschecks value and scale, so2.50and2.5are not equal.compareTocompares numeric value.
Use this if asked to summarize your solution:
I built a simple library-style shopping cart solution. The cart supports adding products, aggregating quantities, calculating subtotal, discount, tax, and total. I used
BigDecimalfor monetary accuracy and explicit rounding. I separated price retrieval and tax calculation behind small interfaces so the cart logic is easy to test. The API client validates status codes and response payloads and throws a domain exception for failures. Tests cover cart calculations, validation, tax calculation, API handling, and Mockito-based dependency isolation. I kept the design intentionally small because the assignment asks for simplicity rather than unnecessary architecture.