What is a Logical and Maintainable coding interview?
Amazon has a unique type of coding interview called “Logical and Maintainable” which is quite different than a typical Algorithm/Data Structure interview. In this type of coding interview, the candidate is expected to write clean, maintainable, and testable code. However, similar to other coding questions, candidates are still expected to write a workable (vs. pseudo code) but with less focus on implementing a specific algorithm.
Usually, the interview starts with a very vague question. Candidates are expected to ask many clarifying questions before proposing any approach. Once the solution is implemented, the interviewer usually adds more requirements to the original question. If the code is maintainable, the candidate should be able to quickly update the code and address the new requirement.
In this article, we demonstrate what a logical and maintainable coding interview looks like for the popular question “Design Amazon Lockers”. The question has been asked in many past Amazon interviews (example)
Design Amazon Lockers (Pickup Location)
Interviewer: An Amazon pickup location has various lockers for packages to be dropped off and picked up. We have both packages and lockers of varying sizes. Model the lockers, packages, and pickup location and implement an algorithm to find the best possible empty locker for a given package efficiently.
Start with Clarifying Questions
A sample communication between a candidate and an interview can look like:
- What are the sizes for packages and lockers?
- Small, Medium, Large
- Can I assume that a package should go to the corresponding locker size?
- Yes
- What if there is no small locker left and a small package arrives?
- Good question, if there is no locker with matching size of the package, the package should go to the next locker size available (e.g. Small package goes inside of a medium locker)
- Nice, how many lockers from each size exist in the pickup location?
- It changes per pickup location. Your model should be able to handle that.
- Understood, what if a package arrives and there is no valid locker available?
- Your pickup location should not accept the package.
- What do you mean by finding an empty locker efficiently?
- Well, customers drop and pick up packages constantly. The lockers becomes full and empty constantly as well. Your code should be able to find an available locker very quickly.
And the list of questions can go on (consider the interview time as well when asking questions) but you got the idea.
Propose Your Solution
Before you try to write any code, communicate with the interviewer about your approach (absolutely important). The interviewer might be able to help you right away if your solution cannot handle the requirements properly.
Note that there is no best solution for a problem. It’s a matter of proposing a good enough solution while being able to justify it. At first, you start with mentioning your classes, like separate classes for PickupLocation, Locker, and Package. For the logic, one possible solution is to use queues to track empty lockers for each size and a hash map to keep track of used lockers. Once you get a green light from the interviewer on your general approach, start coding your solution.
One possible implementation looks like the below (remember that there is no best answer and your code can be much different from this):
enum Size {
SMALL(0),
MEDIUM(1),
LARGE(2);
private int numVal;
Size(int numVal) {
this.numVal = numVal;
}
public int getNumVal() {
return numVal;
}
}
class PickupLocation {
Map<Size, Queue<Locker>> availableLockers;
Map<String, Locker> packageLoc;
public PickupLocation(Map<Size, Integer> lockerSizes) {
availableLockers = new HashMap<>();
packageLoc = new HashMap<>();
// Initialize lockers
for (Map.Entry<Size, Integer> entry: lockerSizes.entrySet()) {
Queue<Locker> lockerQ = new LinkedList<>();
for(int i=0; i< entry.getValue(); i++)
lockerQ.add(new Locker(entry.getKey()));
availableLockers.put(entry.getKey(), lockerQ);
}
}
public Locker assignPackage(Package p) {
for(Size sz:Size.values()) {
// Don't try to assign package to a smaller size
if(sz.getNumVal()<p.getSize().getNumVal()) continue;
Locker assignedLocker = assignLocker(p, sz);
// There is a locker found for the package
if(assignedLocker!=null) return assignedLocker;
// Continue with one size larger
}
return null;
}
public Package getPackage(String packageId) {
if(!packageLoc.containsKey(packageId)) return null;
Locker locker = packageLoc.get(packageId);
Package p = locker.emptyLocker();
// Put the locker back in the available queue
availableLockers.get(locker.getSize()).add(locker);
return p;
}
private Locker assignLocker(Package p, Size sz) {
if (availableLockers.get(sz).size() == 0) return null;
// Remove locker from the available queue
Locker lockerToAssign = availableLockers.get(sz).poll();
lockerToAssign.assignPackage(p);
packageLoc.put(p.getId(), lockerToAssign);
return lockerToAssign;
}
}
class Locker {
private final Size lockerSize;
private final String lockerId;
private Package packageInsideLocker;
Locker(Size size) {
this.lockerSize = size;
lockerId = UUID.randomUUID().toString();
}
private Size getSize() {
return lockerSize;
}
private String getId() {
return lockerId;
}
private void assignPackage(Package p) {
packageInsideLocker = p;
}
private Package emptyLocker() {
Package p = packageInsideLocker;
packageInsideLocker = null;
return p;
}
}
class Package {
private final Size packageSize;
private final String packageId;
Package(Size size) {
this.packageSize = size;
packageId = UUID.randomUUID().toString();
}
private Size getSize() {
return packageSize;
}
private String getId() {
return packageId;
}
}
Follow-up Questions
At this time, the interviewer adds more features and expects your code is flexible enough to address the new requirements.
Requirement 1 – Detect Unclaimed Packages
Interviewer: We need to find the packages that haven’t been picked up for a certain number of days.
Possible Solution: First, you need to modify your code to know when a package is assigned to a locker (left as an exercise). Detecting unclaimed packages is very similar to LRU cache where we want to find the least recently used items to remove. You can use a LinkedHashMap for packageLoc to still be able to quickly lookup the package while the packages are added in order of their assignment.
Requirement 2 – Frozen Food
Interviewer: The pick-up location now accepts frozen food as packages. We also want to add freezer lockers for the coming frozen foods. How do you address the new requirement?
Possible Solution: One way is to use composition (vs. inheritance) to add a new field to the package to mention whether a package has frozen food. Also, you can define separate queues for the freezer lockers to only accept frozen packages. Add comments to this post if you prefer inheritance over composition or vice versa.
Final Note
In the end, the interviewer evaluates you based on your clarifying questions, communication, justifications about your decisions, and cleanness and maintainability of your code. A maintainable code should be flexible enough to address new reasonable requirements and that’s what the interviewers are looking for in this type of interview.
TechMockInterview.com
If you are preparing for Amazon Interviews, ensure you don’t underestimate the logical and maintainable coding interviews. Book a coding mock interview with an Amazon Interviewer on TechMockInterview.com. In the booking form, mention that you want to have a logical and maintainable coding interview.