What TypeScript Actually Is
TypeScript is a typed superset of JavaScript developed by Microsoft. That sentence gets repeated everywhere, but let us unpack what it actually means in practice. TypeScript is JavaScript with an additional layer: a type system. Every valid JavaScript program is also a valid TypeScript program. TypeScript adds the ability to annotate variables, function parameters, and return values with types, and a compiler that checks those types before the code runs.
When you write TypeScript, you are still writing JavaScript. The TypeScript compiler (called tsc) strips out all the type annotations and produces plain JavaScript that runs in any browser or Node.js environment. The types exist only at development time. They help you catch mistakes, document your code, and enable better tooling. At runtime, it is all JavaScript.
This distinction matters because it means TypeScript is not a new language you need to learn from scratch. If you know JavaScript, you already know most of TypeScript. The learning curve is in understanding the type system, which is incremental. You can start with basic types and gradually adopt more advanced features as you need them.
The Problem TypeScript Solves
JavaScript is a dynamically typed language. Variables can hold any value at any time. A variable that starts as a number can become a string, then an object, then undefined. JavaScript will not complain until something breaks at runtime, often in production, often at the worst possible moment.
Consider this JavaScript code:
function calculateTotal(items) { return items.reduce((sum, item) => sum + item.price, 0); }
This function works perfectly when you pass it an array of objects with a price property. But what happens when someone passes it a single object instead of an array? Or an array of objects where one item has Price (capital P) instead of price? Or an array that includes a null value? JavaScript will not warn you about any of these scenarios. You will get NaN, a runtime error, or silently wrong results.
TypeScript catches all of these at compile time:
interface CartItem { name: string; price: number; quantity: number; } function calculateTotal(items: CartItem[]): number { return items.reduce((sum, item) => sum + item.price, 0); }
Now the compiler knows exactly what items should look like. Pass a single object? Error. Missing price property? Error. null in the array? Error. You find out immediately, in your editor, before the code ever runs.
Why TypeScript Wins: The Concrete Benefits
Fewer Bugs in Production
A study by researchers at UCL (University College London) found that 15% of JavaScript bugs on GitHub could have been prevented by TypeScript's type system. That is 15% fewer bugs reaching production, 15% fewer customer complaints, 15% fewer emergency fixes at 11 PM on a Friday.
In our experience building web applications for clients in Lugano and across Switzerland, the number is even higher for business applications that handle complex data structures. E-commerce sites, CRM integrations, financial calculators, booking systems: these applications deal with nested objects, optional fields, and data transformations where type errors are both common and costly.
Better IDE Support
This is the benefit that developers feel most immediately. With TypeScript, your code editor (VS Code, WebStorm, etc.) can provide:
- Accurate autocomplete: When you type
user., the editor shows you every property and method available on a user object. Not guesses based on what you have typed elsewhere, but the actual interface definition. - Inline error detection: Errors appear as you type, not after you run the code. Red underlines show type mismatches, missing properties, and incompatible function arguments in real time.
- Go to definition: Click on a type or function name to jump directly to its definition. In a large codebase, this saves enormous amounts of time navigating between files.
- Safe renaming: Rename a property, and the IDE updates every reference across the entire project. With JavaScript, this is a find-and-replace operation that often misses dynamic references.
These are not cosmetic improvements. They translate directly into developer productivity. Studies show that developers spend more time reading and understanding code than writing it. TypeScript makes reading code faster because the types serve as documentation that the compiler enforces.
Easier Refactoring
Refactoring is the process of restructuring code without changing its behavior. In JavaScript, refactoring is terrifying. You rename a property, change a function signature, or restructure a data model, and you have no way to know if you broke something until you run every test and click through every page.
In TypeScript, the compiler tells you immediately. Changed the shape of an API response? Every component that uses that response will show an error if it accesses a property that no longer exists. Renamed a function parameter? Every call site that passes the old parameter name will light up. You can refactor with confidence because the compiler has your back.
This is particularly valuable for long-lived projects. A website you build today will need changes in six months, a year, three years. The developer making those changes (who might not be the original author) needs to understand the data structures and function contracts. TypeScript makes those contracts explicit and machine-verifiable.
Team Collaboration
When multiple developers work on the same codebase, TypeScript acts as a shared contract. Interfaces define the shape of data that flows between modules. Function signatures document what each function expects and returns. These contracts are checked by the compiler, so they cannot become outdated (unlike comments or external documentation, which inevitably drift from the actual code).
In practice, this means:
- A frontend developer can write code against an API response type before the backend is even built. If the backend team changes the response structure, the frontend code will fail to compile, making the breaking change visible immediately.
- New team members can understand the codebase faster. Types answer the question "what does this function expect?" without reading the implementation.
- Code reviews are more effective. Reviewers can focus on logic and architecture instead of asking "what type is this parameter?"
TypeScript vs JavaScript: A Direct Comparison
| Aspect | JavaScript | TypeScript |
|---|---|---|
| Type checking | None (runtime errors) | Compile-time type checking |
| IDE support | Basic (inference-based) | Full (type-aware autocomplete, navigation, refactoring) |
| Error detection | At runtime | At compile time + runtime |
| Documentation | Comments, JSDoc (often outdated) | Types serve as living documentation |
| Refactoring confidence | Low | High (compiler catches breaking changes) |
| Learning curve | Lower entry barrier | Slightly higher (type system) |
| Build step | Optional | Required (tsc compilation) |
| Ecosystem compatibility | Native | Full (via type definitions, @types packages) |
| Runtime overhead | None | None (types are stripped at compilation) |
How Types Catch Errors at Compile Time
Let us walk through specific error categories that TypeScript prevents:
Typos in Property Names
In JavaScript, user.emial returns undefined silently. In TypeScript, it is a compile error: "Property 'emial' does not exist on type 'User'. Did you mean 'email'?" This single feature alone catches dozens of bugs per project.
Wrong Function Arguments
Passing a string where a number is expected, passing three arguments to a function that takes two, or forgetting to pass a required parameter: all of these are compile errors in TypeScript. In JavaScript, they are runtime bugs that may or may not crash visibly.
Null and Undefined Handling
With strictNullChecks enabled, TypeScript forces you to handle cases where a value might be null or undefined. You cannot call .toString() on a value that might be null without first checking. This eliminates the entire class of "Cannot read property of null" errors that plague JavaScript applications.
Exhaustive Switch Statements
When you use a discriminated union type and a switch statement, TypeScript can verify that you handle every possible case. Add a new variant to the union? The compiler immediately flags every switch statement that does not handle the new case. This is invaluable for state machines, event handlers, and any code that branches on a type discriminator.
Getting Started: The Basics
The tsconfig.json File
Every TypeScript project starts with a tsconfig.json file that configures the compiler. A good starting configuration for a new project:
{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./dist" }, "include": ["src"] }
The "strict": true flag is the most significant setting. It enables all strict type checking options, including strictNullChecks, noImplicitAny, and strictFunctionTypes. Start strict. It is much harder to enable strict checks later than to start with them from the beginning.
Basic Types
TypeScript's fundamental types mirror JavaScript's runtime types:
string,number,boolean: the primitivesstring[]orArray<string>: arrays of a specific type{ name: string; age: number }: object typesany: opt out of type checking (avoid this)unknown: the safe alternative toany(requires type narrowing before use)void: functions that do not return a valuenullandundefined: handled explicitly withstrictNullChecks
Interfaces and Type Aliases
Interfaces define the shape of objects. They are the backbone of TypeScript's type system for application development:
interface User { id: number; name: string; email: string; role: 'admin' | 'editor' | 'viewer'; createdAt: Date; }
Type aliases can define the same shapes and more (union types, intersection types, utility types):
type UserRole = 'admin' | 'editor' | 'viewer'; type ApiResponse<T> = { data: T; status: number; message: string; };
Generics
Generics let you write reusable code that works with multiple types while maintaining type safety. Think of them as type parameters:
function getFirst<T>(items: T[]): T | undefined { return items[0]; }
This function works with arrays of any type and returns the same type. getFirst([1, 2, 3]) returns number | undefined. getFirst(["a", "b"]) returns string | undefined. The type flows through automatically.
Frameworks Using TypeScript
TypeScript is not a fringe choice. It has become the default for the major frameworks in web development:
- Angular: Written in TypeScript from version 2 onward. Angular requires TypeScript.
- Next.js: First-class TypeScript support. New projects are created with TypeScript by default.
- Astro: Built with TypeScript, full TypeScript support for project development. This is the framework we use at Envestis for many of our projects.
- Deno: The runtime created by Node.js's original author. Supports TypeScript natively without a separate compilation step.
- Svelte and SvelteKit: Full TypeScript support in components and routing.
- Vue 3: Rewritten in TypeScript, with significantly improved TypeScript support in the Composition API.
- NestJS: The leading Node.js backend framework, built entirely in TypeScript.
The trend is clear. Choosing JavaScript over TypeScript for a new project in 2021 means swimming against the current of the entire ecosystem.
When NOT to Use TypeScript
TypeScript is not the right choice for every situation. Here are legitimate cases where plain JavaScript makes more sense:
- Quick prototypes and throwaway scripts: If you are writing a one-off data migration script or a prototype that will be rewritten, the setup overhead of TypeScript is not worth it.
- Very small projects: A single-file utility script does not benefit much from a type system. The cognitive overhead exceeds the safety benefits.
- Teams with no TypeScript experience and tight deadlines: If your team has never used TypeScript and you have a hard launch deadline next month, now is not the time to introduce it. Learn it on a side project first.
- Heavily dynamic code: Some patterns in JavaScript (dynamic property access, runtime metaprogramming, proxy-heavy architectures) are difficult to type correctly. You end up fighting the type system more than benefiting from it.
For any project that will be maintained for more than a few months, worked on by more than one developer, or handles complex business logic, TypeScript is the better choice.
Migrating from JavaScript to TypeScript
You do not have to rewrite your entire project. TypeScript supports incremental adoption. Here is a practical migration strategy:
Phase 1: Add TypeScript to the Project
- Install TypeScript:
npm install --save-dev typescript - Create a
tsconfig.jsonwith"allowJs": trueand"strict": false - Rename one file from
.jsto.tsand fix any errors - Verify that the project still builds and runs correctly
Phase 2: Gradual Migration
- Rename files from
.jsto.tsone at a time, starting with utility modules and shared types - Add type annotations to function signatures and exported values
- Create interfaces for your core data models (User, Product, Order, etc.)
- Install
@typespackages for your dependencies (e.g.,@types/express,@types/react)
Phase 3: Enable Strict Mode
- Once most files are converted, enable strict checks one at a time
- Start with
"noImplicitAny": true(forces you to type everything explicitly) - Then
"strictNullChecks": true(forces null/undefined handling) - Work toward full
"strict": true
This approach lets you adopt TypeScript without stopping feature development. Each file you convert becomes safer, and the type definitions you create benefit even the unconverted JavaScript files through IDE inference.
Real Productivity Gains
The arguments for TypeScript are not theoretical. Here are the productivity improvements we have seen in our own projects and client work:
- 50-70% fewer type-related bugs: Bugs like wrong property names, null reference errors, and type coercion surprises are caught before code reaches testing.
- Faster onboarding: New developers on a project understand data structures and function contracts through types, reducing the time to productive contribution.
- Confident refactoring: Large-scale refactors that would take days of manual testing in JavaScript take hours with TypeScript because the compiler identifies every affected location.
- Better API integration: When consuming third-party APIs, type definitions make it clear exactly what data is available and in what format. No more guessing or console.log debugging.
The initial investment in learning and setting up TypeScript pays for itself within weeks on any non-trivial project. For web development projects in Lugano and across Switzerland, where development budgets are real and wasted time costs money, TypeScript is one of the highest-return investments a team can make.
Getting Help
If you are considering TypeScript for a new project or migrating an existing one, and you want guidance from a team that uses it daily, reach out to us. We build web applications with TypeScript for businesses across Ticino and Switzerland, and we are happy to share what we have learned.
Want to know if your site is secure?
Request a free security audit. In 48 hours you get a complete report.
Request Free Audit