Side Effects in Programming

Published: 07/07/2020

A random code snippet image presented as a wobbly GIF for decoration purposes only.

A few years ago, I was showing code I'd written to a friend in order to get feedback and one of the things he pointed out to me was that a function I'd written had side effects. This was the first time I'd heard this term.

After the call with my friend, I googled around to try and understand what he had meant. I started with the Wikipedia page and followed that up with a few blog posts. Nothing that I read was helpful in explaining what side effects in programming were.

Last year while reading Will Kurt's Get Programming with Haskell is when I finally understood what side effects in programming are and why they were considered a code smell and create grounds for poor software design.

This edition of Neat Examples is dedicated to the example that Will shared in his book along with some added details that I wrote down to help explain the concept even better. Let's start!


Suppose you're reading through a code base and you come across the following lines of code.

tick()
if (timeToReset) {
  reset()
}

On seeing this code snippet, it is reasonable to draw the following conclusions:

  1. Both tick and reset are functions that don't take any arguments and possibly don't return any value

  2. It is not a long shot to suppose that tick is incrementing a counter and that reset restores that counter to its starting value

  3. Given (1) and (2), tick and reset must be accessing a value in the environment, and since they don't return a value, they must be changing a value in the environment

  4. It's likely that both tick and reset are accessing a global variable (a variable reachable from anywhere in the program)

This is a classic situation of a block of code with side effects. A programmer looking at this code cannot be sure how its execution alters the state of the program. This, in programming, is considered as poor design.

Side effects in programming make it hard to understand what a simple, well-written piece of code really does. Programmers usually call such chunks of code “hard to reason about”.

Side Effects City

Let's look at an example to understand how side effects in programming lead to a lot of pain for programmers. Consider the following code snippet:

myList = [1, 2, 3]
myList.reverse()
newList = myList.reverse()

There are three completely different answers for this code. Let's break down line by line why this happens.

In Python, the first call to reverse updates (mutates) myList ie. updates the program's state. The second call reverses the list again but does not return a value. This is why newList is None.

# Python
myList = [1, 2, 3]
myList.reverse()  # myList updated to [3, 2, 1]
newList = myList.reverse()  # myList updated to [1, 2, 3]

# newList is None since reverse() returns nothing

What do you expect the value of newList to be? Take a minute to figure out your answer before reading ahead.

The program above is valid in Ruby, Python, and JavaScript, and so, it seems reasonable to assume that the value of newList should be the same for the three languages. Right? Well, here are the answers:

Ruby -> [3, 2, 1]
Python -> None
JavaScript -> [1, 2, 3]

JavaScript does the same thing as Python but returns the value as well and that's why newList is [1, 2, 3] in this case.

// JavaScript

myList = [1, 2, 3]
myList.reverse() // myList updated to [3, 2, 1]
newList = myList.reverse() // myList updated to [1, 2, 3]

// newList is [1, 2, 3]

Clearly, when calling reverse in Python and JavaScript, unintended things happen to the state of the program — side effects!

In Ruby, on the other hand, a call to reverse creates a copy of the original list, reverses the copy and returns it.

# Ruby

myList = [1, 2, 3]
myList.reverse()  # myList is still [1, 2, 3]
newList = myList.reverse()  # myList is still [1, 2, 3]

# newList is [3, 2, 1]

This is exactly how reverse should function. It should always return a reversed version of the list or collection you call it on, no matter what.

Back to Tick and Reset

When we called reset and tick earlier, the changes they made were invisible to the programmer. This is similar to our experience with Python and JavaScript.

Without looking at the source code (or documentation in the case of standard library functions like reverse), a programmer would have no way of knowing exactly how the program's state is being updated by tick and reset.

This is exactly why side effects in programming should be avoided and instead, code that clearly explains what it is doing should be striven for by all programmers.


Neat Examples Logo

This post is part of a larger series of posts under the name 'Neat Examples'. The idea behind this series is to provide examples and analogies to help programmers understand programming concepts with the highest possible chance of retention.