TypeScript: Why Typed JavaScript Became Essential
TypeScript isn't a new language—it's JavaScript with guardrails that become essential at scale.
07/05/2026
JavaScript has evolved dramatically over the past decade. What was once considered standard practice is now often outdated or harmful to your codebase. Yet many developers continue to use patterns that belong in the past.
This article explores the most persistent bad habits in JavaScript and their modern alternatives.
var: Use let and constThe var keyword uses function scoping, which leads to unexpected behavior:
if (true) {
var message = "Hello";
}
console.log(message); // "Hello" — var ignores block scope
Variables declared with var are also hoisted, which can mask bugs:
console.log(name); // undefined, not an error
var name = "Alice";
The fix: Use const by default, and let only when reassignment is needed. Both respect block scope and behave predictably.
querySelector Over Older DOM MethodsMethods like getElementById and getElementsByClassName have inconsistencies. The latter returns live HTMLCollections that don't support array methods:
const items = document.getElementsByClassName("item");
items.forEach(item => console.log(item)); // TypeError
The fix: Use querySelector and querySelectorAll. They accept any CSS selector and return consistent, iterable results:
const items = document.querySelectorAll(".item");
items.forEach(item => console.log(item)); // Works perfectly
Building strings with + is verbose and error-prone:
const greeting = "Hello, " + name + ". You are " + age + " years old.";
The fix: Template literals are cleaner and support multiline strings:
const greeting = `Hello, ${name}. You are ${age} years old.`;
Nested callbacks create unreadable code:
getUser(id, function(user) {
getOrders(user.id, function(orders) {
getDetails(orders[0].id, function(details) {
console.log(details);
});
});
});
The fix: async/await flattens the structure and centralizes error handling:
async function getOrderDetails(id) {
try {
const user = await getUser(id);
const orders = await getOrders(user.id);
const details = await getDetails(orders[0].id);
console.log(details);
} catch (error) {
console.error(error);
}
}
The == operator performs type coercion with confusing results:
console.log(0 == "0"); // true
console.log(0 == []); // true
The fix: Always use === and !== to compare both value and type.
The old pattern fails with valid falsy values:
function greet(name) {
name = name || "Guest";
}
greet(""); // Uses "Guest" instead of empty string
The fix: Use default parameters or nullish coalescing:
function greet(name = "Guest") { }
const value = input ?? "default"; // Only defaults for null/undefined
Methods like push, sort, and splice mutate the original array, causing bugs in frameworks like React.
The fix: Create new arrays instead:
const sorted = [...numbers].sort();
const withNewItem = [...numbers, 4];
const filtered = numbers.filter(n => n !== 2);
ES2023 also introduced toSorted(), toReversed(), and toSpliced() as non-mutating alternatives.
Modern JavaScript is more readable and predictable. By adopting these patterns—const/let, template literals, async/await, strict equality, and immutable operations—you'll write code that's easier to understand and maintain.
Take a look at your current projects. How many of these old habits can you spot?
TypeScript isn't a new language—it's JavaScript with guardrails that become essential at scale.
AI changed the developer's role. We no longer write code: we sculpt the raw material it generates.
Object Calisthenics: 9 strict rules to sharpen your object-oriented code through deliberate practice.
Test your HTML, CSS, JavaScript, PHP, SQL skills and more with free quizzes. Perfect for beginners learning to code or developers preparing for interviews.