Problem solving like a programmer

Janel Brandon
9 min readMar 9, 2019

Programming is fun, and also challenging. It’s easy to get caught up in the syntax and semantics of the languages and frameworks themselves, and there are so many languages and frameworks. While it is important to learn syntax and semantics, I would say that is a less challenging part of programming because you can always just look it up. When you know lots (or even just a few) languages, you have to look it up, because you forget which syntax goes with which language. At least, I do!

A much more challenging aspect of programming is learning to think like a programmer when you are solving a problem. You start with a problem (where problem is a generic term meaning something you want to write code to accomplish), clearly define the problem, then translate it into data structures (the strings, numbers, arrays, hashes, objects, etc. that you will use to represent the problem) and control structures and flow (the loops, conditionals, methods, and algorithms) that will make up a solution.

Let’s look at an example. We’ll walk together through these steps to create a program to play a game of tic-tac-toe (or naughts and crosses, depending on which part of the world you come from).

In this example, we won’t write any code. It will be language-agnostic. We will just go through the steps to create some pseudocode with the data and control structures we would need to write a program.

Step 1: Define the problem

Licence: Creative Commons — CC0. Photos transferred to the public domain from Creative Commons.

When you define the problem, be as precise as possible. It helps to draw pictures here, and to think of some example scenarios. A lot of times, I think I define the problem clearly, but as I try to get through the next steps of defining the data and control structures, I realise I didn’t! That’s OK. I just go back and redefine any parts of the problem that are unclear when necessary.

Here is how I would define the problem of a game of tic-tac-toe.

Problem definition: A game of tic-tac-toe will be played by two players on a 3x3 playing grid. One player will use ‘X’ to mark a play and the other will use ‘O’. Players take turns, making one mark with each play in an empty space on the playing grid until one of them wins or a tie is reached. A win occurs when a player gets three marks in a row vertically, horizontally, or diagonally. A tie is reached when it is not possible for either player to make a play that would result in a win.

At this point, I can already start to see the data structures I will need. I won’t see all of them until I write out some kind of flow diagram, but some of them are evident. Can you see them? Some initial data structures I spot are the playing grid, player1 move, and player2 move. Can you spot others?

Step 2: Brainstorm possible solutions

For a tic-tac-toe game, this step may not be entirely necessary, but in most problems it is an important step. This step benefits greatly from different perspectives, so if you’re working on a team, get everyone’s input. Talk through suggested solutions, making note of the benefits and challenges for each, at least at a high level. Come to agreement on a solution, with an understanding that you will most certainly change parts of it as you work on the solution, and may even change it completely after you talk through the flow in more detail or face unforeseen challenges during implementation.

Step 3: Define the flow

In this step, it’s particularly helpful to write or draw out some possible outcomes.

Four different game outcomes

In the image above, there are four different game outcomes. In #1 and #3, there is a winner, but in #3, the entire grid wasn’t filled. In #2 and #4, there is a tie, but in #4, the grid wasn’t filled completely before we could make that determination. Our flow and our program should consider all of these cases.

Now I’m going to give ONE EXAMPLE of a high level flow. There will be a number of possibilities, and this is the first one that comes to mind for ME. When I’m developing a solution, I start with what comes to me after a little thought. I often refine my flow after I flesh it out more, and often again as I work on implementation for more complex problems. THIS IS OK. If you get bogged down by the idea that you have to come up with the perfect, most elegant, or most performant solution in the first go, YOU WILL GET STUCK. Just let it flow. That’s one of my favourite mantras in programming and in life.

Basic high level flow for the game

I start with a basic high level flow. This will reveal the main control structures — the loops and conditionals. Can you see them?

When I create this high level flow, I phrase any basic, two-answer questions as yes/no questions, and I put them in a diamond. This is just a convention, but one I’m accustomed to, so it makes it easier for me to spot that this is where I need a control structure like an if statement or a loop termination statement.

A side note — if there is a question with more than two answers that each result in unique outcomes, I might represent it differently. That’s because I know that these kinds of questions can be handled with a case or switch statement.

If there are steps that would be repeated, I draw an arrow from the last step in the sequence, to the first step in the sequence that should be repeated. This reveals the loop we need in this high-level solution. In our example, the first step in the repeating sequence is ‘player1 takes turn’, and there are two statements in the sequence that lead to loop termination — ‘there is a tie?’ and ‘there is a winner?’. The last step of the loop sequence is always a yes/no question. That is our loop termination condition.

High level flow with flow control noted

Step 4: Identify variables and data structures

Once we’ve defined a high level flow, we can define some variables and data structures. This is probably the most difficult part of this process. Even in a simple example, there are a lot of ways the data can be represented. Just like with high level flow, just start writing down what comes to mind, knowing you can refine it later if it makes sense.

When I do this step, I think about what things I need to represent as I look at the flow, and what types of data structure could hold the represented information. I’ll point out that we could define more complex objects to represent our data for this program than what I will describe here. For example, we could define a game grid class that represents the field of play for the game. We could define a player class that represents a player, with attributes like name, age, handicap, mark used, etc. I will not do that in this post so I can focus on basic determination of program flow and data structure that is independent of language semantics. I’ll save a discussion of classes for another post.

In this example, this is what I determine we need initially:

Initial variables and data structure

Here’s some more explanation.

I need to represent the 3x3 game grid itself. I will need to keep track of where player 1 and player 2 have placed their marks in the grid, and be able to determine at each iteration through the loop if we have reached the loop termination condition. Specifically, I need to determine if there are three marks from the same player in a horizontal row, vertical column, or a diagonal (player1 wins or player2 wins), or if there is a tie (either all of the grid is filled and there is no winner, or if the empty spots were filled by either player, there would still be no winner).

Considering this, the game_grid could be a two-dimensional array (or a matrix if supported by the language).

The mark for each player (an ‘X’ or an ‘O’ in the traditional game) could be represented by a string (or character) or a number (such as ‘1’ and ‘2’). Similarly, the game outcome (one of four options listed) could be represented as a string or a number. When I’m trying to make these decisions, I think about how I will use the data to control the flow of the program, and how I will use it to interact with the user. For example, if I want to show the current game grid to the user (which would be important), I may want to store the player marks as literal ‘X’ and ‘O’, and therefore storing them as strings would allow me to print the game_grid directly, so I may choose to store them as strings rather than numbers. Mostly these choices do not matter — choose what you like and is easiest for you to understand, and make sure that what the variables represent is apparent to anyone else reading your code.

We will have to record each player’s current move inside of our loop. They will have to choose an empty grid location, and we will have to record their mark in that row and column of the game grid. I would make a note that we need to do some input validation here, to make sure that only an empty and valid game grid cell is chosen.

Step 5: Write some pseudocode

Start with high level code, like this:

High level pseudocode

From this high level description, we can easily identify some functions or methods. For example, I would plan to have functions for print_grid, player_move (to prompt a player to move, validate the move, and update the game grid), and check_outcome. I would also have a function to print_outcome.

We’ll go one step further here and write some pseudocode for the check_outcome function we would define. In this process we’ll see that when we start implementing the code, we’ll define more functions, variables, and data structures as needed. This is where algorithm knowledge and practice comes into play. It requires some experience with common data structures like arrays and strings, and a fundamental understanding of loops and conditionals. This come with practice, so just keep practicing and don’t get discouraged.

Pseudocode for check_outcome function

The items I marked in blue represent other functions I would define. I strive to make each of my functions small, and more importantly, to make sure that they only perform one job with no side effects. The job of the check_outcome function is to just determine if there is a winner, a tie, or if the game is unfinished. Here is pseudocode for the check_rows_cols, check_diags, and grid_full functions. Notice that they are small, and perform very specific jobs:

Pseudocode for check_rows_cols, check_diags, and grid_full functions

I went further and defined row_vals_equal and col_vals_equal to do the actual comparisons based on index in check_rows_cols:

Pseudocode for more helper functions

Each of these functions compares values in the row (row_vals_equal) or column (col_vals_equal) for the index passed in, and if it finds the values are all equal, it returns the player’s mark for that row or column.

You can determine to which extent you modularise your functions. Remember it is better to err on the side of more smaller functions than less larger functions. It makes testing and maintenance of your program much easier when you keep your functions small with specific actions.

Step 6: Write some actual code

From this step, you can write some actual code! Yay! I’ll leave this for you to try, but reach out if you get stuck.

I hope I’ve given some useful info in this blog post. Remember though that the very most important thing to remember when you are programming is TO HAVE FUN!

About me: I’m an instructor at a programming bootcamp with 20+ years experience as a software engineer. I love to see people be empowered by coding to create solutions that make the world a nicer place to live in for everyone.

--

--

Janel Brandon

I have been working in software development for more than 20 years as a developer, sales advocate, teacher, certification developer, and engineering manager