You know JavaScript. TypeScript is just JavaScript with types. Here's everything you need to start.
Why bother with TypeScript?
// JavaScript — no errors until runtime
function greet(user) {
return `Hello, ${user.nme}`; // typo! "nme" instead of "name"
}
greet({ name: "Alice" }); // Returns "Hello, undefined" — silent bug!
// TypeScript — catches errors at compile time
function greet(user: { name: string }): string {
return `Hello, ${user.nme}`; // Error: Property 'nme' does not exist
}
TypeScript catches bugs before you run the code. That's the whole deal.
Setup in 30 seconds
npm install -D typescript tsx @types/node
# tsconfig.json
npx tsc --init
Or use TypeScript directly with Node:
npx tsx my-file.ts
The 8 types you'll use 90% of the time
// 1. Primitives
let name: string = "Alice";
let age: number = 28;
let active: boolean = true;
// 2. Arrays
let tags: string[] = ["ts", "js"];
let scores: number[] = [95, 87, 92];
let mixed: (string | number)[] = ["a", 1, "b", 2];
// 3. Objects (inline)
let user: { name: string; age: number } = { name: "Alice", age: 28 };
// 4. Functions
function add(a: number, b: number): number {
return a + b;
}
const greet = (name: string): string => `Hello, ${name}!`;
// 5. Union types
let id: string | number = "abc123";
id = 42; // also valid
type Status = "pending" | "active" | "inactive";
let userStatus: Status = "active";
// 6. Optional properties (?)
type User = {
name: string;
email: string;
phone?: string; // optional
};
// 7. Any (avoid this — it disables type checking)
let data: any = fetchData();
// 8. Unknown (safer than any)
let response: unknown = fetch("/api");
if (typeof response === "string") {
console.log(response.toUpperCase()); // OK — narrowed to string
}
Interfaces vs Types
// Interface — for objects (recommended for public APIs)
interface User {
id: number;
name: string;
email: string;
}
// Extends
interface AdminUser extends User {
permissions: string[];
}
// Type alias — more flexible, works for primitives, unions, etc.
type ID = string | number;
type Nullable<T> = T | null;
type UserOrAdmin = User | AdminUser;
// Both work for objects — use either, be consistent
Generics: Write once, use with any type
// Without generics — duplicated code
function getFirstString(arr: string[]): string | undefined {
return arr[0];
}
function getFirstNumber(arr: number[]): number | undefined {
return arr[0];
}
// With generics — one function, any type
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
getFirst(["a", "b", "c"]); // string | undefined
getFirst([1, 2, 3]); // number | undefined
getFirst([{ id: 1 }]); // { id: number } | undefined
Common utility types
type User = {
id: number;
name: string;
email: string;
password: string;
};
// Partial — all properties optional
type UpdateUser = Partial<User>;
// { id?: number; name?: string; email?: string; password?: string }
// Required — all properties required
type FullUser = Required<UpdateUser>;
// Pick — select some properties
type PublicUser = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string }
// Omit — exclude some properties
type SafeUser = Omit<User, "password">;
// { id: number; name: string; email: string }
// Record — key-value map
type UserMap = Record<string, User>;
const users: UserMap = {
"alice": { id: 1, name: "Alice", email: "alice@example.com", password: "..." },
};
// ReturnType
function fetchUser() { return { id: 1, name: "Alice" }; }
type FetchedUser = ReturnType<typeof fetchUser>;
// { id: number; name: string }
Type narrowing
type Response = { success: true; data: User } | { success: false; error: string };
function handleResponse(res: Response) {
if (res.success) {
// TypeScript knows this is { success: true; data: User }
console.log(res.data.name);
} else {
// TypeScript knows this is { success: false; error: string }
console.log(res.error);
}
}
Quick wins in your existing JS code
// Add return types to functions
function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
// Type your API responses
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
async function fetchUsers(): Promise<ApiResponse<User[]>> {
const res = await fetch("/api/users");
return res.json();
}
// Type React props
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary" | "danger";
disabled?: boolean;
}
function Button({ label, onClick, variant = "primary", disabled = false }: ButtonProps) {
return <button onClick={onClick} className={variant} disabled={disabled}>{label}</button>;
}
TypeScript cheat sheet
| What | Syntax |
|---|---|
| String | let x: string |
| Number | let x: number |
| Boolean | let x: boolean |
| Array | let x: string[] |
| Object | let x: { key: type } |
| Union | `let x: string \ |
| Optional | {% raw %}property?: type
|
| Function | (arg: type): returnType => {} |
| Generic | function f<T>(x: T): T |
| Interface | interface Name { ... } |
| Type alias | type Name = ... |
| Partial | Partial<Type> |
| Pick | `Pick<Type, 'key1' \ |
| Omit | {% raw %}Omit<Type, 'key'>
|
| Record | Record<string, Type> |
Next steps
- Start migrating one file at a time — rename
.jsto.ts - Fix the type errors (let TypeScript guide you)
- Add types to function parameters first
- Gradually add stricter tsconfig settings
The any type is a crutch — use it to get started, but aim to remove it over time.
Questions? Drop them in the comments — happy to help!
Top comments (0)