Search code examples
javascriptfor-loopundefinedenumerabletypescript-never

Why can for...in loop run on undefined?


Why does this code work?

for (const i in undefined) {}

From for...in - JavaScript | MDN:

The for...in statement iterates over all enumerable string properties of an object (ignoring properties keyed by symbols), including inherited enumerable properties.

But according to undefined - JavaScript | MDN, undefined has no enumerable property. So the for...in loop shouldn't work on it.

The deno-ts linter says:

The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter, but here has type 'never'.


Solution

  • But according to undefined - JavaScript | MDN, undefined has no enumerable property. So the for...in loop shouldn't work on it.

    Looping over something that is "empty" means the loop is a no-op. Not that there would be an error. Consider this for..of example:

    const arr = [];
    
    console.log("start loop");
    for (const item of arr) {
      console.log("item is", item);
    }
    console.log("end loop");

    And with this consideration, let us look at for..in where the behaviour is the same:

    const object = {};
    
    console.log("start loop");
    for (const key in object) {
      console.log("key is", key);
    }
    console.log("end loop");

    Looping over an empty object with no enumerable properties using for..in does not error.

    The same applies to undefined. It has no enumerable properties, so the loop is merely a no-op.

    The deno-ts linter says:

    The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter, but here has type 'never'.

    One of the things that linters do is detect potential problems. Writing a loop which is a guaranteed no-op does not seem like correct code. Same as if you had:

    if (false)
        doSomething();
    

    or maybe a bit more complex (so it is not as easy to spot)

    if (x === 1 && x === 2)
        doSomething();
    

    A linter should flag both of these for the same reason - the code is most likely a mistake because it is guaranteed to never execute.