Search code examples
javascriptfor-loopincrement

Is there a difference between a for loop without increment vs. one with a non incremented variable?


In JS, is there any difference between the following two types of loops?

  1. A for loop without an increment such as:

    for (i = 0; i < 1000;) { // i will be changed inside the block
    

vs

  1. A for loop with a variable defined, but one that is not being incremented? Like this:

    for (i = 0; i < 1000; i) { // i changed inside block
    

I saw this strange construct in several Stack Overflow questions. i meant to note the i variable changes, not that i myself changed something or that i have been changed in some way.


Solution

  • Under normal circumstances: no, there is no difference.

    The for statement creates a loop that consists of three optional expressions, enclosed in parentheses and separated by semicolons, followed by a statement (usually a block statement) to be executed in the loop.

    The three expressions (initialization, condition and final-expression) are all optional (unlike the semicolons themselves), so if you omit the final-expression (which is most commonly used to increment the i/index/counter) that part simply will not be used.

    If you use an isolated i as the final-expression, then this will be evaluated after every iteration of the for-loop. But an isolated variable on its own usually doesn't have any side effects. The value wont change and the value that is returned by the i expression (the value of i) is ignored in case of the final-expression.

    console.log('For loop without final-expression:');
    for (let i = 0; i < 10;) {
      console.log(i++);
    }
    
    console.log('For loop with final-expression:');
    for (let i = 0; i < 10; i) {
      console.log(i++);
    }

    But that is all under normal circumstances. There are cases where it does make a difference, but you really should never encounter this in production-ready code.

    const target = {
      i: null,
    };
    
    const proxy = new Proxy(target, {
      get(target, prop) {
        if (prop === 'i') {
          return target._i++;
        }
        return Reflect.get(...arguments)
      },
      set(target, prop, value) {
        if (prop === 'i') {
          target._i = value;
        } else {
          return Reflect.set(...arguments);
        }
      }
    });
    
    with(proxy) {
      console.log('For loop without final-expression:');
      for (i = 0; i < 10;) {
        console.log("loop");
      }
    
      console.log('For loop with final-expression:');
      for (i = 0; i < 10; i) {
        console.log("loop");
      }
    }

    In the above example I used a Proxy to be able to intercept all access to the properties of an object and I used a with statement so that every variable is treated as a property of this proxied object. As a result, when i is read, the getter of the proxy is called, and I increment the value of i every time this happens.

    In for (i = 0; i < 10; i), i is set once at i = 0 and read twice in i < 10 and the final-expression i. Because it is read twice, the i will be incremented twice, so the loop will only iterate 5 times. In the other for (i = 0; i < 10;) loop i is only read once, so the loop will iterate 10 times.

    There are likely more ways to achieve a similar effect, but again you should really never encounter this in production ready code, so it shouldn't be taken into account when deciding if you want to keep the i in the final-expression or not.

    Personally I would say that if you don't use an optional expression, then you should leave it out. Especially because when glancing over the code, i can be misread as i++ which might confuse the reader. But in the end it is a matter of taste.