sample post — this is mock content written to demonstrate the blog layout and typography system. it does not represent original research or the author’s professional views.
TypeScript gets defended badly. Most arguments for it lead with autocomplete and red squiggles in the editor, as if the value proposition is “JavaScript but with better IDE support.” that misses what makes it interesting.
the real case for TypeScript is not the tooling. it is that types are a form of documentation that the compiler enforces.
the problem with implicit contracts
every function you write has a contract — what it expects, what it returns, what it does when things go wrong. in JavaScript, that contract lives in your head, in a comment if you are disciplined, or in a test file if you are diligent. it does not live in the function signature.
// JavaScript — the contract is invisible
function processUser(user) {
return user.name.toLowerCase();
}
// TypeScript — the contract is enforced
function processUser(user: { name: string }): string {
return user.name.toLowerCase();
}
the second version tells you — without reading the body — what goes in and what comes out. more importantly, it tells the compiler, which means that when you call processUser(42) three files away six months from now, you get an error before you ship.
generics are not scary
the thing that trips people up is generics. the angle brackets look like noise, and tutorials introduce them with abstract T and U examples that clarify nothing.
think of a generic as a placeholder that gets filled in at call time. the classic example is a function that returns the first element of an array:
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // n is number | undefined
const s = first(["a", "b"]); // s is string | undefined
T is not magic. it is a variable that holds a type. the function says: “whatever type the array contains, I will return that type or undefined.” the compiler fills in T based on what you pass.
this becomes genuinely useful when you are writing utilities that operate over data structures you do not own:
function groupBy<T, K extends keyof T>(
items: T[],
key: K
): Map<T[K], T[]> {
const map = new Map<T[K], T[]>();
for (const item of items) {
const k = item[key];
const group = map.get(k) ?? [];
group.push(item);
map.set(k, group);
}
return map;
}
now groupBy works over any array, keys are constrained to actual properties of the element type, and the return value is typed correctly. no casting, no any.
utility types do the heavy lifting
the standard library’s utility types are underused. a few that come up constantly:
Partial<T> — makes every property optional. useful for update operations where you want to accept a subset of a record.
Pick<T, K> — extracts specific keys from a type. useful for projection — when you only need name and email from a full user object.
Record<K, V> — a typed object with consistent key and value types. cleaner than { [key: string]: SomeType }.
Readonly<T> — prevents mutation at the type level. useful for config objects and constants that should never be reassigned.
type User = {
id: string;
name: string;
email: string;
role: "admin" | "viewer";
};
// only the fields a form needs
type UserFormData = Pick<User, "name" | "email">;
// config that should not change at runtime
const DEFAULT_CONFIG: Readonly<Record<string, unknown>> = {
timeout: 5000,
retries: 3,
};
the friction is the point
people complain that TypeScript slows them down. this is true — and it is doing its job.
the friction is highest when you are working against the type system: casting to any, using as SomeType to silence an error, writing ! to assert away nullability. when you feel that friction, it usually means the compiler has found a real problem — a shape mismatch you had not noticed, a null case you had not handled.
TypeScript rewards working with it. let inference do the work where it can. narrow types with conditionals rather than assertions. write the types first and let the implementation follow. the friction drops, and what is left is a codebase that is harder to break quietly.
where it is not worth it
TypeScript is not free. it adds a build step, requires configuration, and has a learning curve that is steeper than it appears. for a script that runs once, or a prototype you will throw away, the overhead is not worth the payoff.
the return on investment is highest in codebases that:
- are worked on by more than one person
- outlive the original author’s memory of the design
- have data shapes that change over time
if neither of those apply, plain JavaScript is a legitimate choice. TypeScript is a tool, not a moral position.