Sitemap
JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.

Why TypeScript Made Me Rewrite My Favorite Utility Functions

3 min readJun 30, 2025

--

Press enter or click to view image in full size
Why TypeScript Made Me Rewrite My Favorite Utility Functions
Why TypeScript Made Me Rewrite My Favorite Utility Functions

Like most developers, I’ve been collecting utility functions for years — little helpers for cloning objects, checking types, validating inputs, and cleaning up code.

I trusted them. I reused them. I shipped them in production.

Then I started using TypeScript seriously.

And suddenly, my beloved helpers started falling apart.

This post is about how TypeScript didn’t just improve my JavaScript — it challenged it. It made me rewrite some of my most “reliable” code.

🧪 Utility #1: deepClone()

Old version:

function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}

It worked… until it didn’t:

  • Lost dates
  • Dropped undefined values
  • Couldn’t clone functions

TypeScript flagged it:

function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj)); // ⚠️ Not safe for all types
}

Even though we typed it as generic T, TypeScript couldn’t guarantee that the returned value had all the right nested types preserved.

Rewrite with structured clone:

function deepClone<T>(value: T): T {
return structuredClone(value); // ✅ built-in, type-safe
}

Now it’s native, cleaner, and keeps the type integrity — no weird surprises with dates or nested objects.

🤷‍♂️ Utility #2: isEmpty()

Old version:

function isEmpty(value) {
return (
value == null ||
(typeof value === 'string' && value.trim() === '') ||
(Array.isArray(value) && value.length === 0)
);
}

Works in JS. But:

  • null and undefined are ambiguous
  • It accepts any input — no clarity
  • Hard to trust in large apps

TypeScript rewrite:

function isEmpty(value: string | any[] | null | undefined): boolean {
if (value == null) return true;
if (typeof value === 'string') return value.trim() === '';
if (Array.isArray(value)) return value.length === 0;
return false;
}

Now:

  • Types are clear
  • Logic is predictable
  • Safer in large codebases

Bonus: TypeScript warns me if I try to pass an unsupported type like an object or number.

💡 Utility #3: toTitleCase()

Old version:

function toTitleCase(str) {
return str.replace(/\b\w/g, c => c.toUpperCase());
}

No checks, no types, and breaks if null is passed.

Get Kavindu Kokila (Kavi Castelo)’s stories in your inbox

Join Medium for free to get updates from this writer.

TypeScript rewrite:

function toTitleCase(str: string): string {
return str.replace(/\b\w/g, (c) => c.toUpperCase());
}

Now it only accepts a valid string. Any mistake is caught before runtime.

🛑 Utility #4: getProp(obj, path)

Old version:

function getProp(obj, path) {
return path.split('.').reduce((acc, key) => acc?.[key], obj);
}

It works… but with no guarantees:

  • Path can be anything
  • Return value could be undefined, null, or an error
  • No type safety

TypeScript rewrite:

function getProp<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

If you’re using dot-paths, it gets trickier — but even a basic keyof rewrite is miles safer than the untyped dynamic string version.

🧪 Utility #5: mergeDefaults()

Old version:

function mergeDefaults(obj, defaults) {
return { ...defaults, ...obj };
}

This was fine for shallow merges. But it had problems:

  • Didn’t alert on type mismatches
  • Could silently overwrite values

TypeScript rewrite:

function mergeDefaults<T extends object, U extends Partial<T>>(obj: U, defaults: T): T {
return { ...defaults, ...obj };
}

Now TypeScript checks:

  • That obj is assignable to T
  • That you’re not introducing invalid keys

📦 What I Learned

TypeScript made me face a hard truth: most of my JS utilities were based on assumptions. Implicit conversions, loose types, silent failures — all hidden under the surface.

When I added TypeScript:

  • Some of them broke
  • Others revealed edge cases
  • A few became unnecessary (because TypeScript solved the problem by design)

But in the process, I wrote better code. More explicit, more testable, more trustworthy.

🧠 Takeaway

You don’t need to rewrite everything. But try running your old utility file through the TypeScript compiler — and see what survives 😅

If you care about long-term maintainability, clarity, and scale — TypeScript isn’t just a tool.

It’s a second brain.

👉 Got a favorite utility TypeScript made you rethink? Drop it in the comments or tweet me your story.

GitHub | LinkedIn

Thank you for being a part of the community

Before you go:

--

--