By the end of this chapter, you'll have TypeScript running on your machine, you'll have written your first typed function, and you'll have caught your first bug before it happened. This isn't a "read the documentation" chapter. This is a "do the thing" chapter. Open your terminal. Let's go.
Installation: Faster Than You Think
You need Node.js installed. If you've written JavaScript, you almost certainly have it. Let's verify:
node --version
# Should show v18 or higher. If not, grab it from nodejs.org.
Now install TypeScript. You have two options, and I'll give you the one you'll actually use:
# Option 1: Install globally (simplest for learning)
npm install -g typescript
# Option 2: Install in your project (what you'll do in real projects)
npm install --save-dev typescript
For this chapter, go with Option 1. It gives you the tsc command everywhere. You can switch to project-local later.
Verify it worked:
tsc --version
# Should show Version 7.x.x
You now have TypeScript installed. That took thirty seconds. The rest of this chapter is about using it.
Your First TypeScript File
Create a new directory and open it in your editor:
mkdir ts-first-steps
cd ts-first-steps
code . # or whatever opens your editor
Create a file called index.ts. The .ts extension is the only difference from JavaScript. That's it. TypeScript files ARE JavaScript files — they just have a different extension so the compiler knows to process them.
Now write some JavaScript. Real JavaScript. The JavaScript you already know:
// index.ts
function greet(name) {
return "Hello, " + name.toUpperCase();
}
console.log(greet("World"));
console.log(greet(42));
This is valid JavaScript. It's also valid TypeScript. But TypeScript is about to earn its keep.
The Moment Everything Changes
In your terminal, compile the file:
tsc index.ts
You'll see... nothing? No errors? But we passed a number to greet(), and toUpperCase() doesn't work on numbers. What gives?
TypeScript, by default, is lenient. It assumes you're migrating from JavaScript and doesn't want to yell at you about every potential issue. But we're not migrating. We're building something new. We want the full protection.
Create a tsconfig.json file:
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler"
}
}
"strict": true is the most important setting in TypeScript. It enables every type-safety check the compiler offers. Without it, TypeScript is a linter. With it, TypeScript is a safety net.
Now compile again:
tsc index.ts
Nothing again? That's because TypeScript infers types from usage. Since we didn't annotate name, TypeScript looks at the function body, sees name.toUpperCase(), and infers that name must be... well, any. Because we didn't give it enough information.
This is the first lesson of TypeScript: the compiler can only protect you from what you tell it. If you don't annotate your parameters, TypeScript defaults to any — which means "I give up, anything goes."
Let's fix that:
function greet(name: string): string {
return "Hello, " + name.toUpperCase();
}
console.log(greet("World"));
console.log(greet(42));
Now compile:
index.ts:6:20 - error TS2345: Argument of type 'number' is not
assignable to parameter of type 'string'.
console.log(greet(42));
~~
There it is. Your first TypeScript error. And it's beautiful.
Look at what just happened. You didn't run the code. You didn't write a test. You didn't open a browser. You just described what you expected — name should be a string — and TypeScript checked your entire codebase against that expectation. It found the mismatch. It told you exactly where. It told you exactly why.
This is the core loop of TypeScript development: annotate, compile, fix, repeat. And the "fix" happens in your editor, not in production.
The Annotation Syntax: A Quick Tour
You just saw your first type annotation: name: string. The : Type syntax is how you tell TypeScript what you expect. It goes after variable names, parameter names, and function declarations.
Here are the annotations you'll use every day:
// Variables
let age: number = 30;
let name: string = "Alice";
let isActive: boolean = true;
// Arrays
let scores: number[] = [95, 87, 92];
let names: string[] = ["Alice", "Bob"];
// Functions — parameters and return type
function add(a: number, b: number): number {
return a + b;
}
// Objects
let user: { name: string; age: number } = {
name: "Alice",
age: 30,
};
The : number after the parameter list in add() is the return type annotation. It says "this function returns a number." If the function tries to return something else, TypeScript complains.
Try it:
function add(a: number, b: number): number {
return "oops";
// ~~~~~~
// Error: Type 'string' is not assignable to type 'number'.
}
TypeScript checks that what you actually return matches what you said you'd return. This is huge. How many times have you refactored a function and accidentally changed what it returns? TypeScript catches that.
Your First Bug Catch
Let's make this real. Here's a function you might actually write:
interface User {
id: number;
name: string;
email: string;
isAdmin: boolean;
}
function getAdminEmails(users: User[]): string[] {
return users
.filter(user => user.isAdmin)
.map(user => user.email);
}
// Usage
const users: User[] = [
{ id: 1, name: "Alice", email: "alice@example.com", isAdmin: true },
{ id: 2, name: "Bob", email: "bob@example.com", isAdmin: false },
{ id: 3, name: "Charlie", email: "charlie@example.com", isAdmin: true },
];
const adminEmails = getAdminEmails(users);
console.log(adminEmails); // ["alice@example.com", "charlie@example.com"]
This works. But what happens when someone adds a new user with a typo in the property name?
const users: User[] = [
{ id: 1, name: "Alice", email: "alice@example.com", isAdmin: true },
{ id: 2, name: "Bob", email: "bob@example.com", isAdmin: false },
{ id: 3, name: "Charlie", email: "charlie@example.com", isAdmin: true },
{ id: 4, name: "Diana", email: "diana@example.com", isadmin: true },
// ~~~~~~~
// Error: Object literal may only specify known properties,
// and 'isadmin' does not exist in type 'User'. Did you mean 'isAdmin'?
];
TypeScript catches the typo. It even suggests the correct property name. In JavaScript, isadmin would just be undefined, filter(user => user.isAdmin) would return false for Diana, and she'd silently never get admin emails. You might never notice. Until Diana complains. Or worse, until she doesn't complain and just assumes your system is broken.
This is the power of TypeScript. Not just catching type mismatches — catching intent mismatches. You said a User has isAdmin. TypeScript makes sure every User actually has isAdmin.
The Compile Step: What Actually Happens
Let's look at what tsc produces. Open the generated index.js:
// index.js (compiled output)
function greet(name) {
return "Hello, " + name.toUpperCase();
}
console.log(greet("World"));
console.log(greet(42));
The type annotations are gone. Completely stripped. The output is clean JavaScript — the same JavaScript you'd write by hand, minus the types.
This is the second lesson of TypeScript: types are compile-time only. They exist to help you write correct code. They don't exist at runtime. They don't make your code slower. They don't add any overhead. They're like scaffolding that gets removed before the building opens.
This also means you can use any JavaScript library with TypeScript. The types are optional. You can add them gradually. You can have a project that's 30% typed today, 60% next week, and 100% next month. TypeScript meets you where you are.
The Watch Mode: Your New Best Friend
Running tsc after every change gets old fast. TypeScript has a solution:
tsc --watch
This compiles your code and then watches for changes. Every time you save a .ts file, it recompiles instantly. The feedback loop is: type, save, see errors, fix, save, see no errors, smile.
In most modern setups (Vite, Next.js, etc.), you don't even run tsc directly. Your build tool handles it. But for learning, tsc --watch in one terminal and your editor in another is the perfect setup.
What You Just Learned
In the time it takes to drink a coffee, you:
- Installed TypeScript
- Wrote your first
.tsfile - Added type annotations to a function
- Caught a real bug before it ran
- Defined an interface and used it to validate data
- Compiled TypeScript to JavaScript
- Set up watch mode for instant feedback
That's the entire TypeScript workflow. Everything else in this book — generics, conditional types, pattern matching, decorators — is just building on this foundation. Annotate. Compile. Fix. Repeat.
Try This: Your First Exercise
Before moving on, do this in your editor:
- Create a function called
calculateTotalthat takes an array of numbers and returns their sum. Annotate everything. - Create an interface called
Productwithname(string),price(number), andinStock(boolean). - Create a function called
getAvailableProductsthat takes aProduct[]and returns only the products whereinStockistrue. - Intentionally pass a string to
calculateTotal. Watch TypeScript catch it. - Intentionally misspell a property in a
Productobject. Watch TypeScript suggest the fix.
Time needed: 10 minutes.
What to notice: How quickly the feedback loop works. Type, save, see the error, fix it. This is the rhythm of TypeScript development. Get comfortable with it — you'll be doing it thousands of times.
If you got stuck: Make sure strict: true is in your tsconfig.json. Without it, TypeScript is much more lenient and won't catch some of these issues.
Looking Forward
You now have TypeScript running. You've seen it catch bugs. You understand the basic annotation syntax. You know that types are compile-time only.
But you've also seen something that might bother you. When we wrote function greet(name: string), TypeScript caught the number argument. But how did it know? What's actually happening inside the compiler? And what's the deal with that tsconfig.json file — what do all those options mean?
The compiler is the engine of TypeScript. Understanding how it thinks — not just what it does — is the difference between fighting it and collaborating with it. In the next chapter, you'll learn to read the compiler's mind.
In the next chapter: tsconfig.json demystified, strict mode explained, and how to configure TypeScript so it works FOR you instead of against you.