Last 30 Days
No notifications
TypeScript is JavaScript with a static type system. You write .ts (or .tsx) files, the compiler checks them, and then *erases* the types to produce plain JavaScript that runs anywhere JS runs. The payoff: autocomplete that actually knows your data, refactors that don't break, and bugs caught before runtime.
JavaScript is dynamically typed. That's flexible — and dangerous. Every undefined is not a function, every typo in an object key, every "oh I forgot this field can be null" is a runtime crash.
TypeScript adds a layer of static analysis on top of JS. You annotate (or let it infer) the shape of your data, and the compiler tells you — *before* you ship — when something doesn't line up.
Key ideas:
| Tool | What it does |
tsc | The official compiler. Type-checks AND emits .js. |
ts-node / tsx | Run .ts files directly without a build step (great for scripts). |
Vite / Next.js / esbuild | Bundlers that strip types fast. They usually do not type-check — you run tsc --noEmit separately. |
@types/* | Community type definitions for libraries written in plain JS (e.g. @types/node, @types/express). |
ts-eslint | Lint rules that understand types. |
Globally is fine for playing, but in real projects always install it as a dev dependency so the version is pinned:
npm install --save-dev typescript
npx tsc --init # creates tsconfig.json with sensible defaults
npx tsc # compile every .ts file in the projectIf you're inside Next.js / Vite / Create React App, TypeScript is supported out of the box — just rename a file to .tsx.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true,
"jsx": "preserve"
},
"include": ["src"]
}The single most important flag is strict: true. It turns on noImplicitAny, strictNullChecks, and a handful of other checks that turn TS from "linter" into "actual type system". Always start a new project with strict on.
// greet.ts
function greet(name: string, age: number): string {
return Hello ${name}, you are ${age};
}console.log(greet("Rahul", 21));
// console.log(greet("Rahul", "21")); // ❌ Argument of type 'string' is not assignable to parameter of type 'number'.
Run it:
npx tsc greet.ts # produces greet.js
node greet.js
# or, in one step:
npx tsx greet.tsYou don't have to annotate everything. TypeScript infers types from initial values:
let count = 0; // inferred as number
count = "hello"; // ❌ Type 'string' is not assignable to type 'number'.const items = [1, 2, 3]; // number[]
items.push("four"); // ❌
Rule of thumb: annotate function *parameters* and public APIs. Let TS infer everything else.
Think of TS as a *separate program* that runs alongside your JS:
1. You write .ts source.
2. The TS compiler builds a graph of all your types and checks them.
3. It either errors out, or strips the type annotations and outputs .js.
4. Node / the browser runs the JS — it knows nothing about TypeScript.
That separation is why TS can never affect runtime behaviour. typeof at runtime is still JS typeof. There are no classes-from-types or runtime type checks. (Libraries like Zod fill that gap when you need it.)
In the next topic we'll go through every primitive type, the difference between any, unknown, and never, and how literal types let TS narrow values down to a single string.