What Is TypeScript?
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It was developed by Microsoft and is maintained as open-source software. In one sentence:
TypeScript is JavaScript with optional type annotations and a compiler that checks them.
This page explains what that means in depth — how the type system works, what happens during
compilation, what .d.ts files are, and how TypeScript enhances your development environment.
TypeScript Is JavaScript — Plus Types
Every JavaScript file is already a valid TypeScript file. TypeScript adds a single layer on top of JavaScript: type annotations. These annotations describe what kind of value a variable, parameter, or function return can hold.
// Plain JavaScript — works in TypeScript with zero changes
function add(a, b) {
return a + b;
}
// TypeScript — same function, with type annotations added
function add(a: number, b: number): number {
return a + b;
}
// The type annotations are the only difference.
// Both compile to exactly the same JavaScript.How .ts Files Become .js Files
The TypeScript compiler (tsc) performs two distinct tasks in sequence:
- Type checking — reads all your
.tsfiles, analyzes every type annotation and inferred type, and reports any inconsistencies as errors. - Transpilation — strips all type annotations and emits plain
.jsfiles that can run in any JavaScript engine.
These two steps are independent. By default, tsc does both. Some setups (like Babel,
esbuild, or SWC) perform only transpilation (step 2) — they strip types without type-checking.
That is faster but means you lose the error-reporting benefit unless you also run tsc --noEmit
separately.
// src/greet.ts — TypeScript source
interface Person {
name: string;
age: number;
}
function greet(person: Person): string {
return `Hello, ${person.name}! You are ${person.age} years old.`;
}
const alice: Person = { name: 'Alice', age: 30 };
console.log(greet(alice));// dist/greet.js — emitted by tsc (targeting ES2020)
function greet(person) {
return `Hello, ${person.name}! You are ${person.age} years old.`;
}
const alice = { name: 'Alice', age: 30 };
console.log(greet(alice));Notice what was removed: the interface Person declaration is gone entirely (interfaces
have no runtime representation), and the type annotations : Person, : string, : number
are all stripped. The runtime behaviour is identical.
The TypeScript Type System
TypeScript uses a structural type system (also called "duck typing"). Two types are compatible if they have the same shape — the names do not matter.
This is in contrast to nominal type systems (like Java or C#), where two types are
only compatible if they are explicitly declared to be related via extends or implements.
// Structural typing — compatible because the shapes match
interface Cat {
name: string;
meow(): void;
}
interface Animal {
name: string;
}
function printName(a: Animal) {
console.log(a.name);
}
const kitty: Cat = { name: 'Whiskers', meow() {} };
// This works — Cat has at least all the properties of Animal
printName(kitty); // ✓
// In a nominal system (Java/C#), Cat would need to explicitly
// implement Animal for this to compile. Not in TypeScript.Structural Typing in Practice
Here is a more concrete example that shows why structural typing matters in daily TypeScript work:
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
function distance(a: Point2D, b: Point2D): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
const p2: Point2D = { x: 0, y: 0 };
const p3: Point3D = { x: 3, y: 4, z: 10 };
// Point3D has x and y (and more), so it satisfies Point2D
distance(p2, p3); // ✓ — TypeScript is fine with this
// This would fail in a nominal system because Point3D does not
// explicitly extend Point2D. TypeScript cares about the shape.Type Inference
You do not need to annotate every variable. TypeScript infers types from context. When the type can be determined from the initial value or the surrounding code, TypeScript figures it out automatically:
// TypeScript infers all of these types without any annotations:
const name = 'Alice'; // inferred: string
const age = 30; // inferred: number
const active = true; // inferred: boolean
const scores = [90, 85, 92]; // inferred: number[]
// Inferred from function return:
function double(n: number) {
return n * 2; // return type inferred: number
}
// Inferred from array methods:
const doubled = scores.map(n => n * 2); // inferred: number[]
// Hover over any variable in VS Code to see its inferred type.Type Annotations in Detail
When you do need to annotate, TypeScript uses the colon syntax:
// Variable annotations
let count: number = 0;
let message: string;
let flags: boolean[];
// Function parameter and return type annotations
function formatPrice(amount: number, currency: string): string {
return `${currency}${amount.toFixed(2)}`;
}
// Object type annotation (inline)
function printUser(user: { name: string; email: string }): void {
console.log(`${user.name} <${user.email}>`);
}
// Arrow function annotations
const multiply = (a: number, b: number): number => a * b;
// Optional parameters (the ? makes the parameter optional)
function greet(name: string, title?: string): string {
return title ? `Hello, ${title} ${name}` : `Hello, ${name}`;
}What TypeScript Looks Like vs JavaScript
Here is the same non-trivial program written in JavaScript and TypeScript, side by side:
// JavaScript version — no types
function processOrder(order) {
if (!order || !order.items) {
throw new Error('Invalid order');
}
const total = order.items.reduce((sum, item) => {
return sum + item.price * item.quantity;
}, 0);
return {
id: order.id,
total,
itemCount: order.items.length,
};
}// TypeScript version — with types
interface OrderItem {
name: string;
price: number;
quantity: number;
}
interface Order {
id: string;
items: OrderItem[];
}
interface OrderSummary {
id: string;
total: number;
itemCount: number;
}
function processOrder(order: Order): OrderSummary {
const total = order.items.reduce((sum, item) => {
return sum + item.price * item.quantity;
}, 0);
return {
id: order.id,
total,
itemCount: order.items.length,
};
}The TypeScript version is longer, but:
- You know exactly what an
Orderlooks like — it is self-documenting - If you pass the wrong shape, the compiler tells you immediately
- Your editor autocompletes
order.items,item.price,item.quantity - If you rename
pricetounitPrice, the compiler finds every broken reference
Declaration Files (.d.ts)
A .d.ts file (pronounced "dot dee dot tee ess" or "declaration file") contains only type
information — no executable code. It tells TypeScript what the types of a JavaScript library
are, without the library needing to be rewritten in TypeScript.
When you install @types/react, you get a .d.ts file that describes every export of the
React library. TypeScript reads it to give you type checking and autocomplete for React, even
though React itself is written in JavaScript.
// Example: a simplified .d.ts file for a JavaScript utility library
// math-utils.d.ts
export declare function add(a: number, b: number): number;
export declare function subtract(a: number, b: number): number;
export declare function clamp(value: number, min: number, max: number): number;
export interface Vector2 {
x: number;
y: number;
}
export declare function dot(a: Vector2, b: Vector2): number;You rarely write .d.ts files by hand. TypeScript can generate them automatically from your
own .ts files when you build a library (using the declaration: true tsconfig option).
For third-party JS libraries, the community maintains the @types packages on npm.
TypeScript as a Development Tool
It is worth emphasizing: TypeScript is a development-time tool. It helps you while you
are writing and building code. At runtime, the JavaScript engine runs plain .js — it knows
nothing about TypeScript.
This has important implications:
TypeScript types cannot validate data from external sources (API calls, user input, localStorage) at runtime — use Zod, Yup, or io-ts for that
A type assertion (the as keyword) does not cast or convert a value — it just tells the compiler to treat it as a different type
TypeScript cannot prevent runtime errors caused by incorrect data from outside your system
The TypeScript compiler has zero impact on your production bundle size or execution speed
You can have 100% type-correct TypeScript that still throws runtime errors — TypeScript only checks what it can see
as SomeType) as a substitute for actual runtime validation. A type assertion is a promise to the compiler — it does not perform any check at runtime.How TypeScript Enhances IDEs
TypeScript's type information powers a dramatically better editor experience. The TypeScript language server (a background process that editors communicate with) provides:
Autocomplete
When you type user., the editor shows every property and method of user's type. No
guessing, no documentation lookup required.
Go to Definition
Click on any function, variable, or type name and jump directly to where it is defined —
even inside node_modules. This works because TypeScript knows the exact shape of every
value.
Find All References Ask the editor to show every place a function or variable is used. TypeScript traverses the entire codebase, following imports across files.
Safe Rename Rename a function or property and TypeScript updates every reference in every file — including files you did not know used it.
Inline Error Highlighting Errors appear as red squiggles as you type, before you save. You see the problem at the exact character that caused it, with a plain-English explanation.
Hover Documentation Hovering over any symbol shows its type, its JSDoc comment, and a link to the source.
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
const user: User = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
createdAt: new Date(),
};
// In VS Code, after typing "user." you get autocomplete:
// id, name, email, createdAt — and their types
user.name; // string
user.createdAt; // Date
// Typo — caught immediately, not at runtime:
user.emial; // Error: Property 'emial' does not exist on type 'User'. Did you mean 'email'?TypeScript vs JavaScript Feature Comparison
Feature | JavaScript | TypeScript |
|---|---|---|
Type annotations | Not available | name: string, age: number |
Interfaces | Not available | interface User { name: string } |
Enums | Not available | enum Direction { Up, Down, Left, Right } |
Generics | Not available | function identity<T>(x: T): T |
Type inference | None | Full inference from initial values |
Access modifiers | Not available | public, private, protected, readonly |
Optional chaining | Yes (ES2020) | Yes + type-aware |
Nullish coalescing | Yes (ES2020) | Yes + type-aware |
Decorators | Stage 3 proposal | Supported (with experimentalDecorators) |
Quick Reference
TypeScript = JavaScript + static types, compiled to plain JavaScript
Uses structural typing — compatibility is based on shape, not name
Type inference: TypeScript deduces types without annotations when possible
.d.ts files contain type information for JavaScript libraries
Types are completely erased at compile time — zero runtime overhead
TypeScript is a development tool; it cannot validate runtime data
The language server powers autocomplete, go-to-def, find-refs, safe rename