The OOP Pillars Exam

A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
medium
Seniority
L3
Asked in
Horizon Media

Problem

You're building the animal model for a small zoo, where a shared base animal defines that every species can speak and each species fills in its own sound. Given a list of [animal_type, name] pairs, build the matching animal for each and return the line it speaks, in the order the pairs arrive.

Four principles, one class hierarchy - show you know all of them.

Example

Input:

animals = [["dog","Rex"],["cat","Whiskers"],["dog","Buddy"]]

Output:

["Rex says: Woof!","Whiskers says: Meow!","Buddy says: Woof!"]

Worked solution and explanation

What this problem is really about

This is a polymorphic-dispatch drill wearing a zoo costume. The skill on trial: can you put each species' behavior INSIDE the type, so a base Animal declares a speak() contract and Dog and Cat each fill it in, and the driver just builds the right instance and asks it to speak? Anyone can write a dict from type string to sound. What actually separates candidates is refusing to branch on the type string in the driver: the pair NAMES a class, you construct it, and the instance answers. Get that wrong and either you call speak() on a raw list (which has no such method, so AttributeError), or your driver becomes the exact if animal_type == 'dog' ladder the hierarchy exists to delete.


Build it up

Step 1: Declare the base contract

Write Animal as a plain base class that stores the name and declares speak(). Have that base speak() raise NotImplementedError: it exists to name the contract every species must honor, not to do the work itself.

Step 2: Override in the subclasses

Create Dog and Cat that inherit from Animal and give their own speak() returning '{name} says: Woof!' and '{name} says: Meow!'. Each subclass owns exactly one behavior, so adding a species later is a new class, never an edit to an existing one.

Step 3: Encapsulate the name

Keep the name behind self._name in __init__. The underscore is the Python convention for 'internal state, read it through the object, not around it', so speak() reaches its own name without the caller poking at attributes.

Step 4: Dispatch through the instance

Map each type string to its class with a small registry dict, then for every pair look up the class, construct it with the name, and call speak() on the instance. The driver never inspects which species it built; the instance's own speak() runs. That is the polymorphism the problem is grading.


The solution

Base contract, concrete subclasses, polymorphic dispatch
class Animal:
    def __init__(self, name):
        self._name = name

    def speak(self):
        raise NotImplementedError

class Dog(Animal):
    def speak(self):
        return f'{self._name} says: Woof!'

class Cat(Animal):
    def speak(self):
        return f'{self._name} says: Meow!'

def animal_sounds(animals):
    registry = {'dog': Dog, 'cat': Cat}
    result = []
    for animal_type, name in animals:
        cls = registry[animal_type]
        instance = cls(name)
        result.append(instance.speak())
    return result

Common follow-up questions