🚀 Introduction to TypeScript
Welcome to your TypeScript journey! Think of TypeScript as JavaScript's incredibly helpful friend who catches your mistakes before they become problems. In this lesson, we'll explore what makes TypeScript special and why it's become the default choice for building serious web applications.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain what TypeScript is and how it relates to JavaScript
- Describe the key benefits TypeScript brings to your development workflow
- Set up a TypeScript development environment from scratch
- Configure TypeScript with a sensible
tsconfig.json - Compile and run your first type-safe program, and understand what the compiler actually does
Estimated Time: 45–60 minutes
Project: Set up TypeScript and build a small type-safe calculator
In This Lesson
What is TypeScript?
Imagine you're writing a letter to a friend. In JavaScript, it's like writing in pencil with no spellcheck — you can write anything you want, and you won't find out about mistakes until your friend reads it and gets confused. TypeScript is like having a smart editor that underlines potential problems as you write, helping you catch issues before you ever send the letter. 📝
TypeScript is JavaScript with a type system bolted on. More precisely, it's a superset of JavaScript that adds optional static typing and rich tooling to the language. Let's unpack that:
📖 Definition
TypeScript: A strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. It's developed and maintained by Microsoft, it's open source, and it compiles down to plain JavaScript that runs anywhere JavaScript runs — browsers, servers, phones, everywhere.
The "Superset" Concept
When we say TypeScript is a "superset" of JavaScript, picture a box of LEGO bricks. TypeScript is that same box of bricks, plus an instruction manual and a few special connector pieces that make building easier and safer. Everything you can do in JavaScript, you can do in TypeScript — TypeScript just gives you extra tools when you want them. This is why a valid .js file is (almost always) already a valid .ts file.
A Real-World Analogy
Think of JavaScript as a car with a manual transmission. You have complete control, but you have to shift gears yourself and pay attention to a lot of details. TypeScript is like adding a modern driver-assistance system to that same car — you still drive, but the system warns you before you drift out of your lane, beeps when you're about to reverse into something, and generally makes the journey smoother. You're still fully in control; you just have a safety net now. 🚗
💡 Key Insight: TypeScript never runs in the browser or in Node.js directly. It always becomes JavaScript first. You get all the benefits of TypeScript while developing, but your final shipped code is ordinary JavaScript that runs everywhere.
Why TypeScript Matters
You might be thinking, "I already know JavaScript. Why should I learn TypeScript?" Great question. Let me show you why TypeScript has become so popular that it's now the default at Google, Microsoft, Airbnb, Slack, Shopify, and countless other companies — and why most modern frameworks ship with TypeScript support out of the box. 🌟
Problem: The Million-Dollar Typo
Picture this: you're building an e-commerce site. You have a function that calculates the total price of items in a shopping cart. In plain JavaScript, you might write:
function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
// Later in your code...
const cart = [
{ name: "Laptop", price: 999, quantity: 1 },
{ name: "Mouse", price: "25", quantity: 2 } // Oops! Price is a string
];
const total = calculateTotal(cart);
console.log(total); // What do you think this prints?
Did you spot the bug? The mouse's price is a string ("25") instead of a number (25). When JavaScript multiplies "25" * 2 it actually coerces the string to a number here, so you might get lucky — but change the shape of that data slightly and you can end up concatenating strings, producing NaN, or charging the wrong amount. In production. With real money. 😱 The point is: JavaScript won't warn you, so the mistake sails straight through.
TypeScript to the Rescue
Now the same code in TypeScript:
// Define what an item should look like
interface CartItem {
name: string;
price: number; // Must be a number!
quantity: number; // Must be a number!
}
function calculateTotal(items: CartItem[]): number {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
const cart: CartItem[] = [
{ name: "Laptop", price: 999, quantity: 1 },
{ name: "Mouse", price: "25", quantity: 2 } // TypeScript error!
];
⚠️ TypeScript Catches the Error
Before you even run this code, your editor shows a red squiggle under "25" and tells you:
Type 'string' is not assignable to type 'number'.
You fix it immediately, before it ever reaches production. Crisis averted! 🎉
The Six Superpowers of TypeScript
1️⃣ Catch Errors Early (Type Safety)
Like a spell-checker for your code, TypeScript catches typos, wrong types, and missing properties before you run your program. It's like having a tireless code reviewer working 24/7, flagging bugs the instant you type them.
✅ Real-World Impact
A widely cited study of bugs at Airbnb estimated that around 38% of the bugs that reached production could have been prevented by TypeScript. That's more than one in three bugs stopped before a customer ever saw them.
2️⃣ Incredible Editor Support (IntelliSense)
Remember trying to recall the exact name of a function or what parameters it takes? With TypeScript, your editor becomes psychic. As you type, it suggests exactly what you need:
- 🔍 Autocomplete: type a dot after an object and see all its properties and methods
- 📚 Inline documentation: hover over any function to see what it does and how to use it
- ⚡ Instant feedback: errors highlighted in real time as you type
- 🔄 Safe refactoring: rename a symbol in one place and it updates everywhere, correctly
It's like having the entire documentation for your project loaded into your editor. 🧠
3️⃣ Self-Documenting Code
Look at these two function signatures. Which one tells you more?
| JavaScript | TypeScript |
|---|---|
function createUser(data) { } |
function createUser(data: { name: string; email: string; age: number }): User { } |
With TypeScript you can see exactly what the function expects and what it returns, without reading a single line of its implementation. The types are the documentation — and unlike comments, they can't go stale, because the compiler checks them.
4️⃣ Fearless Refactoring
Ever been afraid to change code because you might break something elsewhere? TypeScript makes refactoring safe. Change a function's signature, and TypeScript immediately lists every place that needs updating. You can restructure with confidence, knowing the compiler has your back.
5️⃣ Better Collaboration
On a team, TypeScript acts like a contract. When you define a function with types, you're telling your teammates exactly how to call it. No more guessing, no more digging through source, no more "I thought it wanted an array of strings, not a single string!" 🤝
6️⃣ Future-Facing JavaScript
TypeScript supports modern JavaScript features and can compile them down to run in older environments. Write tomorrow's syntax today, and let the compiler produce code that runs where you need it to.
💭 Think About It: Every minute you spend learning TypeScript saves you hours of debugging, code review, and production firefighting. It's an investment that compounds.
TypeScript vs JavaScript: Side by Side
Let's compare JavaScript and TypeScript with concrete examples. Remember: all JavaScript is valid TypeScript, but TypeScript gives you extra guarantees when you want them.
Example 1: A Simple Function
Let's write a function that greets a user.
JavaScript
function greet(name) {
return "Hello, " + name + "!";
}
// All of these run, even when they shouldn't
greet("Alice"); // OK
greet(123); // OK but weird
greet(); // OK but name is undefined
greet({}); // OK but prints [object Object]
TypeScript
function greet(name: string): string {
return "Hello, " + name + "!";
}
// Only this is allowed
greet("Alice"); // ✅ OK
greet(123); // ❌ Error!
greet(); // ❌ Error!
greet({}); // ❌ Error!
See the difference? TypeScript ensures you can only call the function correctly. No more defensive checks for inputs that should never happen — the types are your guardrails. 🛡️
Example 2: Working with Objects
Let's model a user profile.
// JavaScript — what properties does a user have? Who knows! 🤷
const user = {
name: "John",
email: "john@example.com"
};
console.log(user.age); // undefined — no error, but probably a bug
console.log(user.nane); // undefined — typo! But JavaScript won't tell you
// TypeScript — define the structure first
interface User {
name: string;
email: string;
age: number;
}
const user: User = {
name: "John",
email: "john@example.com"
// ❌ Error! Property 'age' is missing
};
console.log(user.age); // ✅ OK — we know this exists
console.log(user.nane); // ❌ Error! Property 'nane' doesn't exist (typo caught!)
Example 3: Array Operations
Let's work with a list of products.
JavaScript
const products = [
{ name: "Laptop", price: 999 },
{ name: "Mouse", price: 25 }
];
// Oops, typo in property name
products.forEach(p => {
console.log(p.nam); // undefined
});
TypeScript
interface Product {
name: string;
price: number;
}
const products: Product[] = [
{ name: "Laptop", price: 999 },
{ name: "Mouse", price: 25 }
];
products.forEach(p => {
console.log(p.nam); // ❌ Error! Did you mean 'name'?
});
The Gradual Adoption Story
Here's the beautiful part: you don't have to choose between JavaScript and TypeScript all at once. You can adopt TypeScript gradually:
- Start simple: rename a
.jsfile to.ts— it still works. - Add types gradually: annotate the spots where types help most.
- Tighten gradually: turn on stricter checks as you get comfortable.
- Mix and match: use JavaScript libraries in TypeScript projects seamlessly (most ship types, and the rest have community types on DefinitelyTyped).
🎯 The Bottom Line
JavaScript: fast to write, flexible — but that flexibility comes at the cost of safety.
TypeScript: a little more work upfront, but it catches bugs early, makes refactoring safe, and turns your types into living documentation.
Think of it like seatbelts in a car — a small habit that occasionally saves you from disaster. 🚗💨
Quick Comparison Table
| Feature | JavaScript | TypeScript |
|---|---|---|
| Type checking | At runtime (when code runs) | At compile time (as you write) |
| Error detection | When a user triggers the bug | As you type |
| Editor support | Basic autocomplete | Rich IntelliSense & refactoring |
| Learning curve | Easier to start | Steeper at first, pays off quickly |
| Maintainability | Relies on careful documentation | Self-documenting through types |
| Refactoring confidence | Risky; needs thorough tests | Safe; the compiler catches breaks |
| Team collaboration | Relies on documentation | Types act as contracts |
Setting Up Your Environment
Enough theory — let's get our hands dirty! Setting up TypeScript is like prepping your kitchen before cooking a great meal: gather the tools, lay out the ingredients, and you're ready. This is easier than it sounds. 👨🍳
What You'll Need
Before we begin, make sure you have these essentials:
📋 Prerequisites Checklist
- ✅ Node.js (version 20 LTS or newer) — the JavaScript runtime
- ✅ npm (ships with Node.js) — the package manager
- ✅ A code editor — VS Code is the gold standard for TypeScript
- ✅ A terminal — where we'll run our commands
Step 1: Verify Node.js and npm
Open your terminal (Command Prompt or PowerShell on Windows, Terminal on macOS/Linux) and check that Node.js and npm are installed:
# Check Node.js version
node --version
# Should show something like: v22.11.0
# Check npm version
npm --version
# Should show something like: 10.9.0
⚠️ Don't Have Node.js?
No problem. Head to nodejs.org and download the LTS (Long Term Support) version — it's free and installs in a few clicks. The installer sets up both Node.js and npm.
Step 2: Set Up Your First TypeScript Project
Modern best practice is to install TypeScript locally, as a dev dependency of your project, rather than globally. That way every project pins its own TypeScript version and your teammates get the exact same compiler. Let's create a proper project:
# Create a new folder and move into it
mkdir my-first-typescript-project
cd my-first-typescript-project
# Initialize a Node.js project (creates package.json)
npm init -y
# Install TypeScript for THIS project (dev dependency)
npm install --save-dev typescript
# Create a TypeScript configuration file
npx tsc --init
Here's what each step did:
- 🗂️
mkdircreated a folder for your project - 📦
npm init -ycreated apackage.json(your project's ID card) - ⬇️
npm install --save-dev typescriptinstalled TypeScript for this project only - ⚙️
npx tsc --initcreated atsconfig.json(TypeScript's settings)
That tsc command — reached here through npx, which runs the local copy — stands for TypeScript Compiler. It's the tool that turns your TypeScript into JavaScript.
💡 Global install (optional)
You can install TypeScript globally with npm install -g typescript so tsc is available everywhere. It's handy for quick experiments, but for real projects prefer the local install above — it keeps versions consistent across machines.
Step 3: Supercharge VS Code
If you're using Visual Studio Code (highly recommended), you already have excellent built-in TypeScript support. A few extensions make it even better:
✨ Recommended VS Code Extensions
- ESLint — catches common mistakes and enforces a consistent style
- Prettier — formats your code automatically on save
- Error Lens — shows errors inline, right where they happen
To install: open the Extensions panel (Ctrl/Cmd + Shift + X), search for each, and click Install.
Your Project Structure
After these steps, your folder should look like this:
tsconfig.json is where you'll spend the most time.my-first-typescript-project/
├── node_modules/ (TypeScript and dependencies live here)
├── package.json (Project information and dependencies)
├── package-lock.json (Exact versions of dependencies)
└── tsconfig.json (TypeScript configuration)
Perfect — you're ready to write TypeScript. Let's make sure everything works. 🚀
The TypeScript Compiler (tsc)
The compiler is your best friend. Think of it as a bilingual translator, fluent in both TypeScript and JavaScript. It reads your TypeScript, checks it for errors, and produces clean JavaScript that runs anywhere. 🌍
How the Compiler Works
Here's the compilation flow at a glance:
Essential Compiler Commands
These are the commands you'll reach for every day:
🔧 Everyday tsc Commands
# Compile a single file
npx tsc myfile.ts
# Creates: myfile.js
# Compile the whole project using tsconfig.json
npx tsc
# Compile and watch for changes (auto-recompile on save)
npx tsc --watch
# Type-check WITHOUT producing output files
npx tsc --noEmit
# Perfect for continuous integration (CI)
# Show help and all available options
npx tsc --help
💡 Running TypeScript directly (no manual compile step)
During development you often want to just run a .ts file without generating a .js file first. Two popular ways:
npx tsx myfile.ts— tsx runs TypeScript instantly (great for scripts).- Recent versions of Node.js can even run
.tsfiles directly by stripping the types. Handy, buttsxis the most reliable choice across environments today.
Either way, remember: running fast and type-checking are separate jobs. Keep npx tsc --noEmit in your workflow (and CI) so nothing untyped slips through.
Understanding Compilation Output
Let's see what the compiler actually produces. Watch what happens to the type annotations:
Input: greeting.ts
// TypeScript with types
function greet(name: string): string {
return `Hello, ${name}!`;
}
const message: string = greet("World");
console.log(message);
Output: greeting.js
// Compiled JavaScript (types removed)
function greet(name) {
return `Hello, ${name}!`;
}
const message = greet("World");
console.log(message);
Notice what happened? The type annotations (: string) vanished. JavaScript doesn't understand types, so once TypeScript has checked them, it removes them to produce pure JavaScript. ✨ (The exact output depends on your target — set it to an older version and modern syntax like template strings gets rewritten too.)
🎯 Key Concept: TypeScript types are completely erased during compilation. They exist only to help you while you write code. The final JavaScript has no trace of them — which means zero runtime overhead.
Compiler Error Messages
When TypeScript finds a problem, it gives you a precise, actionable message. For example:
function add(a: number, b: number): number {
return a + b;
}
const result = add(5, "10"); // Error! "10" is a string, not a number
Compiling this produces:
❌ Compiler Error
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
const result = add(5, "10");
~~~~
See how helpful that is? It tells you:
- 📍 the exact error code (
TS2345) — searchable if you want more detail - 📝 what's wrong (a string where a number was expected)
- 👉 exactly where the problem is (it underlines
"10")
Watch Mode: Your Development Companion
The --watch flag keeps the compiler running, recompiling automatically whenever you save. It's like a personal assistant watching your code. 👀
# Start watch mode
npx tsc --watch
# You'll see:
# [HH:MM:SS] Starting compilation in watch mode...
# [HH:MM:SS] Found 0 errors. Watching for file changes.
# Now every save re-checks your .ts files instantly!
💡 Watch Mode Best Practices
- Run
npx tsc --watchin its own terminal tab - Keep it running while you code for instant feedback
- Press Ctrl + C to stop when you're done
Your First TypeScript Program
Time to write real code! We'll build a small but complete program that shows off the power of types: a type-safe calculator. 🧮
Step-by-Step: Building a Calculator
Step 1: Create the File
In your project folder, create a file called calculator.ts.
Step 2: Write the Code
Add the following:
// calculator.ts — my first TypeScript program!
// Define exactly which operations the calculator supports
type Operation = 'add' | 'subtract' | 'multiply' | 'divide';
// A calculator function with type-safe parameters
function calculate(a: number, b: number, operation: Operation): number {
switch (operation) {
case 'add':
return a + b;
case 'subtract':
return a - b;
case 'multiply':
return a * b;
case 'divide':
if (b === 0) {
throw new Error('Cannot divide by zero!');
}
return a / b;
default: {
// TypeScript proves this branch is unreachable
const exhaustiveCheck: never = operation;
throw new Error(`Unhandled operation: ${exhaustiveCheck}`);
}
}
}
// Type-safe calls
console.log('10 + 5 =', calculate(10, 5, 'add')); // 15
console.log('10 - 5 =', calculate(10, 5, 'subtract')); // 5
console.log('10 * 5 =', calculate(10, 5, 'multiply')); // 50
console.log('10 / 5 =', calculate(10, 5, 'divide')); // 2
// These would be caught BEFORE running:
// calculate(10, 5, 'power'); // ❌ 'power' is not a valid Operation
// calculate("10", 5, 'add'); // ❌ "10" is not a number
Step 3: Compile Your Code
# Compile calculator.ts → calculator.js
npx tsc calculator.ts
Step 4: Run Your Program
# Run the compiled JavaScript
node calculator.js
# You should see:
# 10 + 5 = 15
# 10 - 5 = 5
# 10 * 5 = 50
# 10 / 5 = 2
Prefer to skip the manual compile while experimenting? Run it directly with npx tsx calculator.ts.
🎉 Congratulations!
You just wrote, compiled, and ran your first TypeScript program. Here's what makes this code special:
- Type safety:
aandbmust be numbers - Literal types:
Operationcan only be one of four exact strings - Return type: the function is guaranteed to return a number
- Exhaustive checking: the compiler ensures every operation is handled
- Error prevention: invalid inputs are caught before the code ever runs
See Type Checking in Action
Try the interactive below. Each button feeds a different argument into calculate(...) — click to see whether TypeScript would accept it or flag an error, and why.
calculate(a: number, b: number, op: Operation): number
👆 Click an example above to see what TypeScript thinks.
🎯 Literal Types (a Union of Exact Values)
type Operation = 'add' | 'subtract' | 'multiply' | 'divide';
This says "Operation can be 'add' OR 'subtract' OR 'multiply' OR 'divide' — and nothing else." It's a whitelist of valid values. Pass 'power' and TypeScript complains instantly. 🚫
🎯 Function Type Annotations
function calculate(a: number, b: number, operation: Operation): number
We're telling TypeScript: "this function takes two numbers and an Operation, and it always returns a number." Now the compiler can verify every call site.
🎯 Exhaustive Checking with never
default: {
const exhaustiveCheck: never = operation;
...
}
This is genius. If you later add a fifth value to Operation but forget to handle it in the switch, operation won't be never in the default branch, and TypeScript throws an error at compile time. It guarantees you never silently miss a case. 🎯
Try This Yourself!
🏋️ Mini Challenge
Extend the calculator with these features:
- Add a
'modulo'operation (the remainder after division) - Add a
'power'operation (ato the power ofb) - Write a helper that formats the result as a friendly string
💡 Hint
Add your new names to the Operation union first. The moment you do, TypeScript will flag the switch as non-exhaustive and walk you to the branches you still need to write.
✅ Solution
type Operation =
| 'add' | 'subtract' | 'multiply' | 'divide'
| 'modulo' | 'power';
function calculate(a: number, b: number, operation: Operation): number {
switch (operation) {
case 'add': return a + b;
case 'subtract': return a - b;
case 'multiply': return a * b;
case 'divide':
if (b === 0) throw new Error('Cannot divide by zero!');
return a / b;
case 'modulo':
if (b === 0) throw new Error('Cannot modulo by zero!');
return a % b;
case 'power': return a ** b;
default: {
const exhaustiveCheck: never = operation;
throw new Error(`Unhandled operation: ${exhaustiveCheck}`);
}
}
}
function formatResult(a: number, b: number, op: Operation): string {
return `${a} ${op} ${b} = ${calculate(a, b, op)}`;
}
console.log(formatResult(10, 3, 'modulo')); // "10 modulo 3 = 1"
console.log(formatResult(2, 8, 'power')); // "2 power 8 = 256"
Common First-Time Issues
If something goes sideways, here are the most common beginner snags:
| Problem | Solution |
|---|---|
| "tsc: command not found" | Use npx tsc (runs the local install), or install globally with npm install -g typescript |
| "Cannot find name 'console'" | Add Node type definitions: npm install --save-dev @types/node |
| Code compiles but still shows errors | Check tsconfig.json — you may have strict disabled or the wrong lib |
| No JavaScript file was created | There's a compile error. Run npx tsc --watch to see it in real time |
Configuration Basics (tsconfig.json)
The tsconfig.json file is TypeScript's control panel. It tells the compiler exactly how to behave — like adjusting the mirrors and seat before a road trip. 🚗
Understanding tsconfig.json
When you ran npx tsc --init earlier, you created this file. Most options start commented out. Here's a practical, modern starting point:
{
"compilerOptions": {
/* Language and Environment */
"target": "ES2022", // What JavaScript version to output
"module": "nodenext", // Modern Node.js module resolution
"lib": ["ES2022"], // Which built-in APIs to include
/* Type Checking */
"strict": true, // Enable ALL strict type checks
/* Emit */
"outDir": "./dist", // Where compiled .js files go
"rootDir": "./src", // Where your .ts source files live
"sourceMap": true, // Emit .map files for debugging
/* Interop */
"esModuleInterop": true, // Smoother CommonJS/ES module interop
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true // Skip type-checking of dependency .d.ts files
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Key Settings Explained
Let's break down the options you'll touch most:
🎯 target
"target": "ES2022"
This sets which version of JavaScript the compiler emits. It's like choosing which phone you're texting: an old phone (ES5) only handles plain messages, while a modern one (ES2022+) handles all the fancy features. For today's Node.js and evergreen browsers, ES2022 is a great default.
Common target values
ES5— maximum compatibility with very old browsersES2015— the modern-JS baseline (classes, arrow functions, modules)ES2022— recent features; a solid default for Node.js 20+ESNext— the very latest (bleeding edge)
🎯 strict
"strict": true
This is the single most important setting. It switches on all of TypeScript's strict checks at once. It's like a meticulous teacher reviewing your homework — it catches more, and you learn more. Always keep it true for new projects.
💡 Pro Tip: If you're converting a large existing JavaScript codebase, you can start with looser settings and tighten toward "strict": true file by file. For anything new, turn strict on from day one.
🎯 outDir and rootDir
"outDir": "./dist",
"rootDir": "./src"
These keep your project tidy:
rootDir— where you write your TypeScriptoutDir— where the compiled JavaScript lands
my-project/
├── src/ ← Your TypeScript files (.ts)
│ ├── index.ts
│ └── utils.ts
├── dist/ ← Compiled JavaScript (.js)
│ ├── index.js
│ └── utils.js
└── tsconfig.json
🎯 sourceMap
"sourceMap": true
Source maps are a Rosetta Stone between your TypeScript and the compiled JavaScript. When you hit a breakpoint in the debugger, they let you step through your original TypeScript instead of the generated JavaScript. Super helpful. 🗺️
Quick Configuration Recipes
Ready-to-use starting points for two common scenarios:
✨ For Node.js Projects
{
"compilerOptions": {
"target": "ES2022",
"module": "nodenext",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
🌐 For Browser Projects (with a bundler like Vite)
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"strict": true,
"sourceMap": true,
"esModuleInterop": true
}
}
Testing Your Configuration
Let's verify it all works. Create a source folder and a test file:
# Create the source folder
mkdir src
# Add src/test.ts with the code below
In src/test.ts:
const greeting: string = "Hello, TypeScript!";
console.log(greeting);
Now compile the whole project:
npx tsc
# Check that dist/test.js was created
ls dist/
# You should see: test.js (and test.js.map if sourceMap is true)
🎉 Success!
If dist/test.js exists, your TypeScript configuration is working perfectly. You're ready to build real projects! 🚀
Best Practices
Now that you know the basics, let's cover how to use TypeScript like a pro. These habits keep your code clean, safe, and easy to maintain — the unwritten rules experienced developers follow. 🌟
✅ Do's: Good TypeScript Habits
1. Always Enable Strict Mode
This is rule #1. Strict mode catches so many potential bugs you'll wonder how you lived without it.
// In tsconfig.json
{
"compilerOptions": {
"strict": true // ✅ Always!
}
}
✅ Why It Matters
With strict mode off, TypeScript silently allows code that can break at runtime. Strict mode is your seatbelt — you might not need it most days, but you'll be very glad it's on when you do.
2. Use Meaningful Names
Your code should read like prose. Use descriptive names that reveal intent.
❌ Cryptic
type U = {
n: string;
a: number;
};
function p(u: U): void {
console.log(u.n);
}
✅ Clear
type User = {
name: string;
age: number;
};
function printUserName(user: User): void {
console.log(user.name);
}
3. Prefer Interfaces for Object Shapes
When you're describing the shape of an object, interface is a great default — it's readable and gives clear error messages. Use type for unions, intersections, and aliases of primitives.
// ✅ Interface for an object shape
interface Product {
id: number;
name: string;
price: number;
}
// ✅ type for unions and primitive aliases
type Status = 'pending' | 'approved' | 'rejected';
type ID = string | number;
💡 Note: This is a mild stylistic preference, not a hard rule —interfaceandtypecan both describe object shapes and are interchangeable in most cases. We'll dig into the real differences in Lesson 1.3.
4. Avoid any Like the Plague
The any type switches off type checking for that value — it defeats the whole point of TypeScript. It's like buying a fire extinguisher and filling it with gasoline. 🔥 When you truly don't know a type, reach for unknown instead (it forces you to check before using the value).
❌ Terrible
function processData(data: any) {
// TypeScript can't help you here!
return data.value.toUpperCase();
}
✅ Much Better
interface Data {
value: string;
}
function processData(data: Data) {
// TypeScript knows value is a string!
return data.value.toUpperCase();
}
5. Let TypeScript Infer the Obvious
TypeScript is smart — let it infer types when they're obvious. Don't over-annotate.
❌ Too Verbose
const name: string = "Alice";
const age: number = 30;
const isActive: boolean = true;
✅ Just Right
const name = "Alice"; // inferred: string
const age = 30; // inferred: number
const isActive = true; // inferred: boolean
6. Specify Function Return Types
TypeScript can infer return types, but declaring them explicitly on your public functions catches mistakes and documents intent.
// ✅ Return type is explicit
function calculateTax(amount: number, rate: number): number {
return amount * rate;
}
// If you return the wrong thing, TypeScript catches it:
function calculateTax2(amount: number, rate: number): number {
return `${amount * rate}`; // ❌ Error! Returns string, not number
}
❌ Don'ts: Common Mistakes to Avoid
1. Don't Use the Non-Null Assertion (!) Carelessly
The ! operator tells TypeScript "trust me, this isn't null." If you're wrong, your app crashes at runtime.
❌ Dangerous
const user = users.find(u => u.id === 5);
console.log(user!.name); // Crashes if user is undefined!
✅ Safe
const user = users.find(u => u.id === 5);
if (user) {
console.log(user.name); // Safe!
} else {
console.log("User not found");
}
2. Don't Disable Checks to Silence Errors
You'll sometimes see // @ts-ignore or loosened settings used to hide an error. That's almost always the wrong fix — you're hiding the messenger, not solving the problem.
// ❌ Bad: hiding an error instead of fixing it
// @ts-ignore
const result = someFunction(wrongArgument);
// ✅ Good: fix the actual problem
const result = someFunction(correctArgument);
3. Don't Ignore Compiler Warnings
Every warning is TypeScript trying to help. Each one is a potential bug in waiting.
⚠️ Warning Signs
If you catch yourself thinking any of these, stop and reconsider:
- "I'll just use
anyfor now..." → take a moment to model the real type - "This
@ts-ignoreis only temporary..." → it never is; fix it now - "Strict mode is too strict..." → that's the point; it's saving you from bugs
💡 Pro Tips
1. Use Type Guards for Runtime Checks
Types disappear at runtime, so when data comes from outside your program (an API, user input), verify it at runtime too:
// A type guard function
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processValue(value: unknown) {
if (isString(value)) {
// TypeScript knows value is a string here
console.log(value.toUpperCase());
}
}
2. Use Optional Chaining and Nullish Coalescing
These modern operators make handling uncertain data safer:
// Optional chaining (?.)
const userName = user?.profile?.name; // undefined if user or profile is missing
// Nullish coalescing (??)
const displayName = userName ?? 'Guest'; // 'Guest' only if userName is null/undefined
3. Document Non-Obvious Types with JSDoc
/**
* Represents an item in the shopping cart.
* @property quantity - Number of items (minimum 1)
* @property price - Price per item in CENTS (not dollars!)
*/
interface CartItem {
id: string;
quantity: number;
price: number;
}
🎯 The Golden Rule
If TypeScript complains, there's usually a good reason. Don't fight the type system — work with it. It's trying to save you from a bug. 🛡️
Summary
🎉 Key Takeaways
- TypeScript is JavaScript with a type system — it adds optional static typing that catches errors before your code runs.
- Types are erased at compile time — your shipped JavaScript has zero runtime overhead from types.
- The compiler (tsc) is your friend — it checks your code and translates it to JavaScript.
- tsconfig.json controls everything — it decides how strict TypeScript is and where files go.
- Strict mode is essential — always use
"strict": truefor new projects. - Avoid
any— it disables the very safety you came for; prefer real types orunknown. - TypeScript makes refactoring safe — change code confidently; the compiler catches the breaks.
What You Accomplished
- ✅ Set up a complete TypeScript development environment
- ✅ Learned how the compiler checks and erases types
- ✅ Wrote, compiled, and ran your first TypeScript program
- ✅ Configured TypeScript with a modern
tsconfig.json - ✅ Picked up the best practices professionals rely on
Real-World Impact
Here's what TypeScript does for you, every single day:
📊 By the Numbers
Teams that adopt TypeScript consistently report that it:
- Prevents a sizable share of production bugs (Airbnb's analysis put it around 38%).
- Improves documentation by making data shapes explicit and searchable.
- Reduces debugging time by catching errors at compile time instead of runtime.
- Speeds up onboarding — new developers understand the codebase faster.
- Enables confident refactoring — change code knowing you won't silently break things.
📚 Additional Resources
- Official TypeScript Documentation — the definitive reference
- TypeScript Playground — try TypeScript in your browser, no install needed
- The TypeScript Handbook — a thorough, readable tour of the language
- TypeScript on GitHub — the source code and issue tracker
🚀 What's Next?
In the next lesson we'll dive into TypeScript's basic types. You'll learn about:
- Primitive types (
string,number,boolean, and more) - Arrays and tuples
- Type inference versus explicit typing
- Union types and literal types
- The special types:
any,unknown,never, andvoid
This is where TypeScript really starts to shine. 💎
🎉 Congratulations!
You've completed your first TypeScript lesson. You now understand what TypeScript is, why it's valuable, and how to set it up and use it — the foundation for everything that follows.
Take a moment to celebrate. You're on your way to becoming a TypeScript developer! 🚀
💭 Remember: TypeScript is a journey, not a destination. Start simple, add types gradually, and watch your code quality climb. Every type you add is a bug you prevent.
Quick Quiz: Test Your Understanding
🎯 Check Your Knowledge
Question 1: What does the TypeScript compiler (tsc) do?
Question 2: What happens to TypeScript types at runtime?
Question 3: Which setting should you always enable in tsconfig.json for a new project?
Your Action Items
📝 Checklist
- ☐ Installed Node.js (20 LTS or newer) and npm
- ☐ Created a project with a local TypeScript install and
tsconfig.json - ☐ Wrote and ran your first TypeScript program
- ☐ Experimented with the calculator example
- ☐ Set up your editor (preferably VS Code + ESLint + Prettier)
- ☐ Reviewed the best practices section
Bonus challenge: add modulo and power operations to the calculator. It reinforces literal types and exhaustive checking.