Why avoid increment ("++") and decrement ("--") operators in JavaScript?

Viewed 361051

One of the tips for jslint tool is:

++ and --
The ++ (increment) and -- (decrement) operators have been known to contribute to bad code by encouraging excessive trickiness. They are second only to faulty architecture in enabling to viruses and other security menaces. There is a plusplus option that prohibits the use of these operators.

I know that PHP constructs like $foo[$bar++] may easily result in off-by-one errors, but I couldn't figure out a better way to control the loop than a:

while( a < 10 ) do { /* foo */ a++; }

or

for (var i=0; i<10; i++) { /* foo */ }

Is the jslint highlighting them because there are some similar languages that lack the "++" and "--" syntax or handle it differently, or are there other rationales for avoiding "++" and "--" that I might be missing?

17 Answers

My view is to always use ++ and -- by themselves on a single line, as in:

i++;
array[i] = foo;

instead of

array[++i] = foo;

Anything beyond that can be confusing to some programmers and is just not worth it in my view. For loops are an exception, as the use of the increment operator is idiomatic and thus always clear.

There is a history in C of doing things like:

while (*a++ = *b++);

to copy a string, perhaps this is the source of the excessive trickery he is referring to.

And there's always the question of what

++i = i++;

or

i = i++ + ++i;

actually do. It's defined in some languages, and in other's there's no guarantee what will happen.

Those examples aside, I don't think there's anything more idiomatic than a for loop that uses ++ to increment. In some cases you could get away with a foreach loop, or a while loop that checked a different condtion. But contorting your code to try and avoid using incrementing is ridiculous.

If you read JavaScript The Good Parts, you'll see that Crockford's replacement for i++ in a for loop is i+=1 (not i=i+1). That's pretty clean and readable, and is less likely to morph into something "tricky."

Crockford made disallowing autoincrement and autodecrement an option in jsLint. You choose whether to follow the advice or not.

My own personal rule is to not do anything combined with autoincrement or autodecrement.

I've learned from years of experience in C that I don't get buffer overruns (or array index out of bounds) if I keep use of it simple. But I've discovered that I do get buffer overruns if I fall into the "excessively tricky" practice of doing other things in the same statement.

So, for my own rules, the use of i++ as the increment in a for loop is fine.

Consider the following code

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[i++] = i++;
    a[i++] = i++;
    a[i++] = i++;

since i++ gets evaluated twice the output is (from vs2005 debugger)

    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int

Now consider the following code :

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[++i] = ++i;
    a[++i] = ++i;
    a[++i] = ++i;

Notice that the output is the same. Now you might think that ++i and i++ are the same. They are not

    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int

Finally consider this code

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[++i] = i++;
    a[++i] = i++;
    a[++i] = i++;

The output is now :

    [0] 0   int
    [1] 1   int
    [2] 0   int
    [3] 3   int
    [4] 0   int
    [5] 5   int

So they are not the same, mixing both result in not so intuitive behavior. I think that for loops are ok with ++, but watch out when you have multiple ++ symbols on the same line or same instruction

The "pre" and "post" nature of increment and decrement operators can tend to be confusing for those who are not familiar with them; that's one way in which they can be tricky.

The most important rationale for avoiding ++ or -- is that the operators return values and cause side effects at the same time, making it harder to reason about the code.

For efficiency's sake, I prefer:

  • ++i when not using the return value (no temporary)
  • i++ when using the return value (no pipeline stall)

I am a fan of Mr. Crockford, but in this case I have to disagree. ++i is 25% less text to parse than i+=1 and arguably clearer.

As mentioned in some of the existing answers (which annoyingly I'm unable to comment on), the problem is that x++ ++x evaluate to different values (before vs after the increment), which is not obvious and can be very confusing - if that value is used. cdmckay suggests quite wisely to allow use of increment operator, but only in a way that the returned value is not used, e.g. on its own line. I would also include the standard use within a for loop (but only in the third statement, whose return value is not used). I can't think of another example. Having been "burnt" myself, I would recommend the same guideline for other languages as well.

I disagree with the claim that this over-strictness is due to a lot of JS programmers being inexperienced. This is the exact kind of writing typical of "overly-clever" programmers, and I'm sure it's much more common in more traditional languages and with JS developers who have a background in such languages.

My 2cents is that they should be avoided in two cases:

1) When you have a variable that is used in many rows and you increase/decrease it on the first statement that uses it (or last, or, even worse, in the middle):

// It's Java, but applies to Js too
vi = list.get ( ++i );
vi1 = list.get ( i + 1 )
out.println ( "Processing values: " + vi + ", " + vi1 )
if ( i < list.size () - 1 ) ...

In examples like this, you can easily miss that the variable is auto-incremented/decremented or even remove the first statement. In other words, use it only in very short blocks or where the variable appears in the block on just a couple of close statements.

2) In case of multiple ++ and -- about the same variable in the same statement. It's very hard to remember what happens in cases like this:

result = ( ++x - --x ) * x++;

Exams and professional tests asks about examples like above and indeed I've stumbled upon this question while looking for documentation about one of them, but in real life one shouldn't be forced to think so much about a single line of code.

Is Fortran a C-like language? It has neither ++ nor --. Here is how you write a loop:

     integer i, n, sum

      sum = 0
      do 10 i = 1, n
         sum = sum + i
         write(*,*) 'i =', i
         write(*,*) 'sum =', sum
  10  continue

The index element i is incremented by the language rules each time through the loop. If you want to increment by something other than 1, count backwards by two for instance, the syntax is ...

      integer i

      do 20 i = 10, 1, -2
         write(*,*) 'i =', i
  20  continue

Is Python C-like? It uses range and list comprehensions and other syntaxes to bypass the need for incrementing an index:

print range(10,1,-2) # prints [10,8.6.4.2]
[x*x for x in range(1,10)] # returns [1,4,9,16 ... ]

So based on this rudimentary exploration of exactly two alternatives, language designers may avoid ++ and -- by anticipating use cases and providing an alternate syntax.

Are Fortran and Python notably less of a bug magnet than procedural languages which have ++ and --? I have no evidence.

I claim that Fortran and Python are C-like because I have never met someone fluent in C who could not with 90% accuracy guess correctly the intent of non-obfuscated Fortran or Python.

The operators mean different things when used as prefixes versus suffixes, which can cause hard-to-find bugs. Consider the following example, using bubbleSort:

function bubbleSort(array) {
  if(array.length === 1) return array;

  let end = array.length - 2;
  do {
    for (let i = 0; i < array.length; i += 1) {
      if (array[i] > array[i + 1]) {
        swap(array, i, i + 1);
      }
    }
  } while (end--);
}

bubbleSort([6,5]);

Let's imagine in the course of running our program, we pass a two-item value into our sort function. The code runs fine as-is: the "do/while" loop first executes before reaching the condition. However, the program sees that end is falsy and exits the loop before decrementing the variable.

Now consider the following code, where the -- symbol is used as a prefix, rather than a suffix. This code will enter an infinite loop:

function bubbleSort(array) {
  if(array.length === 1) return array;

  let end = array.length - 2;
  do {
    for (let i = 0; i < array.length; i += 1) {
      if (array[i] > array[i + 1]) {
        swap(array, i, i + 1);
      }
    }
  } while (--end);
}

bubbleSort([6,5]);

Now when we hit the while condition, we decrement the end value before checking it. This returns -1, which in Javascript, is a truthy value.

I don't have a strong opinion on their use one way or the other, but I just wanted to show how they can cause real bugs when used carelessly.

Argument for ++ is that ++ is not applicable for strings which will cause TypeError, which is almost always preferable.

Related