It has nothing to do with arrow functions. let is block level scope, so each time you enter the body of the loop, you get a new variable.
In your case, the timer function prints differently, because of the closure being created around x.
var provides one variable that all iterations of the loop (and therefore, all timer callbacks) share, hence you get the last value of the variable shared by each timer callback. Remember, the timer callbacks won’t run until your loop is finished and, at that time, x has been incremented to 5.
let gives each timer callback it’s own value because of the block scope. So, even though the timer callbacks don’t run until the loop is finished, each callback has a closure around a differently scoped variable.