I spent my first year as a developer wondering why my variables kept changing when I didn't want them to. Then I discovered const and everything clicked.
What you'll master: How to use const correctly and avoid the 3 biggest mistakes that trip up developers
Time needed: 15 minutes of reading, lifetime of cleaner code
Difficulty: Beginner-friendly with advanced tips
Here's the thing nobody tells you: const doesn't make everything immutable. I learned this the hard way when my "constant" objects kept changing and breaking my app.
Why I Stopped Using var (And You Should Too)
My situation: Building a shopping cart where prices kept changing randomly
My setup:
- React frontend with vanilla JavaScript utilities
- Node.js backend processing orders
- Constant debugging sessions wondering why values changed
What broke everything:
- Using
varfor price calculations - Reassigning "constant" configuration values
- Objects that were supposed to stay the same kept mutating
I wasted 2 days tracking down a bug where var price = 29.99 somehow became price = 19.99 three functions later. Never again.
The const Basics That Actually Matter
The problem: Variables changing when you don't expect it
My solution: Use const for everything unless you need to reassign
Time this saves: Hours of debugging mysterious value changes
Step 1: Replace var and let with const
Stop using var. Seriously, just stop.
// ❌ Old way - values can change anywhere
var price = 29.99;
var productName = "JavaScript Course";
var isOnSale = true;
// Later in your code...
price = 19.99; // Oops, this works but breaks everything
// ✅ New way - explicit about what can change
const price = 29.99;
const productName = "JavaScript Course";
const isOnSale = true;
// Later in your code...
price = 19.99; // TypeError: Assignment to constant variable
What this does: Prevents accidental reassignment that breaks your app
Expected output: TypeError if you try to reassign (which is good!)
Personal tip: "I use const for 90% of my variables. Only use let when you actually need to reassign the variable."
Step 2: Understand const with Objects (This Trips Everyone Up)
Here's where it gets confusing. const prevents reassignment, not mutation.
// ❌ This seems safe but isn't
const user = {
name: "John",
email: "john@example.com"
};
// This actually works (and might break your app)
user.email = "hacker@evil.com";
user.isAdmin = true;
console.log(user);
// Output: { name: "John", email: "hacker@evil.com", isAdmin: true }
// ❌ This fails (good!)
user = { name: "Different User" }; // TypeError
What this does: The object reference stays constant, but properties can change
Expected output: The object properties mutate successfully (usually not what you want)
Personal tip: "This confused me for months. const protects the variable, not the contents."
Step 3: Create Actually Immutable Objects
When you need objects that truly can't change:
// ✅ Shallow immutability - properties can't be added/modified
const config = Object.freeze({
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3
});
// This fails silently in normal mode, throws error in strict mode
config.apiUrl = "https://hacker.com"; // Doesn't work
config.newProp = "test"; // Doesn't work
console.log(config);
// Output: { apiUrl: "https://api.example.com", timeout: 5000, retries: 3 }
// ✅ Deep immutability for nested objects
const deepConfig = Object.freeze({
api: Object.freeze({
url: "https://api.example.com",
version: "v1"
}),
features: Object.freeze(["auth", "payments", "analytics"])
});
What this does: Makes objects and their properties truly immutable
Expected output: Properties stay unchanged even when you try to modify them
Personal tip: "Use Object.freeze() for configuration objects that should never change. Your future self will thank you."
The 3 const Mistakes I Made (So You Don't Have To)
Mistake #1: Thinking const Makes Everything Immutable
// ❌ I thought this array couldn't be modified
const shoppingCart = ["laptop", "mouse"];
// But this still works
shoppingCart.push("keyboard");
shoppingCart.pop();
shoppingCart[0] = "desktop";
console.log(shoppingCart); // ["desktop", "mouse", "keyboard"]
// ✅ Actually immutable array
const immutableCart = Object.freeze(["laptop", "mouse"]);
immutableCart.push("keyboard"); // TypeError in strict mode
Personal tip: "Arrays and objects with const can still be modified. Add Object.freeze() if you need true immutability."
Mistake #2: Not Using const in Loops
// ❌ I used let out of habit
for (let i = 0; i < users.length; i++) {
let user = users[i]; // This never needs to be reassigned
processUser(user);
}
// ✅ Better - const when you don't reassign
for (let i = 0; i < users.length; i++) {
const user = users[i]; // Clear that user won't change
processUser(user);
}
// ✅ Even better with modern JavaScript
for (const user of users) {
processUser(user);
}
Personal tip: "Use const in loop bodies when the variable doesn't change. It makes your intent clearer."
Mistake #3: Reassigning When I Meant to Mutate
// ❌ Wrong approach - creates new objects unnecessarily
let userSettings = { theme: "dark", notifications: true };
function updateTheme(newTheme) {
userSettings = { ...userSettings, theme: newTheme }; // New object every time
}
// ✅ Better - mutate when appropriate
const userSettings = { theme: "dark", notifications: true };
function updateTheme(newTheme) {
userSettings.theme = newTheme; // Direct mutation is fine here
}
// ✅ Best - immutable updates with new variable
const originalSettings = Object.freeze({ theme: "dark", notifications: true });
function updateTheme(settings, newTheme) {
return { ...settings, theme: newTheme }; // Return new object
}
const newSettings = updateTheme(originalSettings, "light");
Personal tip: "Know when you want mutation vs. immutability. Both have their place."
Real-World const Patterns I Use Daily
API Configuration (Always const + freeze)
const API_CONFIG = Object.freeze({
BASE_URL: "https://api.myapp.com",
ENDPOINTS: Object.freeze({
USERS: "/users",
ORDERS: "/orders",
PRODUCTS: "/products"
}),
HEADERS: Object.freeze({
"Content-Type": "application/json",
"Accept": "application/json"
})
});
// Use it safely anywhere
fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.USERS}`, {
headers: API_CONFIG.HEADERS
});
Personal tip: "Freeze your config objects. I've seen too many bugs where someone accidentally changed a base URL."
Form Validation Rules
const VALIDATION_RULES = Object.freeze({
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: "Please enter a valid email"
},
password: {
required: true,
minLength: 8,
message: "Password must be at least 8 characters"
}
});
function validateField(fieldName, value) {
const rule = VALIDATION_RULES[fieldName];
if (!rule) return { valid: true };
if (rule.required && !value) {
return { valid: false, message: rule.message };
}
if (rule.pattern && !rule.pattern.test(value)) {
return { valid: false, message: rule.message };
}
return { valid: true };
}
Personal tip: "Validation rules should never change during runtime. const + freeze prevents accidental modifications."
Default Component Props
// ✅ React component with immutable defaults
const Button = ({
type = "button",
className = "",
disabled = false,
children,
...props
}) => {
const defaultClasses = "px-4 py-2 rounded font-medium";
const buttonClasses = `${defaultClasses} ${className}`.trim();
return (
<button
type={type}
className={buttonClasses}
disabled={disabled}
{...props}
>
{children}
</button>
);
};
// Usage stays clean
const MyForm = () => (
<form>
<Button type="submit" className="bg-blue-500 text-white">
Submit
</Button>
</form>
);
Personal tip: "Use const for default values in React components. It's clearer than let and prevents accidental changes."
Performance: Does const Actually Help?
Short answer: Not much for performance, huge win for debugging.
// Performance test I ran (your results may vary)
console.time("const test");
for (let i = 0; i < 1000000; i++) {
const x = i * 2;
const y = x + 1;
}
console.timeEnd("const test"); // ~15ms
console.time("let test");
for (let i = 0; i < 1000000; i++) {
let x = i * 2;
let y = x + 1;
}
console.timeEnd("let test"); // ~15ms (virtually the same)
The real benefit: Code clarity and bug prevention, not speed.
Personal tip: "Choose const for code quality, not performance. The JavaScript engine optimizes both the same way."
What You Just Built
You now know how to use const properly and avoid the mutation bugs that waste hours of debugging time.
Key Takeaways (Save These)
- Use const by default: Only use
letwhen you need to reassign the variable - const ≠ immutable: Objects and arrays with const can still be modified - add Object.freeze() for true immutability
- Fail fast: const throws errors when you try to reassign, which catches bugs early
Your Next Steps
Pick one:
- Beginner: Refactor an existing project to use const instead of var/let where appropriate
- Intermediate: Learn about deep freezing and immutability libraries like Immer
- Advanced: Explore TypeScript's readonly types for compile-time immutability
Tools I Actually Use
- ESLint prefer-const rule: Automatically suggests const when variables aren't reassigned
- Immer: For complex immutable updates without the headache
- TypeScript: For readonly types that catch mutation bugs at compile time
- Object.freeze(): Built-in JavaScript for simple immutability needs