Close

Interview Question: A two-player card game

I’ve been asking interviewees, over at interviewing.io, to write a simulation of a two-player card game. It’s an engaging question, revealing a lot about their abilities. It doesn’t involve any trickery nor random algorithm knowledge. It’s suitable for programmers of any skill level and works in all common languages.

I’d like to share the question and some insights I’ve had.

Two-Player Card Game

I introduce the question with a preamble about being focused on the design and structure of the code. The program doesn’t need to run, but I do want to see an entry point.

I speak the following description of the game.

  • this is a two-player card game
  • the game starts with a deck of cards
  • the cards are dealt out to both players
  • on each turn:
    • both players turn over their top-most card
    • the player with the higher-valued card takes the cards and puts them in their scoring pile (scoring 1 point per card)
  • the turns continue until the players have no cards left
  • the player with the highest score wins

It’s considered a simulation because the players don’t have any choices to make. We need not worry about input.

There are unanswered questions in this description, or perhaps some ambiguities. Some of them weren’t intended, but it’s worked out well. Candidates must identify and resolve issues in the requirements.

Based on this description I’m expected the person to write code that simulates the game: writes out who wins from one round of play.

Design

The interviewee asking questions is good, but not necessarily revealing. I expect some feedback that they’ve at least understood the problem. Beyond that I’m flexible, having seen several styles of solving the problem. The only definite pattern I’ve seen is a negative one with over-design: trying to plan for everything and writing extensive notes. If people are taking too long here, I prompt them to write code.

It’s this communication aspect that prompted me to include a chapter on talking and listening in my course.

The complexity of this problem doesn’t require much pre-work. Other than coming up with a couple of classes, like Player and Game, possibly Card, you can start writing code. The best approach, used by many well-performing candidates, is outlining the structure in actual code. Code is often the best pseudo-code: just leave out details and take short-cuts as you’re planning.

The Cards

Several candidates work from the bottom up: taking a deep dive into what defines a card, or creating a Deck class. I ask them why they are building these classes, as a hint it’s not a good approach. I encourage them to think at a higher level, about what they might need.

Especially in a time-limited scenario, it’s best to think top down and ignore details until you need them. Don’t design all aspects of a theoretical Deck class as you may not use them. It’s okay to sketch out classes, placing some variables, but the details should be sparse.

I avoid giving too much direction at this point; being able to structure a problem in code is an essential skill of coding. I reserve my comments to when I feel it’s going wrong and the approach will not let them finish the task. If they feel confident and making progress, I try not to enforce an ideal view, instead I let them go with it.

One critical question that should arise, probably during design, and at the latest when comparing cards, is what type of cards we’re dealing with. Not everyone is familiar with standard playing cards, which is fine; they ask what type of cards these are. Others assume suited cards and ask how to compare suits. In all cases I simplify the requirement to just be a set of numbered cards, from 1 to N. With this requirement it’s acceptable to not have a Card class at all, instead just using an integer. Though, there’s nothing wrong with a trivial Card type either.

Regardless of the choice of integer or class, it’s important I understand why this decision is being made. In particular, I expect the candidate to tell me why they’ve chosen one or the other. The code should also be clear. A variable like int[] array is not helpful (yes, this happened once). Name things for their logical value, such as deck.

The game progress

I mentioned the programming should be top down. After the initial design period, and some code sketching, I’ll expect them to write the main sequence. I want to see cards dealt out, the turns being taken, and the winner declared.

Starting with a rough structure then filling in details is good. My experience so far is that people who don’t start at the high-level have a hard time finishing the problem.

defn play_game = -> {
    deal_cards()

    take_turns()

    declare_winner()
}

Given the simplicity of the problem, it’s also acceptable code each of these directly in the function, provided it can be refactored into functions afterwards. Whether the interviewee initiates this refactoring, or I have prompt for it, seems to reflect on their experience level.

The importance of top-down design is echoed in processes like test-driven development and the concept of YAGNI “You aren’t gonna need it”.

Common issues

From here it’s a matter of filling in details. This question has a lot of small details, none of which are tricky. This is good for an interview.

The Player class

A common mistake is failing to identify a Player type. We end up with this pattern of coding:

vector<int> player1_cards, player2_cards;
int player1_score, player2_score;

A series of matching variable names, like player1_* and player2_*, is a good indication you should have a Player type. Most candidates come to this conclusion their own, either starting with a Player class directly, or refactoring it. Others require some prompting but usually understand quickly. A select few just didn’t comprehend and needed explicit instruction.

Prompting people, without just giving away an answer, is difficult. In this situation, I try things like; “look at those variables starting with player1 and player2. Do you see a pattern there?”, “Is there some structure you can use to abstract this?”, or “Couldn’t you group this better somehow?”.

Arguments and Member variables

There should be some Game class managing the simulation. The name isn’t that important, but it should contain the players and the functions for each phase of the game. Here I get to test the candidate’s object-oriented programming (OOP) ability.

Member variables replace the need for repeated arguments to functions. Where previouly the playes, scores, and cards are passed as function arguments, now they can be part of the instance. Some people immediately do it this way, others require prompting.

The player details are almost always best as member variables, but there is some variation on how to handle the initial deck and dealing. I‘m not strict in any sense here, as some designs make sense with fewer instance variables. I‘m looking at how they reduce redundancy, in any way possible.

Again, prompting appropriately is difficult. I try things like “Is it necessary to pass the players to all the functions?”, “Is it possible to avoid this redundancy?”, or more directly, “These feel like the players belong to the class.” Honestly, I don’t remember the specific things I’ve said, but the key is to start general, almost mysterious. How much I need to say, and how explicit it is, reflects a lot on the skills of the interviewee.

Magic numbers and details

There aren’t many magic numbers involved in the code, but I don’t enjoy seeing them. For instance, a loop that runs from 1..52 to create cards uses a magic number. There’s also a 2 that comes up when adding to the score: it’s more subtle, but relevant for the extended questions I have planned.

It may seem like a trivial detail, but I’ve noticed that better programmers are attuned to such things. They will create a constant, use an argument to a constructor (possibly with default), or just say, “Yeah, this isn’t good, I should clean it up after.”

Though time may feel limited in an interview, I strongly encourage writing clean code. It communicates clearer to the interviewer and reflects positively on your coding ability. Plus, I find that sloppy code ultimately slows candidates down.

By now, I usually have a good idea of how well the person knows the programming language. Comparison to natural language seems fair, some people seem to speak fluently, and the code flows consistently and cleanly. Others seem to stutter, forget words, translate in their brain, and the code is less eloquent.

Though people that use advanced syntax fair well, people have also completed the question using nothing but basic imperative syntax. Real struggles though aren’t acceptable: the candidate gets to pick the language, so there’s an absolute expectation of basic knowledge.

Extended Questions

If the interviewee has solved the base question and we have at least 15 minutes remaining, I’ll add the next requirement:

  • Extend the game to support more than two players

How many changes this requires depends on the initial approach they’ve taken. Prior to this question I will have requested refactoring, to ensure they have multiple functions, and usually an OOP design. If the code isn’t in a good state, this new requirement is too daunting.

The majority of the candidates make it to this question. That makes it a suitable criterion for filtering: I tend not to pass people that can’t solve this extended question. I often won’t ask this question if I estimate they won’t finish it, or sometimes I propose to extend just one function. I prefer to end the interview early than add additional stress.

More Issues

The change to N players introduces a few new problems:

  • Dealing cards can no longer be done with an if-else to choose the player. I’m kind of surprised at how many people do not know how to use modulus % to select items from an array in a loop.
  • Picking the highest card from a list is no longer an if-else. It challenges some people who know how to get the maximum value, but not the index of that card.
  • The loop to decide if the game is over is now non-trivial, especially as players may not have the same number of cards. I noticed that better programmers naturally create a helper function, or otherwise avoid complex conditions in a loop.

The programmer’s knowledge of the language seems to play a significant role here. Individuals comfortable in their language have an easier time doing these changes. It has been especially true of the candidates proficient with Python. They use many list constructs to make the coding easier.

Completing this question is a positive sign that the interviewee is a good programmer or at least a good coder. I don’t even care if they had minor issues in the refactoring, but usually, they don’t. It’s a curious pattern, the people that make it this far tend not to have difficulties with this question.

Super Bonus Question

Should the interviewee add N players support, I still have one additional change. It’s more challenging than the previous requirements: “don’t assume the cards have unique values”. The details are:

  • Remove the assumption that the cards are unique (that is, go to a standard deck)
  • If players have the same valued card, they draw an additional card, repeat until one has a higher card
  • The person with the highest card takes all the cards, scoring one each
  • Only the players that tie continue drawing cards (1+ players may sit out on the additional draws)

~Only one person~ Two people have made it this far, setting the gold standard for this interview question.

Only the time pressure

What I like about this question is that it lacks any trickery or random algorithmic knowledge. The pacing seems to work well: interviewees manage varying degrees of the problem. Given the difficulties I’ve seen people have, it seems to test a good cross-section of abilities.

As interview practice, try coding the answer. Pay attention to which bits cause you trouble and watch how long you take.

Edaqa Mortoray

An avid writer and expert programmer. He’s been on both sides of the interview table countless times and enjoys sharing his experiences. https://edaqa.com/