Side effects in Programming

Image for post
Image for post
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. Everything I read wasn’t really helpful in clearly 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()

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]

Three completely different answers for the exact same 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.

# PythonmyList = [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

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.

// JavaScriptmyList = [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.

# RubymyList = [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!

Image for post
Image for post

This post is part of a larger series of blog posts I’m working on 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.

Written by

Android Engineer @Fueled. Producing @unboxpod.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store