Why I Finally Made the Jump to TypeScript

I have a confession. I spent two years telling myself I did not need TypeScript. "I know JavaScript well enough," I thought. "Types will just slow me down." I was the person who rolled their eyes at type annotations and said things like "just write better tests."
Then I spent an entire afternoon debugging a bug that turned out to be passing a string where a number was expected. A type checker would have caught that in zero seconds. That was my conversion moment.
Getting Started Without Pain
The thing that finally got me over the hump was learning that you do not have to convert your entire project at once. TypeScript supports gradual adoption. You can rename a file from .js to .ts, fix the type errors that show up, and leave the rest of your codebase untouched.
My tsconfig started minimal:
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"strict": false,
"esModuleInterop": true,
"allowJs": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
Notice strict: false and allowJs: true. This lets JavaScript and TypeScript files coexist. You can convert files one at a time without breaking anything. Once I had most files converted, I turned on strict: true and fixed the remaining issues. It took about a week for a medium-sized project.
Type Inference Does the Heavy Lifting
The biggest misconception I had was thinking TypeScript meant annotating every single variable. In practice, the type inference engine is smart enough to figure out most types on its own.
// You do NOT need to write this:
const name: string = 'Bryan';
const count: number = 42;
// TypeScript already knows:
const name = 'Bryan'; // inferred as string
const count = 42; // inferred as number
const users = []; // inferred as never[] (you want to annotate this one)
// Where annotations help:
function getUser(id: string): Promise<User | null> {
return db.users.findById(id);
}
// Function parameters need types, return types are often inferred
function add(a: number, b: number) {
return a + b; // return type inferred as number
}
The places where you actually need to write type annotations are function parameters, empty arrays, and complex return types. Everything else, TypeScript figures out. This means the actual amount of extra typing (pun intended) is much less than I feared.
Interfaces vs Type Aliases
This confused me at first because they look so similar. Both can define the shape of an object. The practical difference in most cases is small, but here is how I think about it.
// Interface: best for objects and classes
interface User {
id: string;
name: string;
email: string;
}
// Type: best for unions, intersections, and computed types
type Status = 'active' | 'inactive' | 'suspended';
type UserWithPosts = User & { posts: Post[] };
Interfaces can be extended and merged (declaration merging). Types are more flexible for complex type operations. For simple object shapes, I use interfaces. For everything else, I use types. Some people pick one and use it exclusively, and honestly that works fine too.
What Strict Mode Catches
When I finally turned on strict: true, I found real bugs. Not hypothetical bugs. Actual bugs that had been in my code for months.
Strict mode enables several checks at once. The ones that caught the most issues for me were strictNullChecks (forces you to handle null and undefined) and noImplicitAny (prevents accidentally untyped values).
// Without strictNullChecks, this compiles fine:
function getUser(id: string): User {
return users.find(u => u.id === id); // might be undefined!
}
// With strictNullChecks, the compiler tells you:
// Type 'User | undefined' is not assignable to type 'User'
// You're forced to handle it:
function getUser(id: string): User | undefined {
return users.find(u => u.id === id);
}
// And callers must check:
const user = getUser('123');
if (user) {
console.log(user.name); // safe
}
Every one of those checks prevented a potential runtime error. The find function can return undefined. Without TypeScript, I would have accessed user.name without checking and gotten a "Cannot read property of undefined" error at runtime.
The IDE Experience
This might be the single biggest quality-of-life improvement. VS Code with TypeScript is incredible. You get autocomplete that actually knows what properties an object has. You get inline documentation. You get "Go to Definition" that works perfectly. You get rename refactoring that updates every reference across your entire project.
Before TypeScript, I spent a lot of time reading source code to figure out what shape an API response had, or what arguments a function expected. Now the editor just tells me. It sounds small, but it adds up to hours saved every week.
My Advice for Switching
Start with strict: false and allowJs: true. Convert files one at a time, starting with utility functions and data models. Do not try to make everything perfectly typed on day one. Use any as a temporary escape hatch when something is too complex to type right away, then come back and fix it later.
Once you have most of your codebase converted, turn on strict mode. Fix the errors it finds. Many of them will be real bugs.
Two years of resistance, and now I cannot imagine starting a project without TypeScript. The upfront cost is small, and it pays for itself almost immediately.