The Difference Between Statements and Expressions

The Difference Between Statements and Expressions Featured Image

As I grow more interested in programming languages—and languages in general—I find that the theory doesn’t always match up with reality. For instance, I just learned about the difference between statements and expressions and how that difference isn’t always explicit in modern programming languages.

Table of Contents

Background

As a current PhD student and Graduate Teaching Assistant, I’ve been focusing a lot on what it takes to be a good professor. To do that, I’ve been learning from different faculty about their experiences and philosophies. Recently, I learned about the difference between statements and expressions, so I thought that would be fun to share with you.

Oddly enough, I actually learned the distinction the hard way while training to teach a software fundamentals course. As a part of that training, I had to complete all the programming assignments, so I could get feedback from the instructor. At one point, the instructor mentioned to me that they didn’t like the following Java syntax:

a[++i]

In this case, we have an array that we’re accessing through ++i. In other words, we increment i then access a at that index—all in one line. See any problems? If not, don’t worry! That’s the topic of today’s article.

Terminology

Right out of the gate, I’d like to differentiate two terms: expression and statement. These terms will form the basis of the argument behind why a[++i] is considered bad practice.

Expressions

In Computer Science, when we talk about expressions, we’re referring to anything that can be evaluated to produce a value. Naturally, we can think of any data by itself as an expression because data always evaluates to itself:

4
"Hi!"
x
'w'
true
9.0

Of course, expressions can be made up of expressions:

4 + 2
"Hi," + " friend!"
x * y
'w' + 4
true == !false
9.0 / 3

In each of these scenarios, we use operators to nest our expressions, so we get something that might look like the following language grammar:

<expr>: number 
      | (<expr>)
      | <expr> * <expr>
      | <expr> + <expr> 

Here, we’ve created a silly grammar which defines an expression as a number, an expression in parentheses, an expression times an expression, or an expression plus an expression. As you can probably imagine, there are a lot of ways to write an expression. The only rule is that the expression must return a value.

Statements

In contrast, statements do not return anything. Instead, they perform an action which introduces some form of state (aka a side effect). The following list contains a few examples of statements:

x = 5
if (y) { ... }
while (true) { ... }
return s

If we look closely, we might notice that some statements contain expressions. However, the statements themselves do not evaluate to anything.

The interesting thing about statements is that they depend on order. To make sense of some statement, it’s important to understand the context leading up to it.

In contrast, expressions don’t depend on state since they do not produce side effects, so any nested expression can be reasoned about directly. For instance, notice how we can isolate any part of the following expression and evaluate its result:

((6 * 7) + (5 + 2 + 1)) > 17

Sure, any outer scope is going to depend on the result of some inner scope, but evaluating (6 * 7) has no effect on 17. As a result, it’s very easy to reason about the expression even when elements of it change. Welcome to the foundations of functional programming—but, that’s a topic for a different time!

What’s the Catch?

Unfortunately, while the definitions I’ve provided are clean cut, modern programming languages don’t always adhere to the same principles. For example, is ++i a statement or an expression? Trick question: it may be both.

In Java, ++i and i++ can be used as standalone statements to change the state of the program. For instance, they’re often used to increment a variable in a for loop. In addition, however, they can be used as expressions:

a[++i]
a[i++]
someFunction(i++)

In other words, ++i returns a value, and that value is different from i++. As you can probably imagine, this ambiguity between statements and expressions can manifest itself into some nasty bugs. For example, what do you think the following program does?

i = 0
while (i < 5) {
  print(i)
  i = i++
}

Without getting into the weeds, this code snippet may do many different things. In Java, it will actually print zero indefinitely despite clearly incrementing i in the 4th line. As it turns out, the postfix ++ operator returns the old value of i after increasing its value by one. In other words, i is incremented then reset to zero.

The consequences of the ambiguity between statements and expressions is immense, and it carries over into functions and procedures as well.

But Wait, There’s More

Often times, terms like methods, functions, procedures, and subroutines are all used interchangeably. In fact, you’ll probably find that I hardly differentiate between the terms on my own site. That said, there is a subtle difference at least between functions and procedures, so let’s talk about it.

Functions

Like mathematical functions, programming functions return a value given some input:

int getLength(String s) { ... }
double computeAreaOfSquare(double length) { ... }
double computePotentialEnergy(double m, double g, double h) { ... } 

In other words, the return type of a function cannot be nothing (i.e. void). As a result, functions are similar to expressions: they return a value without any side effects. In fact, they often work in the place of expressions:

(getLength(s1) * 2) > getLength(s2)

By definition, a function would then be an expression.

Procedures

In contrast, procedures do not return a value. Instead, they perform some action:

void scale(Square sq, double sc) { ... }
void insertElementAt(int[] list, int index, int element) { ... }
void mutateString(char[] str) { ... }

As a result, procedures relate more closely to statements in that they only produce side effects. Naturally, they cannot be used as expressions:

mutateString(s) * 4 // What?

By definition, a procedure would then be a statement.

Blurring the Lines

Like with expressions and statements, modern programming languages have blurred the lines between functions and procedures. In some cases, it’s not even possible to separate the two.

Consider Java which has a pass-by-value system. If we want to design a data structure, we often implement actions like add, remove, push, pop, enqueue, dequeue, etc. These actions are intuitive because they work how we expect them to work. For example, if we want to add an element to a stack, we’re going to call push with a copy of the element as input.

Now, imagine we want to implement one of the remove methods (i.e. pop). How do we go about doing it without blurring the lines between function and procedure? Clearly, pop has a side effect: it removes the top element from the stack. Ideally, however, we’d also like to be able to return that value. Since Java is pass-by-valueOpens in a new tab., we can’t pass a reference to the element back to the caller through one of our parameters. In other words, we’re stuck creating a function with side effects.

As a consequence, our pop method could be used as either an expression or a statement. When used in an expression, it suddenly becomes difficult to reason about what that expression is doing because parts of that expression may see different states of the stack. In addition, successive calls to the same expression may yield different results as the state of the stack changes each call.

That said, there is one way around this problem. We could create a pair of methods, one function and one procedure, to get the top element from the stack (peek) and remove that element (pop). The idea here is that we maintain the separation between pure functionsOpens in a new tab. and procedures. In other words, we can use peek when we want to know what value is on the top of the stack without modifying the stack. Then, we can use pop to remove that top element.

Of course, introducing a pure function and a procedure in place of a function with side effects requires a bit of discipline that may or may not pay off. It’s up to you to decide if it’s worth the effort.

Discussion

For me, learning about the distinction between statements and expressions set off a chain reaction of questions about language design. After all, millions of people around the world are coding without any concern for these details, so my question is: does it really matter?

Lately, I’ve noticed a trend toward functional programming (FP), and I wonder if that’s a consequence of all the technical debt that’s built up from the blurred lines between expressions and statements. If not, is this trend toward FP really just hype? After all, FP isn’t new. For instance, Lisp is over 60 years old which is eons in the tech community. What are your thoughts?

While you’re here, check out some of these related articles:

Also, if you’re interested in growing the site, I have a mailing listOpens in a new tab. where you’ll get weekly emails about new articles. Alternatively, you can become a full blown memberOpens in a new tab. which will give you access to the blog. At any rate, thanks for taking some time to read my work!

Edit: Back when I used to have comments enabled on this blog, someone shared some kind words:

Statements and Expressions Thanks
Coding Tangents (45 Articles)—Series Navigation

As a lifelong learner and aspiring teacher, I find that not all subjects carry the same weight. As a result, some topics can fall through the cracks due to time constraints or other commitments. Personally, I find these lost artifacts to be quite fun to discuss. That’s why I’ve decided to launch a whole series to do just that. Welcome to Coding Tangents, a collection of articles that tackle the edge case topics of software development.

In this series, I’ll be tackling topics that I feel many of my own students have been curious about but never really got the chance to explore. In many cases, these are subjects that I think deserve more exposure in the classroom. For instance, did you ever receive a formal explanation of access modifiers? How about package management? Version control?

In some cases, students are forced to learn these subjects on their own. Naturally, this forms a breeding ground for misconceptions which are made popular in online forums like Stack Overflow and Reddit. With this series, I’m hoping to get back to the basics where these subjects can be tackled in their entirety.

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife and kid, playing Overwatch 2, Lethal Company, and Baldur's Gate 3, reading manga, watching Penguins hockey, and traveling the world.

Recent Posts