Why We Resisted — and Why We Were Wrong

We were TypeScript skeptics. When we started StrikingWeb in 2018, we believed that TypeScript added unnecessary complexity to JavaScript projects. The type annotations felt verbose, the compilation step seemed like overhead, and we were productive enough with JavaScript and good testing practices.

We were wrong. After gradually introducing TypeScript into our codebase throughout 2020 and fully committing to it for all new projects in 2021, we can confidently say that TypeScript is the single most impactful improvement we have made to our development process. Here is why, and how you can make the transition.

What TypeScript Actually Is

TypeScript is a superset of JavaScript that adds optional static typing. Every valid JavaScript program is also a valid TypeScript program, which means you can adopt TypeScript incrementally without rewriting your existing code. TypeScript compiles to plain JavaScript, and the type annotations are stripped away during compilation — they exist only for development-time tooling and error checking.

// JavaScript
function calculateTotal(items, taxRate) {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0) * (1 + taxRate);
}

// TypeScript
interface LineItem {
  name: string;
  price: number;
  quantity: number;
}

function calculateTotal(items: LineItem[], taxRate: number): number {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0) * (1 + taxRate);
}

The TypeScript version is slightly longer, but it communicates far more information. Anyone reading this function immediately knows exactly what data it expects and what it returns. The compiler will catch errors if you pass the wrong data types, before the code ever runs.

The Benefits That Changed Our Mind

Catching Bugs Before They Ship

The most obvious benefit of TypeScript is catching type-related errors at compile time rather than at runtime. In JavaScript, passing a string where a number is expected, accessing a property that does not exist, or calling a function with the wrong number of arguments are all silent errors that only surface when the code runs — often in production.

// This JavaScript code silently produces a bug
const user = { name: 'Alice', age: 30 };
console.log(user.email.toLowerCase());
// Runtime error: Cannot read property 'toLowerCase' of undefined

// TypeScript catches this immediately
interface User {
  name: string;
  age: number;
  email?: string; // optional property
}
const user: User = { name: 'Alice', age: 30 };
console.log(user.email.toLowerCase());
// Compile error: Object is possibly 'undefined'

In our experience, TypeScript catches approximately 15 to 20 percent of the bugs that would otherwise make it past code review. These are not hypothetical savings — they are real bugs that we would have had to debug, fix, test, and deploy patches for.

Superior IDE Support

TypeScript transforms your IDE from a text editor into an intelligent development environment. With type information, your editor can provide accurate autocompletion for every object, function, and module. You can hover over any variable to see its type. Refactoring tools become reliable because the compiler understands the relationships between your code.

Self-Documenting Code

Type annotations serve as documentation that the compiler enforces. Unlike comments, which can become outdated, types are always accurate because the code will not compile if they are wrong. When a new developer joins a project, they can understand the data model by reading the type definitions rather than tracing through runtime behavior.

// Types document the API contract
interface CreateOrderRequest {
  customerId: string;
  items: Array<{
    productId: string;
    quantity: number;
    priceOverride?: number;
  }>;
  shippingAddress: Address;
  couponCode?: string;
  notes?: string;
}

interface CreateOrderResponse {
  orderId: string;
  total: number;
  estimatedDelivery: Date;
  status: 'pending' | 'confirmed' | 'processing';
}

This interface definition tells you everything you need to know about the order creation API: what fields are required, what is optional, what data types are expected, and what values are valid for the status field. No separate documentation needed.

Confident Refactoring

Refactoring JavaScript codebases is nerve-wracking. Without type information, you cannot be sure that changing a function signature, renaming a property, or restructuring a module will not break something in a distant part of the codebase. With TypeScript, the compiler acts as a safety net. If a refactoring introduces a type error anywhere in the project, you know immediately.

This confidence in refactoring has a compounding effect on code quality. When refactoring is safe, developers do it more often. When developers refactor regularly, technical debt stays manageable. When technical debt is manageable, the team moves faster. TypeScript creates a virtuous cycle of code quality improvement.

How to Migrate Gradually

You do not need to rewrite your entire codebase to start using TypeScript. The migration can be done incrementally, file by file, without disrupting ongoing development.

Step 1: Add TypeScript to Your Project

npm install --save-dev typescript @types/node
npx tsc --init

Configure tsconfig.json with relaxed settings to start. Set allowJs: true to allow JavaScript files alongside TypeScript, and set strict: false initially to avoid being overwhelmed by type errors in existing code.

Step 2: Rename Files Gradually

Start by renaming .js files to .ts (or .jsx to .tsx for React components). Because TypeScript is a superset of JavaScript, most files will work without modification. Fix any type errors that the compiler flags, and add type annotations where the benefit is clearest — function parameters, return types, and complex data structures.

Step 3: Increase Strictness Over Time

As your team gains confidence, progressively enable stricter compiler options. The most impactful options to enable are:

Eventually, enable strict: true to get the full benefit of TypeScript's type system. This is the setting we use on all new projects.

Common Objections — and Our Responses

"TypeScript is too verbose"

TypeScript's type inference is excellent. In practice, you need far fewer explicit type annotations than you might expect. TypeScript infers types from assignments, return values, and context, so the additional code is often minimal. The verbosity you do add pays for itself many times over in reduced debugging time.

"Our tests catch type errors"

Tests and types are complementary, not competing approaches. Types catch structural errors (wrong data types, missing properties, incorrect function signatures) instantly and exhaustively. Tests verify business logic and behavior. TypeScript reduces the number of tests you need to write by eliminating an entire class of bugs, but it does not replace testing for application logic.

"The learning curve is too steep"

If you know JavaScript, you already know most of TypeScript. The basic type annotations (string, number, boolean, arrays, objects) take minutes to learn. Advanced features like generics, mapped types, and conditional types can be learned gradually as you need them. Most developers are productive with TypeScript within a week.

TypeScript does not make you write more code — it makes you write better code. The time spent on type annotations is a fraction of the time saved on debugging, documentation, and onboarding.

Our Recommendation

If you are starting a new JavaScript project in 2021, use TypeScript from day one. If you have an existing JavaScript codebase, begin migrating incrementally. The investment pays dividends immediately through better tooling and catches bugs that your tests and code reviews would miss. At StrikingWeb, TypeScript is now a non-negotiable part of our technology stack, and we have never looked back.

Share: