Design an extensible poker-hand evaluator
Company: Rippling
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Technical Screen
Design and implement an object-oriented solution for a simplified two-player poker-like game called **Camel Cards**. The interviewer is explicitly evaluating your **OOP design** alongside correctness, so structure your code for clarity and extension, not just to pass the examples.
### Game Rules
- There are **two players**. Each player has a **hand of exactly 4 cards**.
- Card labels are one of `9, 8, 7, 6, 5, 4, 3, 2, 1`, where **9 is the highest** and **1 is the lowest**.
- A hand is classified into **exactly one** hand type. From **strongest to weakest**:
1. **Four of a kind** — all four cards the same (e.g., `9999`)
2. **Two pair** — two cards the same + two other cards the same (e.g., `2332`)
3. **Three of a kind** — three cards the same + one different card (e.g., `9998`)
4. **One pair** — two cards the same + two distinct others (e.g., `5233`)
5. **High card** — all four cards distinct (e.g., `2345`)
> This ordering is **intentional and non-standard**: e.g., **any two-pair beats any three-of-a-kind**. Encode the order the prompt gives, not real-poker convention.
### Tie-Breaking (Ordering Rules)
1. Hands are primarily ordered by **hand type** — the stronger type wins.
2. If both hands have the **same type**, compare cards **from most recently dealt to first dealt** (i.e., **right-to-left**), **without sorting the hand**:
- Compare the rightmost card values; the higher value wins.
- If equal, move one position left and repeat.
- If all four positions are equal, the result is a **TIE**.
### Task
Implement:
```
evaluate(hand1, hand2) -> "HAND_1" | "HAND_2" | "TIE"
```
Each hand may be given as a 4-character string like `"2332"` or as an array/list of 4 card labels. Return `"HAND_1"` if `hand1` is stronger, `"HAND_2"` if `hand2` is stronger, or `"TIE"`.
### Design Requirement
Many more hand types will be introduced in the future. Design your solution so that **adding a new hand type requires no edits to the evaluator, comparator, or classification logic** — only adding new code.
```hint Where to start
Separate the two concerns the prompt deliberately tangles: (1) *which type* a hand is — an order-independent property of the multiset of card values — and (2) *who wins a tie within a type* — an order-**dependent** comparison of the raw dealt cards. Don't let one leak into the other.
```
```hint Detecting a type cheaply
Notice every count-based type depends only on *how many of each value appear*, not on which values or their positions — `9998` and `2221` are "the same kind of hand." What compact, value-agnostic summary of a 4-card hand captures exactly that and lets each type's check stay a one-liner?
```
```hint The extension point
The design requirement ("add a type without editing the evaluator") is the Open/Closed Principle in disguise. What would each hand type have to *own* — about itself — so that the evaluator never names any type by hand? Think about what the classifier needs from a type to both pick it and rank it, and how you'd register a new one without renumbering the others.
```
```hint The trap that passes the obvious tests
Re-read the tie-break: it walks the cards as dealt, right-to-left. It's tempting to reuse the same normalized representation you used for *typing* here too — but ask yourself whether two same-type hands that share the same multiset of values can still resolve to different winners. If they can, what does that forbid you from doing to the cards before the tie-break?
```
### Constraints & Assumptions
- Each hand is exactly **4 cards**; labels are single characters `1`–`9` (no `0`, no `T/J/Q/K/A` in this version).
- Inputs may be a string (`"5233"`) or a list (`['5','2','3','3']` or `[5,2,3,3]`) — your API should accept either.
- You may assume valid input unless you choose to validate; state whichever you pick.
- Optimize for **readability and extensibility** over micro-performance — `n = 4` cards, so asymptotics are not the point.
- This is a ~40-minute coding round: a clean, working `evaluate` plus a clearly extensible class structure is the bar, not a full game engine.
### Clarifying Questions to Ask
- Is the input guaranteed valid (exactly 4 cards, labels `1`–`9`), or should I validate and reject malformed hands?
- Can a hand be passed as both a string and a list, and should both be supported by the same entry point?
- When two hands have the same type, am I correct that the tie-break ignores pair rank and kickers entirely and only walks the raw dealt positions?
- Will the *strength ordering* of existing types ever change, or do only new types get added between them?
- Are card labels guaranteed to stay single digits `1`–`9`, or should the design anticipate `T/J/Q/K/A`?
### What a Strong Answer Covers
- A clean separation of responsibilities: parsing/validation, type classification, and ordering/comparison live in distinct collaborators.
- A type system that satisfies **Open/Closed** — a new hand type slots in without touching the evaluator, comparator, or classifier.
- Correct handling of the **non-standard** type order (two-pair above three-of-a-kind).
- Correct **right-to-left, no-sort** tie-break, and an articulated reason why sorting is wrong (permutation hands).
- Acceptance of both string and list input through a single normalization point.
- Clear naming, fail-fast validation (or a stated assumption), and a few targeted tests including the sort-vs-no-sort trap.
- A statement of how a future type that needs more than frequency counts (e.g., a straight) would fit without disturbing existing code.
### Follow-up Questions
- A new type **Straight** (four consecutive labels, in any dealt order) is added between two existing types. Walk through exactly what code you add and confirm nothing existing changes.
- How would your design change if the label alphabet expanded to `2`–`9, T, J, Q, K, A`, and how do you keep that change to one place?
- If two *different* types were ever assigned the same strength, what would break, and how does your design prevent it?
- How would you unit-test this so adding a type later doesn't silently regress the comparison rules? What's the smallest set of cases that pins down the tie-break behavior?
Quick Answer: This question evaluates object-oriented design and software architecture skills, specifically the ability to define modular classification and comparison responsibilities for a game-hand evaluator as part of Software Engineering Fundamentals.