1.1 What is TypeScript?
When building large-scale applications with JavaScript, certain categories of bugs tend to appear repeatedly. Passing a string where a number is expected, accessing a property that doesn't exist, or using undefined as if it were a number — these mistakes can't be caught until the code actually runs, and the larger a project grows, the harder they become to track down. TypeScript was created specifically to solve this problem.
Defining TypeScript
TypeScript is an open-source programming language released by Microsoft in 2012. Its chief architect is Anders Hejlsberg, the creator of C#. TypeScript's official definition is "a superset of JavaScript."
A superset is a language that includes all the syntax of an existing language while adding extra features on top. In other words, every valid JavaScript program is also a valid TypeScript program. You can rename any .js file to .ts and it will compile without errors. This property allows TypeScript to be adopted incrementally in existing JavaScript projects.
The core feature TypeScript adds on top of JavaScript is a Static Type System.
Dynamic Typing vs Static Typing
JavaScript is a dynamically typed language. The type of a variable is determined at the moment the code runs (runtime).
// JavaScript — types are determined at runtime
let value = 42; // number
value = "hello"; // can change to string — no error
value = true; // can change to boolean — no error
TypeScript is a statically typed language. The type of a variable is determined at the moment the code is written (compile time).
// TypeScript — types are determined at compile time
let value: number = 42;
value = "hello"; // Error: Type 'string' is not assignable to type 'number'
value = true; // Error: Type 'boolean' is not assignable to type 'number'
The error appears in your editor the moment you type the code — before you ever run it. That is the essence of a static type system.
The Origins of TypeScript
Back in 2012, Microsoft was developing large-scale JavaScript applications internally. Working with hundreds of thousands of lines of JavaScript across teams of hundreds of developers surfaced serious problems.
Problem 1: No code navigation When modifying a function, it was difficult to understand how it was used across the rest of the project. Renaming a parameter or changing a type made it impossible to know what effects would ripple through other files.
Problem 2: Dangerous refactoring Renaming a property on an object required manually hunting down every piece of code that referenced it. Miss even one and the bug wouldn't surface until runtime.
Problem 3: No IDE support Without type information, IDEs had no way of knowing "what methods does this variable have?" Autocomplete simply didn't work properly.
Problem 4: Implicit type coercion
Operations like 5 + "3" silently return "53" without any error. Unintended type conversions happening quietly in the background caused hard-to-find bugs.
Anders Hejlsberg's team designed a language that adds type information to JavaScript to solve these problems — and that language is TypeScript.
The Compilation Pipeline and Type Erasure
TypeScript files (.ts) cannot be run directly in a browser or Node.js. They must first be converted to JavaScript files (.js) via the TypeScript compiler (tsc).
Write .ts file
│
▼
tsc (TypeScript Compiler)
├─ Type Checking
│ └─ Prints error messages when issues are found
└─ Transpilation
└─ Removes type annotations
└─ Produces .js file
│
▼
.js file (pure JavaScript)
│
▼
Runs in Node.js / browser
There is one important concept here: Type Erasure.
TypeScript's type information is completely removed after compilation. The final .js file contains no type annotations whatsoever.
// TypeScript source code (hello.ts)
function greet(name: string): string {
return `Hello, ${name}!`;
}
const user: string = "Alice";
console.log(greet(user));
// Compiled JavaScript output (hello.js)
function greet(name) {
return `Hello, ${name}!`;
}
const user = "Alice";
console.log(greet(user));
All type annotations like : string and : number are gone. Type information exists only during development; at runtime there is no trace of it.
Real JavaScript Bugs That TypeScript Catches
These are the kinds of errors every JavaScript developer has encountered at some point.
undefined is not a function
// JavaScript — error occurs at runtime
const user = {
name: "Alice",
getAge: function() { return 30; }
};
user.getname(); // TypeError: user.getname is not a function
// You have to run the code to discover the error
// TypeScript — error detected the moment you type it
const user = {
name: "Alice",
getAge: function(): number { return 30; }
};
user.getname();
// Error: Property 'getname' does not exist on type '...'
// Did you mean to call 'getAge'?
Cannot read properties of undefined
// JavaScript — accessing a property missing from an API response
function displayUserCity(user) {
console.log(user.address.city); // runtime error if user.address is undefined
}
displayUserCity({ name: "Bob" }); // TypeError thrown
// TypeScript — error caught before you even access it
interface User {
name: string;
address?: {
city: string;
};
}
function displayUserCity(user: User): void {
console.log(user.address.city);
// Error: Object is possibly 'undefined'
// Correct code: console.log(user.address?.city);
}
Unintended type coercion
// JavaScript — silently returns a wrong result
function calculateTotal(price, quantity) {
return price * quantity;
}
const total = calculateTotal("100", 3); // Returns 300, not "100100100" (string * number = number)
// But what if price is "100abc"? Returns NaN
// A common bug when passing form input values directly
// TypeScript — blocks incorrect input at compile time
function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}
const total = calculateTotal("100", 3);
// Error: Argument of type 'string' is not assignable to parameter of type 'number'
The Practical Benefits of a Static Type System
Compile-time error detection
Bugs that could only be found at runtime are now caught at the moment the code is written. This prevents users from ever encountering those errors after deployment.
Improved IDE support
With type information, the IDE can fully understand your code. As a result:
- Autocomplete (IntelliSense): Automatically suggests object properties, function parameters, and return types
- Instant error highlighting: Displays errors with red underlines before you even save the file
- Safe refactoring: Renaming variables or functions safely updates every reference in one operation
- Go to Definition:
Ctrl+Clickjumps directly to where a function or type is defined - Find All References: Locates every place a function or type is used
Safe refactoring
When changing a function signature in a large codebase, TypeScript immediately highlights compatibility errors everywhere that function is used. You can see the full blast radius of a change and make it safely.
Code as documentation
Type annotations act as documentation in their own right.
// Without types — what does this function accept and return?
function processOrder(order, options) { ... }
// With types — immediately clear at a glance
function processOrder(order: Order, options: ProcessOptions): Promise<OrderResult> { ... }
JS vs TS Comparison
| Aspect | JavaScript | TypeScript |
|---|---|---|
| When types are determined | Runtime | Compile time |
| When errors are found | After execution | While writing / compiling |
| IDE autocomplete | Limited | Full support |
| Refactoring safety | Low | High |
| Maintainability | Difficult at scale | Well suited for large projects |
| Learning curve | Low | Moderate |
| Execution | Runs directly | Compiled by tsc first |
| File extension | .js | .ts, .tsx |
| Existing JS code | — | Works as-is |
Practical Example: JS Bugs That TS Catches Side by Side
Let's compare scenarios that commonly occur in real codebases.
Scenario: E-commerce discount calculator
// JavaScript version — there's a bug hiding here
function applyDiscount(price, discountPercent) {
return price - (price * discountPercent / 100);
}
// Developer accidentally passes a string
const itemPrice = "50000"; // value pulled from a form input
const discount = 10;
const finalPrice = applyDiscount(itemPrice, discount);
console.log(finalPrice); // 45000 — happens to be correct (string * number = number)
// But what about this case?
const finalPrice2 = applyDiscount("$500", 10);
console.log(finalPrice2); // NaN — only discovered at runtime
// TypeScript version — catches the problem at compile time
function applyDiscount(price: number, discountPercent: number): number {
return price - (price * discountPercent / 100);
}
const itemPrice = "50000"; // string from a form field
const discount = 10;
const finalPrice = applyDiscount(itemPrice, discount);
// Error: Argument of type 'string' is not assignable to parameter of type 'number'
// Compilation fails — the bug must be fixed before deployment
// Correct usage
const finalPrice2 = applyDiscount(Number(itemPrice), discount);
console.log(finalPrice2); // 45000
Scenario: Handling an API response
// JavaScript version
async function fetchUserProfile(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// You can access fields immediately without knowing the shape, but...
return `${data.firstName} ${data.lastName}`;
// What if the actual API returns first_name/last_name instead of firstName/lastName?
// "undefined undefined" — only discovered at runtime
}
// TypeScript version
interface UserProfile {
id: number;
firstName: string;
lastName: string;
email: string;
}
async function fetchUserProfile(userId: number): Promise<string> {
const response = await fetch(`/api/users/${userId}`);
const data: UserProfile = await response.json();
// The IDE knows exactly what properties data has and autocompletes them
return `${data.firstName} ${data.lastName}`;
// If you try to access first_name instead?
// Error: Property 'first_name' does not exist on type 'UserProfile'
}
The TypeScript Ecosystem and Adoption
TypeScript has become the de facto standard language in the JavaScript ecosystem.
Major frameworks and libraries
Almost every major JavaScript framework is written in TypeScript or officially supports TypeScript.
| Tool / Framework | TypeScript support level |
|---|---|
| Angular | TypeScript-only (designed with TS from the start) |
| React | .tsx files, official type definitions provided |
| Vue 3 | Rewritten in TypeScript, optimized for Composition API |
| Next.js | TypeScript configuration provided by default |
| NestJS | TypeScript-only Node.js backend framework |
| Deno | TypeScript as a first-class language |
| Bun | Runs TypeScript files natively |
DefinitelyTyped — Adding types to JavaScript libraries
Even JavaScript libraries not written in TypeScript can be used in a type-safe way. The DefinitelyTyped project provides type definitions (.d.ts files) for thousands of libraries, published as @types/xxx packages.
npm install --save-dev @types/node # Node.js built-in module types
npm install --save-dev @types/express # Express.js types
npm install --save-dev @types/lodash # lodash types
npm install --save-dev @types/jest # Jest testing framework types
Newer libraries ship their own type definitions. Libraries like axios, zod, and prisma support TypeScript out of the box without needing a separate @types package.
Type declaration files (.d.ts)
A .d.ts file contains only type definitions — no implementation code. TypeScript uses them to understand the API of JavaScript libraries.
// Excerpt from node_modules/@types/node/index.d.ts (illustrative)
declare module 'path' {
function join(...paths: string[]): string;
function resolve(...paths: string[]): string;
const sep: string;
}
Because of this file, after import path from 'path', calling path.join gives you full autocomplete and type checking.
TypeScript Version History
TypeScript has steadily evolved since its initial release in 2012. Key milestones:
| Version | Year | Key features |
|---|---|---|
| 1.0 | 2014 | Official GA release |
| 2.0 | 2016 | strictNullChecks, Tagged Unions |
| 3.0 | 2018 | Project References, Tuples improvements |
| 4.0 | 2020 | Variadic Tuples, unknown in catch clauses |
| 4.5 | 2021 | Awaited<T>, import type modifiers |
| 5.0 | 2023 | Decorator standardization, const type parameters |
| 5.4 | 2024 | NoInfer<T>, closure type narrowing improvements |
| 5.5 | 2024 | Inferred type predicates, regex syntax checking |
The TypeScript 5.x series focuses on performance improvements and more precise type inference rather than sweeping new features.
Pro Tips
TypeScript has no effect at runtime
TypeScript's type system is purely a development tool. Since all type information is stripped out during compilation, the final JavaScript has no trace of types — there is zero impact on runtime performance and bundle size does not increase.
Type annotations are not "code that executes" — they are a contract between the developer and the compiler.
Types are just a development aid
Even when TypeScript finds type errors, compilation generally continues and a JavaScript file is still produced. (Setting noEmitOnError: true prevents output when errors exist.) Code with type errors can still run.
This is extremely useful when incrementally migrating a JavaScript project to TypeScript.
You can add types to any JS library
Existing JavaScript libraries can still be used from TypeScript. The DefinitelyTyped project (@types/xxx packages) provides type definitions for thousands of JavaScript libraries.
npm install --save-dev @types/node # Node.js type definitions
npm install --save-dev @types/lodash # lodash type definitions
TypeScript is a "type eraser" language
TypeScript = JavaScript + type annotations → compile → JavaScript (types removed)
Once you understand this, there's nothing to fear about TypeScript. Learning TypeScript means learning how to annotate the JavaScript you already know with types. If you already know JavaScript, you already know 80% of TypeScript syntax.
Summary
| Concept | Description |
|---|---|
| TypeScript | A superset of JavaScript with a static type system |
| Superset | All JS code is valid TS code |
| Static typing | Types are determined and checked at compile time |
| tsc | The TypeScript compiler — transforms .ts to .js |
| Type erasure | Type information is removed after compilation |
| Runtime impact | None — types are purely a development tool |
| Origin | Microsoft, 2012, Anders Hejlsberg |
| Purpose | Improve stability and maintainability of large-scale JS projects |
The next chapter covers how to install TypeScript and set up a development environment to start using it.