TypeScript vs JavaScript
TypeScript and JavaScript are deeply connected — TypeScript is a strict superset of JavaScript, meaning every valid JavaScript program is also valid TypeScript. But the differences between them shape how you write, maintain, and reason about code. This page gives you an honest, side-by-side comparison across the dimensions that matter most.
The Relationship Explained
The "superset" relationship is important:
- All JavaScript is valid TypeScript — you can rename a
.jsfile to.tsand it usually compiles. - TypeScript adds an extra layer (types, interfaces, enums, generics) that gets erased during compilation.
- The final output is plain JavaScript — browsers and Node.js never see TypeScript syntax.
// TypeScript source
function add(a: number, b: number): number {
return a + b;
}
const result: number = add(2, 3);// Compiled JavaScript output (types erased)
function add(a, b) {
return a + b;
}
const result = add(2, 3);High-Level Comparison
Feature | JavaScript | TypeScript |
|---|---|---|
Type system | Dynamic (types checked at runtime) | Static (types checked at compile time) |
Syntax | Standard ECMAScript | ECMAScript + type annotations |
Runs directly in browser | Yes | No — must compile to JS first |
IDE support | Good (inference-based) | Excellent (fully typed) |
Compile step | None required | Required (tsc, esbuild, etc.) |
Null safety | No (undefined is everywhere) | Yes (with strictNullChecks) |
Interfaces / Generics | Not available | First-class features |
Learning curve | Low | Moderate |
Best for | Small scripts, prototypes | Large apps, teams, long-lived code |
Type Annotations: The Most Visible Difference
The most obvious difference is that TypeScript lets you annotate variables, function parameters, and return types. These annotations are optional (TypeScript infers types when possible) but become increasingly valuable as your codebase grows.
// JavaScript — no annotations, dynamic types
let count = 0;
count = 'oops'; // Valid JS — no error, but probably wrong
function double(n) {
return n * 2;
}
double('5'); // Returns '55' (string * 2 === NaN? No — JS coerces)
// Actually: '5' * 2 = 10 because * coerces to number
// But: '5' + 2 = '52' — surprise!// TypeScript — explicit contracts
let count: number = 0;
count = 'oops'; // Error: Type 'string' is not assignable to type 'number'
function double(n: number): number {
return n * 2;
}
double('5'); // Error: Argument of type 'string' is not assignable to parameter of type 'number'Type Inference: Less Annotation Than You Think
TypeScript is smart enough to infer types without explicit annotations in many cases. You don't have to annotate everything — only the parts where the compiler needs a hint.
// TypeScript infers the type from the assignment
let name = 'Alice'; // inferred: string
let age = 30; // inferred: number
let active = true; // inferred: boolean
// TypeScript infers the return type from the function body
function square(n: number) {
return n * n; // inferred return type: number
}
// TypeScript infers array element types
const scores = [95, 87, 92]; // inferred: number[]
scores.push('A'); // Error: Argument of type 'string' is not assignable to type 'number'Error Detection: Compile Time vs Runtime
This is the most practically significant difference between the two languages:
Error Type | JavaScript | TypeScript |
|---|---|---|
Wrong argument type | Runtime error or silent bug | Compile-time error |
Missing property access | Runtime: undefined | Compile-time error |
Null/undefined dereference | Runtime: TypeError | Compile-time error (with strict) |
Typo in property name | Silent undefined | Compile-time error |
Missing case in switch | Silent fallthrough | Detectable with exhaustiveness check |
Wrong number of arguments | Runtime silent | Compile-time error |
interface Product {
id: number;
name: string;
price: number;
}
function displayProduct(product: Product) {
// Error: Property 'titel' does not exist on type 'Product'.
// Did you mean 'name'?
console.log(product.titel);
}Null Safety: A Critical Improvement
JavaScript's null and undefined are the source of more runtime errors than almost anything else. The infamous "Cannot read properties of undefined" error is a JavaScript rite of passage.
TypeScript's strictNullChecks option forces you to handle null and undefined explicitly:
// JavaScript — null is silent
function getUser(id) {
return database.find(id); // might return null
}
const user = getUser(42);
console.log(user.name); // TypeError at runtime if user is null// TypeScript with strictNullChecks
function getUser(id: number): User | null {
return database.find(id) ?? null;
}
const user = getUser(42);
// Error: Object is possibly null
console.log(user.name);
// Correct: handle the null case explicitly
if (user !== null) {
console.log(user.name); // TypeScript knows user is User here
}
// Or use optional chaining
console.log(user?.name); // string | undefined — safeInterfaces and Structural Typing
TypeScript introduces interfaces — a way to describe the shape of an object. JavaScript has no equivalent built-in construct (class is not the same thing).
TypeScript uses structural typing: a type is compatible with another if it has at least the required properties, regardless of what it was declared as.
interface Point {
x: number;
y: number;
}
// This works even though we didn't say "implements Point"
function printPoint(p: Point) {
console.log(`(${p.x}, ${p.y})`);
}
// Any object with x and y is compatible
const origin = { x: 0, y: 0 };
const fancy = { x: 10, y: 20, label: 'Home' }; // extra property is fine
printPoint(origin); // OK
printPoint(fancy); // OK — structural typing accepts extra propertiesEnums: TypeScript-Only Feature
TypeScript adds enums, which have no direct equivalent in JavaScript (you'd normally use a const object or string union).
// JavaScript pattern — manual enum simulation
const Direction = Object.freeze({
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
});
// No compile-time protection
function move(dir) {
// dir could be anything
}// TypeScript enum
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
function move(dir: Direction) {
console.log(dir);
}
move(Direction.Up); // OK
move('diagonal'); // Error: Argument of type '"diagonal"' is not assignable to type 'Direction'type Direction = "UP" | "DOWN" | "LEFT" | "RIGHT". It produces no runtime code and works just as well for most use cases.Generics: Writing Reusable Typed Code
Generics are a TypeScript feature that lets you write functions and classes that work with any type while still being type-safe. JavaScript has no equivalent.
// JavaScript — works, but loses type information
function first(array) {
return array[0]; // always returns 'any' — type is lost
}
const n = first([1, 2, 3]); // we know it's a number, but JS doesn't
const s = first(['a', 'b']); // same function, different types — JS can't tell// TypeScript generic — preserves type information
function first<T>(array: T[]): T | undefined {
return array[0];
}
const n = first([1, 2, 3]); // TypeScript knows: n is number | undefined
const s = first(['a', 'b']); // TypeScript knows: s is string | undefined
n?.toFixed(2); // OK — number method available
s?.toUpperCase(); // OK — string method availableTooling and Developer Experience
TypeScript's language server gives editors information that plain JavaScript simply cannot provide:
IDE Feature | JavaScript | TypeScript |
|---|---|---|
Autocomplete | Heuristic / partial | Comprehensive and accurate |
Hover type info | Sometimes (inferred) | Always available |
Go to definition | Works for local symbols | Works across the entire project |
Rename symbol | Unreliable | Safe — checks all call sites |
Unused variable detection | ESLint only | Built into the compiler |
Inline error display | ESLint only | Compiler + ESLint |
Build Pipeline Differences
JavaScript can run directly in browsers and Node.js without any transformation. TypeScript always needs a compilation step.
JavaScript workflow: Write .js → Run directly in Node.js or browser TypeScript workflow: Write .ts → tsc / esbuild / SWC → .js → Run in Node.js or browser
In practice, most modern JavaScript projects already have a build step (webpack, Vite, Rollup), so adding TypeScript to that pipeline is usually straightforward — often a one-line config change.
Ecosystem and Third-Party Libraries
A common concern about adopting TypeScript is whether third-party libraries support it. The situation has improved dramatically:
Many popular packages now ship built-in TypeScript definitions (React, Next.js, Express, Prisma, Zod)
The @types/* packages on npm provide community-maintained definitions for thousands of libraries
When a package ships no types and no @types/* exists, you can write local declarations or use any
The DefinitelyTyped repository contains type definitions for over 8,000 npm packages
# Install types for packages that don't include them npm install --save-dev @types/node # Node.js built-in types npm install --save-dev @types/lodash # Lodash types
Which Should You Choose?
Situation | Recommendation |
|---|---|
New project, any size | TypeScript — setup cost is low, benefits compound over time |
Small script / quick prototype | JavaScript — no build step needed |
Large existing JS codebase | Migrate gradually — allowJs: true in tsconfig |
Team with TypeScript experience | TypeScript without hesitation |
Team new to typed languages | Start with TypeScript basics, ramp up strictness over time |
Library you will publish to npm | TypeScript — you generate .d.ts files for consumers |
The Bottom Line
TypeScript and JavaScript are not competitors — TypeScript is JavaScript, with an optional type layer on top. You can start using TypeScript today without abandoning any of the JavaScript ecosystem you already know.
The question is not really "TypeScript or JavaScript?" — it is "do I want a compiler checking my work?". For any project that will be maintained, grown, or worked on by more than one person, the answer is almost always yes.
Summary
TypeScript is a superset of JavaScript — all JS is valid TS
TypeScript adds type annotations, interfaces, enums, and generics that are erased at compile time
The key benefit is catching type errors at compile time rather than runtime
TypeScript inference means you annotate far less than you might expect
strictNullChecks eliminates the most common class of runtime errors
Editor support (autocomplete, hover docs, safe rename) is dramatically better with TypeScript
Adoption is gradual — you can mix JS and TS files in the same project