OOP · Interview Prep
Interview Capstone
Start with the problem. The code comes last.
What you'll learn
- Turn a one-sentence interview prompt into a working class design.
- Decide which nouns deserve a class — and say why the others don't.
- Use SOLID as five questions rather than a checklist to recite.
- Justify the pattern you used, and the one you deliberately left out.
How to use this module. Wherever you see THINK, PREDICT or SPOT THE PROBLEM, stop and answer before reading on. The answer always follows immediately — cover it with your hand if you need to. Every code sample here compiles and was run to confirm its output.
The interviewer finishes their coffee and says:
"Design a smart package locker system. Use OOP."
Then they stop talking and look at you.
Most people feel the same thing here, and it isn't "I've forgotten how to write a class." The syntax is the easy part. The hard part is that the problem has no obvious first move.
Think — You have an hour and a whiteboard. What is the very first thing you write down?
Be honest about what your instinct says.
The common instinct is to start writing classes. class Locker {... }. It feels like progress and it is the single most reliable way to lose the interview — because five minutes later you are rearranging boxes, the interviewer still doesn't know how you think, and you never asked what the system actually has to do.
There is a better first move, and it is not code.
Key idea — Do not start with code. Start with the problem.
This module is the capstone of the course. Everything from M1 to M8 gets used — but not as a checklist. It gets used the way it would be used in a real design conversation: one decision at a time, each one explained.

Twelve steps. Most candidates begin at step 11. The rest of this module walks all twelve, on one system, from the sentence above to working code.
Watch out — You already met a parcel locker in Module 3, when it taught encapsulation. That is deliberate. A capstone should not also ask you to learn a new domain — the effort here goes into the design reasoning, not into figuring out what a locker is.
9.1 Design a small system, start to finish
Step 1 — Understand the requirements
Here is the problem in full. Read it once, then read it again slowly.
Customers collect packages from a bank of lockers. A courier puts a package in a locker; the customer gets a pickup code; they enter the code and the locker opens.
The requirements:
- A package can be assigned to a locker.
- Lockers come in different sizes.
- A customer receives a pickup code.
- Entering the code opens the locker.
- The system records that the package was collected.
- The customer can be notified.
- Different notification methods should be supported.
Seven lines. Notice what is not there: no payments, no login system, no database, no cloud, no API.
That absence is the point. This is an OOP design interview, not a system design interview. Nobody is asking how you'd shard a database. They are asking what your classes look like and why.
Step 2 — Ask clarifying questions
Before any design, ask a few things. Not twenty — a few.
The filter is simple: ask only what would change your design.
| Question | Why It Changes The Design |
|---|---|
| Can one locker hold more than one package? | One-to-one or one-to-many changes what Locker stores |
| Does a small package fit a large locker? | Decides whether size is a rule or just a label |
| Can a pickup code be reused? | Decides whether the code lives on the locker or on its own |
| What happens on a wrong code? | Decides whether open() returns a bool or throws |
| Who needs to know a package arrived? | Hints at one notifier — or several listeners |
"How many lockers are there?" is a bad question here. Whether it's 20 or 20,000, the classes are identical.
INTERVIEWER "Good questions. One package per locker. A small package can go in a bigger locker if needed. Codes are single-use. A wrong code should just fail, not crash. And yes — the customer's app and the physical display both need to know when a package lands."
That last answer is the most valuable thing you'll hear. It quietly tells you more than one thing needs to react to the same event — remember it for step 9.
Step 3 — Find the important objects
Go through the requirements and underline the nouns.
Customer · Package · Locker · PickupCode · Notification · Size · System
Think — Seven nouns. How many should become classes?
Pick your answer before reading on.
REVEAL — four
This is the step where beginners lose the most points, because the instinct is to turn every noun into a class.

A noun earns a class when it has a job of its own. Ask: does this thing do anything, or does it just hold a value?
Package— knows its tracking ID and size. Keep it.Locker— holds a package, checks codes, opens. Clearly a class.Notification— sends a message, and the how differs. Keep it.PackageLockerSystem— runs the workflow. Keep it.
And the ones that don't make the cut:
PickupCode— it is a string that lives on a locker. A class wrapping one string with no behaviour adds a file and buys nothing. If codes later grow rules — expiry, retry limits — promote it then.Size— a label with three values. That is anenum.Customer— real, but outside this system's boundary. We send them a notification; we don't model them. Adding aCustomerclass we never use is scope creep.
Remember — Saying "I considered
PickupCodeas a class but it has no behaviour yet, so I'd keep it as a field onLocker" is a better answer than silently creating it. The interviewer hears you making a decision rather than following a habit.
Step 4 — Give each object a responsibility
Responsibility just means: what job does this object take care of?
The test is whether you can say it in one sentence without the word "and" joining two unrelated things.

Look at the last row. PackageLockerSystem coordinates — it decides what happens in what order, and delegates each actual job. That word matters. A coordinator that also formats SMS messages and picks lockers is the beginning of a class nobody wants to open.
Step 5 — Protect what should be protected
Here is Locker written the way it comes out first:
class Locker {
public:
bool isOpen;
bool occupied;
std::string pickupCode;
};
Spot the problem — Both lines compile. What just happened?
> Locker l{false, true, "4417"};
> l.isOpen = true;
> l.occupied = false;
>
REVEAL
The locker opened without anyone typing a code, and a package vanished from the records. No function was called, no rule was checked, because there are no rules — only three exposed values.
The fix is the encapsulation idea from Module 3: outside code should ask the locker to do something, not reach in and set its state.
class Locker {
public:
Locker(int id, Size s) : lockerId(id), size(s) {}
bool store(const Package& p, std::string code) {
if (occupied || p.getSize() > size) return false;
occupied = true;
pickupCode = code;
return true;
}
bool open(std::string code) {
if (!occupied || code != pickupCode) return false;
occupied = false;
return true;
}
bool isFree() const { return !occupied; }
private:
int lockerId;
Size size;
bool occupied = false;
std::string pickupCode;
};
store -> 1
open("0000") -> 0 wrong code, refused
open("4417") -> 1
isFree -> 1
Why it is better: the rules now live where the data lives. A wrong code cannot open the locker from anywhere in the program, because there is no path that skips the check.
Step 6 — Decide where inheritance genuinely fits
Requirement 7 says different notification methods. The version that writes itself first:
if (channel == "sms") /* send an SMS */;
else if (channel == "email") /* send an email */;
else if (channel == "app") /* push to app */;
Apply the Module 4 test out loud: "An SMS notification is a notification." True. "An email notification is a notification." Also true.
The sentence holds, so inheritance fits — and with it, the polymorphism from Module 5.
class Notification {
public:
virtual void send(const std::string& message) = 0;
virtual ~Notification() = default;
};
class SmsNotification : public Notification {
public:
void send(const std::string& m) override { std::cout << "SMS: " << m << "\n"; }
};
class EmailNotification : public Notification {
public:
void send(const std::string& m) override { std::cout << "Email: " << m << "\n"; }
};
BEGINNER NOTE = 0 is not assigning zero to anything. It marks a pure virtual function — the base class saying "this must exist, and every child has to supply it." A class with one cannot be created on its own.
The = default below it is unrelated: it asks the compiler for its standard version of that destructor.

The system holds a Notification& and calls send(). It never asks which kind it has.
Predict — Next quarter, WhatsApp notifications are added.
Which existing classes have to change?
REVEAL — none
WhatsAppNotification is a new class implementing send(). PackageLockerSystem isn't touched, and neither are the three existing notification classes.
Step 7 — Check that substitution actually holds
Inheritance compiling is not the same as inheritance being correct. Module 7's Liskov check asks one question:
If the system expects a Notification , will it work correctly with every child?
Walk them: SMS sends a text. Email sends an email. App pushes an alert. Each one genuinely delivers a message. The promise Notification makes — call send() and the customer is told — is kept by all three.
Now picture one that isn't: a DraftNotification that stores the message and sends nothing. It compiles perfectly. The system calls send(), believes the customer was notified, and the customer waits by an empty locker.
Remember — Each child should keep the basic promise the parent made. A child that quietly does nothing is worse than one that fails loudly — the failure surfaces days later, far from its cause.
Step 8 — Decide the relationships
Module 6's questions, asked about this design:
Who owns whom? The system creates its lockers and they exist only as part of it. That is composition — a filled diamond.
Who only knows whom? A locker holds a package but doesn't destroy the courier's record of it. The system uses a notification it was handed. Both are plain associations.
Who keeps a list of things it didn't make? The listeners in step 9 — the app feed and the display exist independently and sign up. Aggregation, hollow diamond.

Four classes, three kinds of link. No relationship here exists to show off a notation.
Step 9 — Use SOLID as questions, not a checklist
Don't recite five principles at the interviewer. Use them as five questions about the design you just drew.
| Ask | Here |
|---|---|
| Does one class have too many jobs? | PackageLockerSystem coordinates only — storing, checking and sending live elsewhere. |
| Will a new feature force edits to working code? | A new notification type is a new class. Nothing existing reopens. |
| Can every child stand in for its parent? | Checked in step 7 — all three notifications really notify. |
| Is any class forced to implement things it can't do? | Notification has one method. Nothing is carrying dead weight. |
| Is the main logic welded to one concrete class? | The system holds Notification& and LockerAllocationStrategy&, not SmsNotification. |
If every answer is comfortable, say so and move on. A design that needs no fixing is a fine outcome.
Step 10 — Add a pattern only where a problem asks for one
Three places in this system have a real problem that a pattern answers. One has none, and that matters just as much.
A factory, because the channel comes from the customer's saved preference and isn't known until the program is running:
Notification* createNotification(std::string channel) {
if (channel == "sms") return new SmsNotification();
if (channel == "email") return new EmailNotification();
return nullptr;
}

Observer, because of what the interviewer said in step 2 — the app feed and the display both need to know when a package lands, and more listeners are likely:
class PickupListener {
public:
virtual void onReady(int lockerId, const std::string& code) = 0;
virtual ~PickupListener() = default;
};

Strategy, because "which free locker should this package go in?" has several reasonable answers and the operations team keeps changing their mind:
class LockerAllocationStrategy {
public:
virtual Locker* choose(std::vector<Locker>& lockers, Size needed) = 0;
virtual ~LockerAllocationStrategy() = default;
};
class SmallestFit : public LockerAllocationStrategy {
public:
Locker* choose(std::vector<Locker>& lockers, Size needed) override {
Locker* best = nullptr;
for (Locker& l : lockers)
if (l.isFree() && l.getSize() >= needed)
if (!best || l.getSize() < best->getSize()) best = &l;
return best;
}
};

And one that does not belong: Singleton. It is tempting — "there's only one locker system, right?" But nothing breaks if a second one exists, and a company with lockers in forty buildings needs forty. Forcing one instance would be a bug wearing a pattern's name.
Remember — Patterns should appear because the problem needs them — not because the interviewer might want to hear the names.
Saying "I considered Singleton here and decided against it, because a second locker bank is a real scenario" scores higher than using it.
Step 11 — Draw the class map

One coordinator, two owned parts, three contracts. Six boxes that matter.
Every interface on that diagram exists because something behind it can genuinely change: the notification channel, the allocation rule, the list of listeners. None was added to look thorough.
Step 12 — Now write the code
Only now. The coordinator, holding contracts rather than concrete classes:
class PackageLockerSystem {
public:
PackageLockerSystem(LockerAllocationStrategy& a, Notification& n)
: allocator(a), notifier(n) {}
void addLocker(int id, Size s) { lockers.emplace_back(id, s); }
void subscribe(PickupListener* l) { listeners.push_back(l); }
bool acceptPackage(const Package& p, std::string code); // shown below
bool collect(int lockerId, std::string code) {
for (Locker& l : lockers)
if (l.getId() == lockerId) return l.open(code);
return false;
}
private:
std::vector<Locker> lockers; // owned
LockerAllocationStrategy& allocator; // handed in
Notification& notifier; // handed in
std::vector<PickupListener*> listeners; // signed up from outside
};
The one method worth reading closely is the workflow itself:
bool PackageLockerSystem::acceptPackage(const Package& p, std::string code) {
Locker* l = allocator.choose(lockers, p.getSize()); // strategy decides
if (!l) { std::cout << "No locker available\n"; return false; }
if (!l->store(p, code)) return false; // locker checks itself
notifier.send("Package " + p.getId() + " is ready in locker "
+ std::to_string(l->getId())); // polymorphic send
for (PickupListener* listener : listeners)
listener->onReady(l->getId(), code); // observers told
return true;
}
Five lines of real work, and the coordinator makes none of the decisions itself. It asks the strategy which locker, asks the locker to store, asks the notifier to send, and tells the listeners. That is what "coordinates" looks like in code.
BEGINNER NOTE allocator and notifier are references, so C++ requires them to be attached in the constructor's initializer list — the : allocator(a), notifier(n) before the { }. A reference must point at something the moment it is created; you cannot leave it empty and assign it later inside the braces.
Wiring it up:
SmallestFit allocator;
Notification* notifier = createNotification("sms");
PackageLockerSystem system(allocator, *notifier);
system.addLocker(1, Large);
system.addLocker(2, Small);
system.addLocker(3, Medium);
CustomerAppFeed app;
LockerDisplay display;
system.subscribe(&app);
system.subscribe(&display);
Package p("PK-77", Small);
system.acceptPackage(p, "4417");
std::cout << "wrong code: " << system.collect(2, "0000") << "\n";
std::cout << "right code: " << system.collect(2, "4417") << "\n";
SMS: Package PK-77 is ready in locker 2
App feed: locker 2, code 4417
Display: locker 2 is loaded
wrong code: 0
right code: 1
Notice locker 2. A Small package went into the Small locker, not the Large one — SmallestFit doing exactly its job. Swap in NearestFree and that single line of output changes; nothing else does.
9.2 The OOP questions interviewers actually ask
Not fifty questions. Eighteen that actually distinguish a candidate who understands from one who memorised.
Level 1 — Fundamentals
Q1. What is a class, and what is an object? Being checked: whether you can explain the basics without reciting a textbook line. Good answer: A class is the description — what data something holds and what it can do. An object is one actual thing built from that description. Locker is the class; locker 2 in the lobby is an object. Common mistake: "A class is a blueprint" and then stopping. The interviewer wants a concrete example.
Q2. Why do we use encapsulation? Being checked: whether you know it is about control, not secrecy. Good answer: So an object stays in charge of its own data. If occupied and pickupCode are public, anything can open a locker without a code. Making them private forces every change through a function that checks the rules. Common mistake: "To hide data." Hiding is the mechanism; controlled change is the point.
Q3. What is the difference between abstraction and encapsulation? Being checked: whether you can separate two ideas that both involve private. Good answer: Encapsulation asks who is allowed to change this data. Abstraction asks what does the user need to know to use this. The locker keeps pickupCode private — encapsulation. It offers open(code) instead of exposing its latch mechanism — abstraction. Common mistake: Treating them as the same thing with two names.
Q4. Inheritance or composition — what is the difference? Being checked: whether you reach for the is-a test. Good answer: Inheritance says is a kind of; composition says has a. An SmsNotification is a Notification, so inheritance. A locker has a display, so composition — a locker is not a kind of display. Common mistake: "Composition is better." That is a slogan, not a decision.
Q5. What is polymorphism? Being checked: whether you can explain it by what you observe, not by definition. Good answer: The same call doing different things depending on the object behind it. The system calls notifier.send(msg); whether an SMS or an email goes out depends on what it was handed, and the calling line never changes. Common mistake: "Many forms" and nothing else.
Level 2 — Reasoning
Q6. When would you choose composition over inheritance?
INTERVIEWER "Why would you choose composition over inheritance?"
WEAK ANSWER — "Composition is better than inheritance."
BETTER ANSWER — "I'd check the relationship first. If the object has another object rather than being a kind of it, composition fits. A locker has a display, so I'd hold one as a member. If I'd inherited, the locker would have picked up every display function as part of its own interface, which makes no sense for a locker."
WHY IT WORKS — it shows a decision being made from the relationship, not a preference being repeated.
Q7. Why should a class protect its internal state? Because otherwise every rule about that state has to be remembered by every caller. occupied being private means there is exactly one path that changes it, and that path checks the code. With it public, the rule exists only in people's heads.
Q8. What does virtual do?
class Notification { public: void send() { std::cout << "generic\n"; } };
class SmsNotification : public Notification { public: void send() { std::cout << "sms\n"; } };
SmsNotification s;
Notification* n = &s;
n->send(); // prints: generic
Without virtual, C++ picks the function from the pointer's type, so the base version runs even though the object is an SmsNotification. Add virtual to Notification::send() and the object decides instead — it prints sms. That one keyword is what makes polymorphism through a base pointer work.
Q9. Overloading or overriding?
class Locker {
public:
bool open(std::string code); // A
bool open(int staffId); // B
};
A and B are overloading — same name, different parameters, chosen at compile time from the arguments, no inheritance involved. Overriding needs a child replacing a parent's virtual function with a matching signature, chosen at run time by the object.
Q10. Association, aggregation or composition? All three mean objects are connected; the difference is lifetime. Association is just "they know each other" — a locker holds a package.
Aggregation is "I have you, but you outlive me" — the system keeps listeners it did not create. Composition is "you are part of me" — the system's lockers die with it.
Level 3 — Design thinking
Q11. What does "program to an abstraction" mean? Write code against a contract rather than a specific class. PackageLockerSystem holds a Notification&. It never names SmsNotification, so a new channel never forces it to change.
Q12. Why is tight coupling a problem? Because a change in one place forces changes elsewhere. If the system created a SmsNotification directly, switching to email would mean editing the class that runs the locker workflow — a file with nothing to do with messaging. Tight coupling turns one small change into several risky ones.
Q13. How would you apply the Open/Closed Principle here? It is already applied. Adding a notification type, an allocation rule or a listener means writing a new class; no existing class reopens. The test is whether tomorrow's feature is an addition or an edit.
Q14. What does Dependency Inversion mean in simple terms? Important logic should depend on a contract, not on a specific provider. Here the system depends on Notification and LockerAllocationStrategy. The concrete classes depend on those same contracts. Nobody points at anybody's implementation.
Q15. When is a Factory useful? When the decision about which concrete object to build would otherwise be scattered, or when the type isn't known until run time. Here the channel comes from a saved preference, so one place decides and nothing else needs to know the concrete names.
Q16. When is Strategy useful? When one job has several interchangeable ways of being done and you want to swap them without editing the class that uses them. Locker allocation is exactly that — smallest fit, nearest free, or a priority bay.
Q17. When is Observer useful? When one thing changes and several unrelated objects need to hear about it, especially when the list will grow. A package landing has to reach the app feed and the display today, and more listeners later.
Q18. When should you not use a design pattern? When there is no problem for it to solve. I considered Singleton for the locker system and rejected it — a second locker bank is a real scenario, so forcing one instance would create a bug rather than prevent one. Adding a pattern to a design that doesn't need it makes the code longer and harder to follow, and leaves the original problem exactly where it was.
Remember — Q18 is the question that separates candidates. Anyone can name four patterns. Explaining why you didn't use one shows you are making decisions rather than reciting a catalogue.
9.3 Live design walkthrough
Same problem, now as a conversation. Watch how the thinking gets said out loud — that's the part being marked.
Round 1 — Clarify before designing
INTERVIEWER "Design a smart package locker system."
CANDIDATE — "Before I design anything, three quick questions. Can a locker hold more than one package? Can a pickup code be reused? And does anything other than the customer need to know when a package arrives?"
INTERVIEWER "One package. Single-use codes. And yes — the customer app and the lobby display both need to know."
CANDIDATE — "That last one is useful. More than one thing reacting to the same event suggests I shouldn't hard-code the reactions into the locker system."
Three questions, each one changing something. The candidate also said why the answer mattered — that sentence is worth more than the question.
Round 2 — Objects, with the ones left out named
CANDIDATE — "I'll start with the important things. Package, Locker, Notification, and a PackageLockerSystem to coordinate.
I considered PickupCode and Customer too. The code is a single string with no behaviour yet, so I'd keep it as a field on Locker — if it grows rules like expiry, I'd promote it. And the customer sits outside this system; we notify them but don't model them."
Naming the rejected classes is deliberate. It tells the interviewer these were decisions.
Round 3 — One job each
CANDIDATE — " Locker manages whether it's occupied and whether a code opens it. Package knows its ID and size. Notification defines how a message goes out.
PackageLockerSystem coordinates — it runs the workflow but does none of those jobs itself. If I catch it formatting an SMS, I've given it a second job and I should split it."
Round 4 — Relationships, with lifetimes
INTERVIEWER "How do these connect?"
CANDIDATE — "The system owns the lockers — it creates them and they die with it, so that's composition. A locker holds a package, but doesn't control whether the package record exists elsewhere, so that's a plain association. And the listeners are aggregation: they exist on their own and just sign up."
Round 5 — Polymorphism, prompted by change
INTERVIEWER "Suppose next quarter we add WhatsApp notifications."
CANDIDATE — "That's why I'd make Notification an abstraction with a pure virtual send(). The system holds a Notification& and calls send() without knowing the type. WhatsApp becomes one new class — the workflow doesn't change at all.
I'd check substitution while I'm there: every notification must really deliver a message. A child that silently does nothing would break every caller's assumption."
Round 6 — Factory, with a reason
INTERVIEWER "How does the right notification get created?"
CANDIDATE — "The channel comes from the customer's saved preference, so it's a run-time decision. I'd put that in a small factory function. Otherwise the if channel ==... block spreads into every place that sends anything, and a new channel means hunting all of them down.
To be precise, that's a simple factory rather than the Factory Method pattern. One decision point is all this design needs."
Round 7 — Observer, cashing in Round 1
INTERVIEWER "Both the app and the display need to know when a package is ready. How?"
CANDIDATE — "I'd define a PickupListener contract and let the system keep a list of them. When a package is stored, it walks the list and tells everyone.
The alternative is the system calling the app and the display directly, but then it depends on both, and the third listener means editing it again. This way a new listener just subscribes."
Round 8 — Strategy, only because it was asked for
INTERVIEWER "How do you pick which locker to use?"
CANDIDATE — "Is there one rule, or does it change? If operations want smallest-fit today and nearest-free next month, I'd make it a LockerAllocationStrategy and pass one in. If it's genuinely always smallest-fit, I'd write one method and skip the abstraction — no point building a swap mechanism for something that never swaps."
CHOOSE The interviewer answers: "It changes by location. Some sites want smallest-fit, busy sites want nearest-free."
Strategy, or one method with an if?
REVEAL — Strategy
Different sites need different rules at the same time, so it isn't one rule with an occasional change. That's an interchangeable algorithm chosen per site, which is exactly what Strategy is for. Had the answer been "it's always smallest-fit", one method would have been the better design.
Round 9 — Review your own design out loud
CANDIDATE — "Let me check it before I call it done.
Each class has one job. Locker state is private with rules on every change. Inheritance appears once, where the is-a sentence is genuinely true. Polymorphism is doing real work in three places. Nothing is tied to a concrete implementation — the system holds contracts.
One thing I deliberately left out: Singleton. There's only one locker bank in this building, but a second building needs its own, so forcing one instance would create a bug rather than prevent one."

How to talk while you design
The sentences below are worth having ready. Not as scripts — as the shape of a reasoned answer.
- "I'll start by clarifying a few requirements."
- "I see two responsibilities here, so I'd separate them."
- "I don't think inheritance fits — this is a has-a relationship."
- "I'd use polymorphism here, because these share an operation but implement it differently."
- "I'd introduce a factory, because object creation is becoming its own concern."
- "I considered a pattern here and decided against it, because…"
- "I'm not certain about this trade-off — here's how I'd decide."
That last one matters more than people expect. Saying you're unsure, then explaining how you'd resolve it, reads as confidence. Bluffing reads as bluffing.
Remember — Good design ≠ most classes. Good design ≠ most patterns.
Good design = clear responsibilities + sensible relationships + decisions you can explain.
Final OOP interview checklist
Use this in the last two minutes before you say "I think that's my design."
REQUIREMENTS - Did I understand the problem, and can I restate it in my own words? - Did I ask the few questions that would change the design?
OBJECTS - What are the important things here? - Does each class have one job I can say in a sentence? - Did I name the nouns I chose not to turn into classes?
RELATIONSHIPS - Who knows whom, who has whom, who owns whom? - For every inheritance: is the is-a sentence true when said out loud?
OOP TOOLS - Is state private, with rules on every change? - Does each class expose what callers need and hide the rest? - Is inheritance actually needed, or would composition fit better? - Where does polymorphism do real work?
SOLID - Is one class quietly collecting jobs? - Will tomorrow's feature be an addition or an edit? - Can every child stand in for its parent? - Is any class implementing things it cannot do? - Is the important logic tied to one concrete provider?
PATTERNS - Does each pattern answer a problem that actually exists here? - Which pattern did I consider and reject, and why?
COMMUNICATION - Can I explain every class on the board? - Can I explain why each relationship is the kind it is? - Can I name one trade-off I'd revisit with more information?
Final course recap

Nine modules, and they collapse into one habit: look at a problem, find the objects, give them jobs, connect them sensibly, and be able to say why.
The design sequence, one last time:
- Understand the problem.
- Ask the questions that change your answer.
- Find the important objects.
- Give each one a clear responsibility.
- Decide how they relate.
- Protect internal state.
- Use abstraction where it earns its place.
- Use inheritance only when is-a is genuinely true.
- Use polymorphism when one call should behave differently.
- Check for strain with SOLID.
- Add a pattern only when a real problem asks for one.
- Explain your decisions.
A last word
You do not need to have memorised every definition in this course to do well in an interview. Plenty of people can define encapsulation and still freeze at a blank whiteboard.
What you need is the thing this module practised: take a problem you have not seen before, break it into objects, give those objects clear jobs, connect them in a way that makes sense, and explain why you made each choice.
You will still meet problems that are harder than this one. You will still pick a design and later wish you had picked another — everyone does, including the person interviewing you. That is what the explanation is for: a design you can reason about is a design you can change.
Start with the problem. The code comes last.