How I Cracked the Object Oriented Design Interview at Amazon

Unlock FAANG's Object Design Interview Secrets: Master the fundamentals of Object Oriented Programming (OOP) with our guide. Learn how to prepare for Amazon's OOP interviews, understand class design, and excel in extending functionalities through subclassing. Essential tips for aspiring FAANG engineers.

How I Cracked the Object Oriented Design Interview at Amazon
Do not index
Do not index
The most prominent questions to last week’s article, “How I got into FAANG”, revolved around the Object Design interview. Namely, “what it is?”, “how to prepare for it?” as well as what the OOP interview at Amazon actually looks like.

Object Oriented Design

Object Oriented Programming is a programming paradigm based on the concept of objects, which can contain properties and functionality.
When designing the OOP way, we could define a class Human. With attributes such as height and hairColor and methods such as eat() and sleep().
Our desire could now be to extend the functionality of the Human Object by the method code(). But since we don't want every Human to be able to code(), we'd sub-class Human, to inherit all attributes and methods.
We then have the ability to add further functions or attributes, or override existing ones.
class SoftwareEngineer: Human {}
When designing objects, make sure to think about what kind of attributes should be exposed and what kind of parent properties can potentially be extracted into a super-class.
Let’s look at another example.
class Human {
  // The eat function returns an indicator,
  // whether or not the food has been eaten.
  // Our human has free choice.
  func eat(_ food: Food) -> Bool {
    // But they're a simple person,
    // that eats everything that's edible.
    return food.isEdible
  }
}

// Now we want to define a sub-class of Human
// to give our objects a more detailed definition.
class Vegan: Human {
  // We're overriding eat to change the output.
  override func eat(_ food: Food) -> Bool {
    // Our Vegan only eats edible food that's vegan.
    return food.isEdible && food.isVegan
  }
}

The Interview

It’s fair to say that the process is pretty much almost always the same. As interviewee, you’ll be presented with some kind of a real world scenario like “Design a Parking Lot”, and then you’ll be asked to create a technical design for that.
In this article, we’re going to design a “Puppy Hotel”.
The hotel has a clearly defined amount of hotel rooms for small dogs, rooms for medium sized dogs, and the same for large dogs.
In the interview, what is described above is literally all we’ll get as a scenario description. It is now on us to ask the right questions to the interviewer. These would include questions to clarify what functionality the design is expected to full-fill.
Never ever make assumptions. It’s part of the interview to see how you handle vague questions. Are you asking clarifying questions, or are you jumping straight into code, potentially wasting your interview coding into the wrong direction?
We’d ask the interviewer what ability the hotel needs to have and we learn that we need to be able to “check in” and to “check out”.
Once I know what I need to develop, I’m starting out with some code snippets as a conversation starter with the interviewer. While typing the code I’m explaining what I’m doing.
// I'm creating a hotel class
class Hotel {

  // And I'm giving it the following defintions for some functions
  // One function to check a dog in that returns a roomID
  func checkIn(_ dog: Dog) -> String
  // And one function that checks the dog out.
  func checkOut(_ dog: Dog) -> Dog?
}
Next I’d design a quick type for the Dog.
// Since we're iOS engineers,
// we might want to use a 
// light weight type like a struct
struct Dog {
  // A dog object needs an id for identification
  let uid: String 
  // And important for out hotel, we need to know the size.
  // We could use an integer, but that's not very easy to read.
  // A String is a bad idea because mistakes can easily occur.
  // That's why we want to use a dedicated enum.
  // Tell your interviewer, that type-safety is important to you.
  // Really hard to disagree.
  let size: DogSize
}

enum DogSize {
  case small, medium, large
}
Now that we have a type definition for the sizes of our dogs and the Dog construct itself, we need to add the data structures to the Hotel. Since we want to prove to the interviewer that we "Think Big", we will have a custom initializer passing the size definition for each room size. We can reuse this class for every hotel we might own at some point.
// This is not a new Hotel class.
// It's the same class as above. 
// I'm just not going to add every line of code to every snippet.
// This way it's easier for you to see,
// what lines of code I'm talking about.
class Hotel {

  // We have 3 individual integers,
  // that reflect the available rooms in total
  let smallRoomsCount: Int
  let mediumRoomsCount: Int
  let largeRoomsCount: Int
  
  // And we have 3 dictionaries that reference the rooms.
  // 3 dictionaries, one each per size.
  var smallRooms = [String: Dog]()
  var mediumRooms = [String: Dog]()
  var largeRooms = [String: Dog]()
  
  // The custom intializer
  init(small: Int, medium: Int, large: Int) {
  	smallRoomsCount = small
        mediumRoomsCount = medium
        largeRoomsCount = large
  }
  ...
}
We’re using dictionaries as the data structure of choice, because every operation has a perfect constant Big O operation cost for each time and space.
During the interview, it’s important to verbally express your thinking. The interviewer wants to hear that you’re thinking about making a class or function generic. That means you don't hard code variables, like the amount of available rooms. It shows seniority, and that you're thinking about the bigger picture.
“Thinking small is a self-fulfilling prophecy. Leaders create and communicate a bold direction that inspires results. They think differently and look around corners for ways to serve customers.”- Amazon Leadership Principle: Think Big.
Next we’ll add the function declarations for the stubs of before.
// Again, same class as before.
class Hotel {

  // We now have the returned `roomID` optional.
  // It's possible, that there are no rooms available anymore,
  // so we need to make nil a valid option.
  func checkIn(_ dog: Dog) -> String? {
  
    // We want to return a roomID so each user 
    // is able to retrieve their dog using the ID.
  	var roomID: String?
    
    // We check for the dog size and we check if 
    // the desired room size is available
    if dog.size == .small, smallRooms.keys.count < smallRoomsCount {

      // Only then do we generate a PIN.
      // To safe memory. Mention that to your interviewer.
      // That's part of "scalablity".
      roomID = UUID()
      // We use the ID to assign a room for the dog.
      // Of course there are other ways to do this.
      // But this is an easy way and it makes sense.
      // No reason to overcomplicate it in an interview.
      smallRooms[roomID] = dog
    }

    // Same for medium as we did for small
    if dog.size == .medium, mediumRooms.keys.count < mediumRoomsCount {
      roomID = UUID()
      mediumRooms[roomID] = dog
    }

    // Same for large as we did for small and medium
    if dog.size == .large, largeRooms.keys.count < largeRoomsCount {
      roomID = UUID()
      largeRooms[roomID] = dog
    }

    // Lastly we return the ID that can be nil
    // in case of no availability.
    return roomID 
  }
  
  // For the checkOut we've created a more swifty syntax.
  // That shows that we care about writing well readable code.
  // We now require the dogID and the roomID to check out. 
  // That makes more sense.
  // If the IDs are invalid, then the return is nil.
  func checkOut(dogWithID dogID: String, outOfRoom roomID: String) -> Dog? {
  
    // To not overcomplicate it, we'll just check if 
    // there's a dog in the room, 
    // and then if the dogIDs are matching
    //
    // It's possible, that your interviewer asks you
    // about concerns regarding efficiency of this approach.
    // Let them know, that the Big O cost is constant 
    // and very efficient, even if checked in 3 dictionaries.
    if let dog = smallRooms[roomID], dog.uid == dogID {
      // If so, it's safe to return the dog
      return dog
    }

    // Same then for medium rooms.
    if let dog = mediumRooms[roomID], dog.uid == dogID {
      return dog
    }

    // And for large rooms.
    if let dog = largeRooms[roomID], dog.uid == dogID {
      return dog
    }

    // If there's no matches in the IDs, 
    // then we're not gonna return any pups.
    return nil
  }
}

The Follow Up Questions

With above basic construct in place and hopefully some time left in the interview, we’re expecting some follow up questions from the interviewer. The purpose of these questions is to see how well you understand writing scalable code.
Are you locking yourself into a corner, or are you able to identify opportunities for scalability already? How well are you communicating your thinking and planning process?
I intentionally left some room for improvement in the code snippets above. Right now in our design, if all rooms for small dogs are taken, the customer is sent away. To improve the user experience, we want to add the ability to have small dogs occupy medium sized rooms as well.
class Hotel {

  // MARK: - Public
   
  // Another major improvement in our design,
  // is to clearly define the desired access level of a function.
  // We mark this function as public, as we want 
  // consumers to be able to access this function.
  public func checkIn(_ dog: Dog) -> String? {
	
    // Declare a nil roomID
    var roomID: String?
		
    // In the new design we're going to 
    // try to get a roomID from a private method
    // This function is not async! 
    // This function is just written with a closure,
    // because we're returns a reference.
    // That's the inout equivalent of returns.
    getRooms(for: dog) { rooms in
      // If we have rooms, then we want to create an ID
      roomID = UUID().uuidString
      // Assign the dog to that room
      rooms[roomID!] = dog
    }

    // And lastly return the ID.
    return roomID
  }

  // MARK: - Private 

  // To define a clear API for consumers of our object, 
  // we're going to set the access level of this function to private.
  // No one outside of this class needs access to it.
  // The function executes syncronous and utilizes a closure 
  // to return the reference of the available sizes dictionary.
  //
  // It's possible to optimize or further refactor this function.
  // But you need to think small when it comes to those kind of details.
  // You typically got less than 20 mins for implementation.
  private func getRooms(for dog: Dog, result: (inout [String: Dog]) -> Void) {
	
    // For large dogs we can only return large rooms.
    // They won't fit into the smaller rooms.
    // #PETA.
    if dog.size == .large, largeRooms.keys.count < largeRoomsCount {
      // If we have availability, 
      // complete with the reference to the rooms dictionary.
      result(&largeRooms)
    }
		
    // For medium sized dogs...
    if dog.size == .medium {
      // We can use medium sized rooms... 
      if mediumRooms.keys.count < mediumRoomsCount {
        result(&mediumRooms)
      }
      // As well as large rooms.
      if largeRooms.keys.count < largeRoomsCount {
         result(&largeRooms)
      }
    }
		
    // For small dogs...
    if dog.size == .small {
      // Small..
      if smallRooms.keys.count < smallRoomsCount {
        result(&smallRooms)
      }
      // Medium..
      if mediumRooms.keys.count < mediumRoomsCount {
        result(&mediumRooms)
      }
      // And large...
      if largeRooms.keys.count < largeRoomsCount {
        result(&largeRooms)
      }
    }
  }
}
The hotel is now able to utilize room sizes based on demand, and we have optimized the code by setting the access levels of our functions.
The API clearly states that checkIn is a function available on any Hotel Object, while getRooms is a private function only available to the Hotel object.
What methods and attributes are public and what are private depends on the circumstances of the project, but a good rule is the "need-to-know" basis. Only expose functions and attributes the consumer NEEDS to know about.
class Hotel {
  private let smallRoomsCount: Int
  private let mediumRoomsCount: Int
  private let largeRoomsCount: Int
  
  private var smallRooms = [String: Dog]()
  private var mediumRooms = [String: Dog]()
  private var largeRooms = [String: Dog]()
  
  // MARK: - Public 
  
  public func checkIn(_ dog: Dog) -> String? {
}

Interview Preparation

There are many ways to prepare for Object Design interviews. If you’re just reading this because you’re curios, but you’re not forced to study up on this topic, then I’d suggest for you to take it easy.
Expertise comes through practice. The more often you design objects, and the more feedback you can gather, the more you’ll learn and the better your designs will become. A mentor or a study group is always a good idea for such trainings.
If you’re about to interview and you’d like some last minute ideas and influences, then YouTube provides a flood of content. I’d look for something short with a high view count. That typically gets to the point fairly fast.
Then try to design a few objects.
  • Design a Parking Lot
  • Design a Package Locker
  • Design an Airport
Try to predict some follow up questions an interviewer might ask.
  • How could you optimize?
  • How could you extend functionality?
  • Drawbacks?
  • Potential time/space improvements?
Ask friends, colleagues, or strangers on Twitter. There is a whole community of friendly students and trainers, as well as professionals available at pretty much every major social media platform.
There are tons of books available for this topic as well. Although, in my experience, the 5 minute introduction followed by “learning by doing” served me better than reading a book for a week.

Conclusion

In the end, it all narrows down to the same thoughts and principles, all the same ideas and concepts. No matter if you design a House, a Car or a Spaceship, you'll always go with a clearly defined API–private and public attributes that define the access level and functions with the best possible efficiency and security.
This article illustrated my process when faced with an Object Design situation. I’m typing some code snippets, I’m re-thinking, and I’m adjusting my code based on the verbal discussions and potential changes in scope.
In an interview or code review scenario, I’m trying my best to “think out loud”, and I’m defining and verbally justifying custom type definitions like the DogSize enum. This shows the interviewer that my code is type safe and scalable.
I’m setting expectations and I’m documenting my code well. That way, it’s easier to refactor and adjust the code later, as well as to debug crashes or issues.
Learning Object Design is not a step by step tutorial. It’s a process and as you’ll learn from mistakes of the past, from talks, articles and suggestions, you’ll naturally get better over time.
While you evolve, so does your understanding of Object Oriented Design.

Ready to take the next big step for your business?

Join other 3200+ marketers now!

Subscribe