Stop waving the wand of magic numbers

37. You have no idea what that number is, do you? A number without context nor a label is a random value. It doesn’t tell us anything. Imagine walking by a billboard, with a picture of a person on a boat, and the text is a giant number 89. I’d be intrigued, but utterly confused. We rightfully reject meaningless numbers…

…so why then do they appear so often in source code? Any time a constant appears in an expression, like 12, we call it a magic number. Through some dark craft, they arise out of the ether and populate our code. We don’t know where they came from, what they mean, or how they got there.

Though I don’t go down to the level of this article, my book has a chapter about interviewing as an essential programmer skill. Something simple like fixing magic numbers can leave a positive impression on the interviewer.

An example of magic

When I conduct interviews, I ask the candidate to create a deck of cards. I let them simplify it, by using sequentially numbered cards, instead of suits and ranks. In the majority of code I’m presented a loop of this form:

for i in 0..52:
    cards.add( i )

The number 52 doesn’t convey any information. Only because we’re talking about a card game, and the candidate has assumed a standard poker deck, does the number 52 appear. There are of course many card games that don’t have 52 cards.

I request they deal the cards out to multiple players. I accept splitting the deck as well. Frequently, I get code like this:

for i in 0..26:
    player1.add( cards[i] )
for i in 26..52:
    player2.add( cards[i] )

It’s not immediately obvious that this is splitting the deck in half. I have to reconstruct that in my head. Making a mistake while writing is easy. What if instead, you saw this code:

for i in 0..27:
    player1.add( cards[i] )
for i in 28..51:
    player2.add( cards[i] )

Is the code accounting for some inclusive/exclusive end condition? Is there a bunch of off-by-one errors in there? What is 51? It doesn’t line up with any number I know at all, not even about cards.

Magic number

In this code:

for i in 0...52:

The 52 is known as a magic number. A future coder has no information about what it is. These types of numbers convey no information as to their purpose.

It’s easy to get rid of them. Give the numbers a name.

num_cards_in_deck = 52
for i in 0..num_cards_in_deck:

We’ve immediately improved the quality of this code. A reader knows what the number 52 means. The loop now logically does something for every card in a deck.

Giving a name to a number removes its magical quality. Here we’ve done it beside the code, but typically symbols like num_cards_in_deck end up as global constants. They are context-free facts. If multiple pieces of code need the same value, then create one constant and share it.

Another response to the dilemma is to add a comment, such as # create a standard size deck to the loop. Just say no to this! Comments are a tool of last resort. They have no structure, can’t be enforced by the compiler, and inevitably become out-dated. Using proper symbol names is superior — code trumps prose.

With our new constant, we can change the second loops:

for i in 0..(num_cards_in_deck/2):
    player1.add( cards[i] )
for i in (num_cards_in_deck/2)..num_cards_in_deck:
    player2.add( cards[i] )

Again, the quality of the code has improved considerably. These loops now have meaning; it’s easy to see we’re treating the deck as two halves. It’s also much less likely to have an error as we’re letting the compiler do the division. Moreover, if the number of cards changes, this loop is still correct.

I begrudgingly use this example only because it comes up in my interviews. These two loops are still bad. From a defensive programming standpoint, you shouldn’t use a constant value at all. You should use len(cards) and len(cards)/2. Many magic numbers can be removed in this fashion. Rather than tying your code to an arbitrary value, base it on some other knowledge you already have. In this case, we have a cards collection which has all the information we need.

Put the wand down

Not all constants are magic numbers though. There are some special cases, in particular, 1. Adding 1 to a number creates its natural successor. There’d be no confusion about what is happening. There are more, but without seeing the code in particular, I don’t wish to give general exceptions.

Even a number like 2 may not seem magical, but can nonetheless be improved upon. In the example I’ve given, it’s clear that it splits something in half, but it doesn’t convey what half means. It’d be better to say num_players in that example. Many times, even if the number is obvious, it helps to give it a name. They add clarity to the code.

As fun as magic may be, it’s time to put the want down and stop conjuring these numbers into our code.

Addendum: A better dealing loop

The dealing loops shown above are still wrong in my opinion. Though not related to magic numbers anymore, here is code that does the dealing between players in a cleaner fashion. It avoids duplicate loops.

for i in len(cards):
    player_cards[i % num_players].add( cards[i] )

Another possible option, which closer mimics dealing, and avoids ranges entirely. It also uses an OOP player that contains cards. This type of code is suitable when you need to model all the states visually, or when there is partial dealing involved. It retains intermediate states.

cur_player = 0
while len(cards) != 0:
    player[cur_player].cards.add( cards.pop() )
    cur_player = (cur_player + 1) % num_players

9 replies »

  1. that is one scary dealing model. Do people really go balls through the wall like that?

    • Yes, it’s surprisingly rare when I encounter candidates that actually “deal” the cards. However, one of my followups is to add N-player support, at which point most candidates switch to a one-by-one dealing model. A few chose to do some fun range math and keep splitting though.

  2. I think it belongs into a different article. The dealing example is really simple, but if you dive into it you can come with different solutions that illustrate both approaches to the programming side of the problem and its user side. It should be educational to compare how complexity links to functionality in its good and bad ways.

    • Yes, I think another article is required. I posted an update, since I started feeling bad about leaving that improved, yet still ugly loop. I’m not quite sure how to formulate the next article — but I think it’s valuable, as it may not be obvious to many why the double-loop is bad. Or the pros/cons with my replacements as you say.

    • what comes from the top of my mind is the concept of responsibility separation and background assessment(both in interviews and real world). In a sense it is a follow up to this article since, as you point out, the magic numbers represent the ultimate irresponsibility- even replacing those with constants leaves us with a potential crash condition. A layer on top may be the actual game as concept and how you should approach the problem at hand while considering the background. The latter is more or less the issue with the loop rendered by the people you interviewed. The solution they provided is constrained in a context, so limited, that it actually completely disregards the problem at hand.

      A broader topic that is also relevant is in how far we should model reality in our code. For example should the player get a card using a reference of the deck as a parameter, or should the deck give a card using a reference of a player. Should we consider a table with ‘chairs’ for the players when modelling the game/dealing and if we do how will this improve the flexibility of our solution at what cost.

  3. 37 is the mirror of the best number 73. 73 is the 21st prime number. The mirror, 37, is the 12th prime and its mirror, 21, is the product of multiplying 7 and 3… and in binary 73 is a palindrome.

Leave a Reply to Tim Stoev Cancel reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s