DEV Community

Chris
Chris

Posted on

How TypeScript helped with JavaScript gotchas

I was thinking about how TypeScript removed the need to think about "JavaScript gotchas" from years gone by.


Adding numbers instead of combining text

Adding text "5" to number 100 makes "1005".

// ❌ (No warning, charges $1005)
const price = 100;
const tax = "5"; 
const total = price + tax; // "1005"

Enter fullscreen mode Exit fullscreen mode
// ✅ Catches it
const price: number = 100;
const tax: string = "5";
const total = price + tax; 
// 🔴 Error: Cannot apply '+' to 'number' and 'string'.

Enter fullscreen mode Exit fullscreen mode

Calling something that doesn't exist

Running a function that wasn't passed crashes the page.

// ❌ Crashes
function sendNotification(config) {
  config.onSuccess(); // Uncaught TypeError: config.onSuccess is not a function
}
sendNotification({}); 

Enter fullscreen mode Exit fullscreen mode
// ✅ Forces safe checking
type Config = { onSuccess?: () => void };

function sendNotification(config: Config) {
  config.onSuccess(); 
  // 🔴 Error: 'onSuccess' is possibly 'undefined'.

  config.onSuccess?.(); // Safe!
}

Enter fullscreen mode Exit fullscreen mode

Spelling mistakes in property names

A single wrong letter gives undefined instead of a crash, hiding the bug.

// ❌ Quietly fails
const user = { firstName: "Sam" };
console.log(user.firstname); // Output: undefined

Enter fullscreen mode Exit fullscreen mode
// ✅ Spots the typo
const user = { firstName: "Sam" };
console.log(user.firstname); 
// 🔴 Error: Property 'firstname' does not exist on user. Did you mean 'firstName'?

Enter fullscreen mode Exit fullscreen mode

Forgetting required information

Skipping an argument turns math into NaN (Not a Number).

function multiply(a, b) {
  return a * b;
}
multiply(5); // Output: NaN

Enter fullscreen mode Exit fullscreen mode
// ✅ Enforces all parameters
function multiply(a: number, b: number) {
  return a * b;
}
multiply(5); 
// 🔴 Error: Expected 2 arguments, but got 1.

Enter fullscreen mode Exit fullscreen mode

Accidentally changing important data

Any piece of code can accidentally overwrite critical settings.

// ❌ Allows accidental changes
const config = { adminUrl: "https://wordpress.com/admin" };
config.adminUrl = "https://danger.com"; // Changed!

Enter fullscreen mode Exit fullscreen mode
// ✅ Locks data down
type Config = { readonly adminUrl: string };
const config: Config = { adminUrl: "https://site.com/admin" };

config.adminUrl = "https://hacked.com"; 
// 🔴 Error: Cannot assign to 'adminUrl' because it is read-only.

Enter fullscreen mode Exit fullscreen mode

6. Forgetting a scenario in a list

Adding a new state gets ignored if you forget to handle it.

// ❌ Returns nothing for new states
function getStatus(status) {
  if (status === "PAID") return "Green";
  if (status === "PENDING") return "Yellow";
  // Forgot "REFUNDED"! Returns undefined.
}

Enter fullscreen mode Exit fullscreen mode
// ✅ Demands every case is handled
type Status = "PAID" | "PENDING" | "REFUNDED";

function getStatus(status: Status): string {
  if (status === "PAID") return "Green";
  if (status === "PENDING") return "Yellow";
  // 🔴 Error: Function lacks ending return statement. You forgot 'REFUNDED'!
}

Enter fullscreen mode Exit fullscreen mode

The 0 treated as "false" trap

The Problem: JS treats 0 as "empty", so 0 items defaults to 10.

// ❌ 0 triggers the fallback
function setQuantity(quantity) {
  return quantity || 10; 
}
setQuantity(0); // Output: 10 (Wrong!)

Enter fullscreen mode Exit fullscreen mode
// ✅ Pairs with ?? to handle 0 correctly
function setQuantity(quantity?: number) {
  return quantity ?? 10; // Only defaults if null or undefined
}
setQuantity(0); // Output: 0 (Correct!)

Enter fullscreen mode Exit fullscreen mode

Comparing completely unrelated things

JS equality rules lead to absurd true results.

// ❌ Nonsensical comparison passes
if ([] == false) {
  // Runs! An empty array equals false in JS...
}

Enter fullscreen mode Exit fullscreen mode
// ✅ Blocks bad comparisons
if ([] == false) {} 
// 🔴 Error: This comparison is unintentional. Array and boolean have no overlap.

Enter fullscreen mode Exit fullscreen mode

Accidentally ruining original lists

.sort() alters the original array instead of making a new one.

// ❌ Mutates original array silently
function getSorted(items) {
  return items.sort(); 
}
const names = ["Bob", "Alice"];
getSorted(names); // 'names' is now changed to ["Alice", "Bob"]

Enter fullscreen mode Exit fullscreen mode
// ✅ Protects original arrays
function getSorted(items: readonly string[]) {
  return items.sort(); 
  // 🔴 Error: 'sort' does not exist on 'readonly string[]'. Make a copy first!
}

Enter fullscreen mode Exit fullscreen mode

Trusting random data from the internet

Reading missing fields from API data crashes the app.

// ❌ Crashes if structure changes
const data = JSON.parse(apiResponse);
console.log(data.user.name); // Uncaught TypeError: Cannot read property 'name' of undefined

Enter fullscreen mode Exit fullscreen mode
// ✅ Forces you to verify before accessing
const data: unknown = JSON.parse(apiResponse);

// console.log(data.user.name); 🔴 Error: 'data' is of type 'unknown'.

// Must check shape first:
if (data && typeof data === "object" && "user" in data) {
  // Now it's safe to use!
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)