The If-Else Chain Hiding Inside a Single Bird Class
Why one Bird class trying to hold every kind of flying turns into an if-else chain, and the five real problems that chain causes in production.
"Design a bird" is a real interview question at Amazon. It sounds like a joke the first time you hear it. It isn't.
Picture a zoo management system. It needs to store every kind of bird it holds: sparrows, crows, pigeons, peacocks, penguins. It needs their details, and it needs their behavior. How do you structure the classes for that?
The One-Class Answer Everyone Starts With
The first instinct almost everyone has: one Bird class, done. Give it a few attributes, give it a few methods, ship it.
class Bird {
public:
double weight;
std::string color;
std::string type;
std::string name;
bool canFly;
void fly() { /* ... */ }
void eat() { /* ... */ }
void walk() { /* ... */ }
};You'd use it like this:
Bird b1;
b1.color = "blue";
b1.type = "sparrow";
b1.fly();This compiles. It runs. It is not what the interview is testing.
Every Bird Doesn't Fly the Same Way
Look closer at fly(). Not every bird flies the same way. A sparrow's flight has nothing in common with a penguin's. So what actually goes inside that method?
void fly() {
if (type == "sparrow") {
// sparrow-specific flying logic
} else if (type == "crow") {
// crow-specific flying logic
} else if (type == "pigeon") {
// pigeon-specific flying logic
}
// ...and one more branch for every bird that gets added
}This is the actual shape of the "one Bird class" solution. One method, branching on a string, forever.
Five Problems Hiding in One Method
Nobody needs a formal definition to feel that this is bad. Here's what's actually wrong with it, spelled out.
What "Responsibility" Actually Means
All five of these problems trace back to one root cause, and that root cause has a name: the Single Responsibility Principle, the S in SOLID. It states it plainly: every code unit, a class, a method, a package, should have exactly one responsibility.
"Responsibility" has a specific meaning here, and it isn't obvious on first read.
Responsibility means reason to change.
Look at what Bird is actually responsible for. It's not just "holding bird data." It's responsible for how a sparrow flies, how a crow flies, how a pigeon flies, and every bird added after that. Adding a new bird, or changing how an existing one flies, both mean opening the exact same class. That's not one responsibility. That's one responsibility per bird type, all crammed into a single class.
LLD Has No Single Right Answer
One thing worth saying before going further: low-level design questions rarely have one correct answer. Whether a class has "one responsibility" or three is a judgment call, not a formula, and reasonable engineers can define that boundary differently depending on context.
What actually matters in an interview isn't landing on the one true design. It's naming the trade-off you're making and explaining why you're making it. Every design decision trades something for something else, and being able to say what you gave up and what you got in return is the actual skill being tested.
There's a practical question that makes "responsibility" easy to spot in your own code, instead of just a definition to memorize: why would I need to open this class again? Ask that about Bird, and the answer keeps landing on the same class, no matter which bird changes. The next post turns that question into concrete signs you can check for in any codebase, not just this one.
Keep reading