Comments explain decisions that code alone cannot make obvious. Variables give names to values so a program can read input, calculate results, track state, and pass data between operations. These two features appear in almost every JavaScript file, so using them clearly prevents confusion from the beginning.
Use const for a binding that will not be reassigned. Use let when the binding must receive a different value later. Treat var as legacy syntax that you still need to recognize because it has function scope and different hoisting behavior. Choose the narrowest useful scope and a name that describes the value rather than its type or storage location.
A line comment begins with // and continues to the end of that line. A block comment begins with /* and ends with */. JavaScript ignores comment text while parsing executable statements, but comments inside strings and template literals remain ordinary text.
Write a comment when the reason for a decision, constraint, workaround, unit, or side effect is not clear from the code. Do not translate every statement into English. A comment that only says "increase count" above count += 1 adds noise and can become incorrect when the code changes.
// Prices arrive in cents to avoid floating-point rounding errors.
const priceInCents = 1299;
/*
* The API permits at most three attempts for this operation.
* Keep this value aligned with the server-side limit.
*/
const maxAttempts = 3;
const message = "// This is text, not a comment";
console.log(priceInCents, maxAttempts, message);
A variable declaration creates a binding between an identifier and a value. The value may be a number, string, object, function, or any other JavaScript value. The binding is what const protects: const prevents assigning a different value to the same name, while let permits reassignment.
Declare a variable near its first use and inside the smallest block that needs it. This limits accidental changes and makes dependencies easier to see. Initialize the declaration immediately when a meaningful value is available; an uninitialized let binding contains undefined after its declaration executes.
const taxRate = 0.18;
const subtotal = 2500;
let status = "pending";
status = "paid"; // valid: status was declared with let
const tax = subtotal * taxRate;
const total = subtotal + tax;
console.log({ status, subtotal, tax, total });
// { status: "paid", subtotal: 2500, tax: 450, total: 2950 }
const and let are block-scoped. A binding declared inside an if statement, loop, or standalone block is unavailable outside that block. var is function-scoped, so a var declaration inside a block remains visible throughout the containing function. This wider scope is a common source of accidental reuse in older code.
All three declarations are processed before execution reaches their line, but their access rules differ. Reading let or const before the declaration throws a ReferenceError because the binding is in the temporal dead zone. Reading var before its declaration returns undefined, which can hide an ordering mistake.
| Declaration | Scope | Reassign | Redeclare in same scope | Before declaration | Recommended use |
|---|---|---|---|---|---|
| const | Block | No | No | ReferenceError | Default for bindings that do not change |
| let | Block | Yes | No | ReferenceError | Counters, state transitions, and accumulators |
| var | Function | Yes | Yes | undefined | Maintain existing legacy code when required |
function inspectScope(isReady) {
if (isReady) {
const message = "Ready";
let attempts = 1;
var legacyFlag = true;
attempts += 1;
console.log(message, attempts); // Ready 2
}
console.log(legacyFlag); // true: var escaped the if block
// console.log(message); // ReferenceError
// console.log(attempts); // ReferenceError
}
inspectScope(true);
A const binding cannot point to a different value after initialization. However, an object or array stored in that binding may still be mutable. Updating a property, pushing an item, or deleting a key changes the value itself without reassigning the binding.
Use Object.freeze when shallow runtime protection is useful, but remember that nested objects remain mutable unless they are frozen as well. In application code, immutability is often maintained by creating a new object with spread syntax instead of modifying shared state in place.
const user = { name: "Mira", role: "viewer" };
user.role = "editor"; // valid mutation
// user = { name: "Mira" }; // TypeError: reassignment
const updatedUser = {
...user,
role: "admin"
};
console.log(user.role); // editor
console.log(updatedUser.role); // admin
A declaration is hoisted in the sense that JavaScript creates its binding while preparing the scope. That does not mean every binding is safe to read before the declaration statement. let and const stay inaccessible from the beginning of their block until execution reaches the declaration. This interval is called the temporal dead zone.
Function declarations have their own hoisting behavior and can usually be called earlier in the same scope. Function expressions stored in const or let follow the access rules of that variable declaration. Keep declarations before their first use even when a language rule technically permits another order.
console.log(legacyTotal); // undefined
var legacyTotal = 20;
// console.log(modernTotal); // ReferenceError
const modernTotal = 20;
function calculateTotal(price, quantity) {
return price * quantity;
}
console.log(calculateTotal(modernTotal, 2)); // 40
An identifier can start with a letter, underscore, or dollar sign. Later characters may also contain digits. JavaScript identifiers are case-sensitive, cannot contain spaces or hyphens, and cannot be reserved keywords such as const, class, return, or import. Unicode letters are valid, but teams commonly use ASCII names for consistency across tools and keyboards.
A valid name is not automatically a useful name. Prefer a name that communicates meaning, unit, and state. userCount is clearer than n, timeoutMs is clearer than timeout, and isAuthenticated is clearer than flag. Avoid encoding a type that may change, such as userArray, when users describes the domain adequately.
const customerName = "Ravi";
const orderItems = ["book", "pen"];
const MAX_RETRIES = 3;
let retryCount = 0;
let isPaymentComplete = false;
// Invalid declarations:
// const 2ndUser = "..."; // starts with a digit
// const user-name = "..."; // hyphen is subtraction
// const class = "..."; // reserved keyword
console.log({ customerName, orderItems, retryCount, isPaymentComplete });
Shadowing occurs when an inner scope declares the same identifier as an outer scope. JavaScript then resolves the inner name until that block ends. Intentional shadowing can be valid, but repeated names make it easy to update or inspect the wrong value.
Top-level declarations live longer and are accessible to more code than local declarations. In classic browser scripts, some var declarations also become properties of window, while top-level let and const do not. JavaScript modules have their own module scope. Keep values local and export only the bindings another module actually needs.
const currency = "INR";
function formatInvoice(amount) {
const formattedAmount = amount.toFixed(2);
return `${currency} ${formattedAmount}`;
}
console.log(formatInvoice(1250)); // INR 1250.00
// total = 1250; // ReferenceError in strict mode: undeclared name
When a declaration fails, read the exact error first. "Identifier has already been declared" means the same let or const name exists in that scope. "Cannot access before initialization" points to the temporal dead zone. "Assignment to constant variable" means code attempted to reassign a const binding. "x is not defined" means the name is misspelled, outside its scope, or was never declared.
Inspect the smallest relevant scope and search for every declaration and assignment of the name. Browser developer tools can pause at the failing line and show local, block, closure, module, and global bindings. A linter catches undeclared variables and suspicious var usage before the code runs.
The following example uses const for input and derived values, let for a status that intentionally changes, descriptive names with units, and comments only where they explain a business rule. This is the declaration style to aim for in everyday JavaScript.
const itemPriceInCents = 799;
const quantity = 3;
const freeShippingThresholdInCents = 2000;
const subtotalInCents = itemPriceInCents * quantity;
const hasFreeShipping = subtotalInCents >= freeShippingThresholdInCents;
// Standard shipping is charged only below the published threshold.
const shippingInCents = hasFreeShipping ? 0 : 150;
const totalInCents = subtotalInCents + shippingInCents;
let orderStatus = "draft";
orderStatus = "ready";
console.log({
orderStatus,
subtotalInCents,
shippingInCents,
totalInCents
});
Explore 500+ free tutorials across 20+ languages and frameworks.