Skip to content

Module 2 — Challenge Problems

These are open-ended on purpose. There is no single right answer, and several of them have no code at all — the deliverable is a decision, defended.

They are also where the module's real skill is assessed: deciding what a type is responsible for, and what it is not. Exercises measure whether you can write a class. These measure whether you can design one.

Do them on paper first. Every one of them is faster to argue with yourself about in prose than in a text editor, and the prose is what you will be asked for in a design review.


X1 — Course Registration, end to end [L5 Design]

A university department needs a registration system. Students register for course sections. A section has a capacity. A student may hold at most five registrations. Some sections have a waitlist, and a place freed by a withdrawal goes to the first person on it.

Deliverable — a design, not a program:

  1. The types, each with a one-sentence responsibility containing no "and".
  2. For each rule, the type that owns it: capacity, the five-registration limit, the waitlist order, and "you may not register twice for the same section".
  3. The messages between the types, written as a numbered interaction: "student asks section to enrol them; section refuses; student asks section to waitlist them". Show the ordinary path and the refusal path separately.
  4. The invariants, one sentence each — the statements that must be true of every valid object at every observable moment.
  5. What you deliberately did not model, with a reason. At least one thing.

Success looks like. Four or five types, no rule owned by two of them, and a refusal path that leaves both objects unchanged. The interaction script should read like a conversation, because that is what it is.

How you will be judged. On (2) and (5). A design where the section's capacity lives in the student's code has not decided anything, and a design that models everything — rooms, timetables, invoices — has avoided deciding anything.

Where this leads. The ownership question is Module 9; the waitlist order is Module 14's container choice; making the policy replaceable is Module 15.


X2 — one rule, two owners [L4 Analyze]

Take examples/module-02/05-bank-account/main.cpp and add an overdraft facility: an account may go up to Rs 5000 below zero, but no further.

Analysis, in writing, before any code:

  1. Where is the invariant now? State it in one sentence, and in one sentence for the original version too.
  2. Is the overdraft limit a property of the account or of the bank? Argue both sides in two sentences each, then decide. What would change if a second kind of account had a different limit?
  3. withdraw now has two refusal reasons — "would exceed the overdraft limit" and "the amount is not positive". What should a caller do with that information? Options: return an int code, return a bool plus a query, throw (Module 13), or say nothing and refuse. Choose one, and say what a caller loses with your choice.

Then implement your decision, keeping the invariant enforced in exactly one place.

Success looks like. The revised invariant sentence; a defended ownership decision; a refusal design with its cost named; and an implementation where there is no path that leaves the balance below the limit.

Full credit includes naming one new way a caller can now break the invariant that did not exist before the overdraft — because adding a rule nearly always adds an edge.


X3 — the design review [L5 Design]

A colleague submits the following public interface and asks you to approve it. The class records an employee.

class EmployeeRecord {
public:
    EmployeeRecord(std::string name, int staffNumber);

    std::string getName() const;
    void setName(const std::string& name);

    int getStaffNumber() const;
    void setStaffNumber(int number);

    long long getSalaryPaisa() const;
    void setSalaryPaisa(long long paisa);

    std::string getDepartment() const;
    void setDepartment(const std::string& department);

    bool getIsOnLeave() const;
    void setIsOnLeave(bool onLeave);

    void print() const;
};

Deliverable — a written review:

  1. For each of the five set… methods: keep, delete, or replace with an operation. For every deletion, say what a caller does instead. For every replacement, name the operation and the rule it enforces.
  2. Rewrite the interface, with a one-line promise beside each remaining public member.
  3. State the invariant your version enforces that the original does not.
  4. Write the three sentences you would say to your colleague, in the order you would say them, so that the review is useful rather than merely correct.

Success looks like. Fewer public members than you started with, each with a reason; at least one setter replaced by a verb (applyRaise, approveLeave, transferTo); and a review that begins with what the original got right.

How you will be judged. Point 4 matters more than it looks. "Justify every public member" is a skill; "tell somebody their interface is a public representation wearing a class" without ending the conversation is the professional version of it.


X4 — is it the class's business? [L4 Analyze]

Five proposed changes. For each, decide whether the new rule belongs inside the class, in the caller, or in a third type — and justify it in two sentences.

  1. A Book's title must be at most 120 characters, because the label printer cannot handle more.
  2. The library shows at most ten search results at a time.
  3. A Member's name must be unique across the library.
  4. A Loan's due date is 14 days after it is created.
  5. The library closes the desk at 17:00, so nothing is lent after 16:45.

Success looks like. Five decisions with reasons, and at least one where you argue that the rule should not be in the class that a naive reading suggests.

The theme to look for. A rule belongs where the data it constrains lives — and "the label printer" is not the same object as "the book". Getting this wrong is how a class ends up knowing about its own presentation, which is the leak Challenge X2 in Exercises asks you to hunt.


Stretch (optional)

  • S1. Take your X3 interface and compile a small program that uses it. Then delete every method call that compiles but that a reviewer would query. What is left? That residue is your interface's real minimum, found empirically.
  • S2. Write the same class twice — once with a struct and public members, once as a class with operations — and write four sentences on when each would be the right choice. There is a correct answer for a pair as simple as Point, and being able to articulate it is what separates a habit from a decision.

Evaluating your own answer

Use this rubric on every design above, and on your own lab work. It is the same rubric the course's later labs and the capstone report are marked with, which is why it is worth internalising now.

Criterion A strong answer A weak answer
Responsibilities Every type's job is one sentence without "and"; you can name the boundary between two types "It holds the data and the methods for it" — true of everything, so it decides nothing
Rule ownership Each rule names exactly one owner type, and you can say what breaks if it moves A rule "checked everywhere it is needed" — which means it is enforced nowhere
Invariants Stated as sentences that must always hold, and every public operation preserves them Described as validation performed at certain call sites
Interface Every public member has a reason; the deletions are defended as strongly as the additions No public member is questioned; accessors mirror the fields one-for-one
Refusal semantics A refused operation leaves every object unchanged, and the caller can tell which failure occurred false for five different reasons, or an object silently "corrected"
Single source of truth Counts and flags are derived from the data that implies them A count stored next to the collection it counts, with no rule connecting them
Exclusions At least one thing deliberately not modelled, with a reason Everything in the problem description has become a type

Diagnostic. If your answers score well on Responsibilities and Invariants but poorly on Single source of truth, your design is sound and your implementation needs one pass of simplification. If Rule ownership is weak, the problem is conceptual, not coding — re-read §9 of Concepts before attempting any more design work. That distinction is what makes this rubric useful rather than discouraging.


Next: Self-assessment — nine questions, with answers and a re-test schedule. · All modules