What is the difference between var, let and const in terms of scope, hoisting and reassignment?
There are three keywords and three sets of rules. They differ on scope, on when the name becomes usable, and on whether the value can be replaced later.
Scope. var is function scoped. It ignores blocks completely, so a var that is declared inside an if statement or inside a for loop is still visible after that block ends. let and const are block scoped, which means the name is only visible inside the braces that contain it.
Hoisting. All three are hoisted, in the sense that the engine knows about the name before the line runs. What happens at that moment is different, though. A var binding is created and set to no value, so reading it before the declaration gives you no value instead of an error. A let or const binding is created but left empty until the declaration runs, and touching it in between throws a ReferenceError. That short window is called the temporal dead zone.
Reassignment and redeclaration. const forbids both of them. You cannot give the binding a new value, and you cannot declare the same name twice in the same scope. let allows a new value but not a second declaration. var allows both, and that is why declaring the same var name twice is perfectly legal.
Practical guidance. Use const by default, switch to let only when the value really does change, and treat var as something you maintain in old code rather than something you write. There is one important subtlety here. const freezes the name, not the object behind it. You can still push items onto a const array, and you can still assign new properties to a const object.