TypeScript Introduction
Type annotations, interfaces, generics, and why TypeScript matters
TypeScript Fundamentals
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It adds optional static typing and class-based object-oriented programming. TypeScript helps catch errors at compile time and improves code quality and developer experience.
Why TypeScript?
- Type Safety — Catch errors before runtime
- Better IDE Support — Autocomplete, refactoring, navigation
- Self-Documenting — Types serve as inline documentation
- Refactoring — Safer large-scale code changes
- Modern Features — ES6+ features with backward compatibility
Basic Types
// Primitive types
let name: string = "Alice";
let age: number = 25;
let isActive: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;
// Arrays
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"];
// Tuples - fixed length arrays with specific types
let tuple: [string, number] = ["Alice", 25];
// Enum
enum Status {
Pending = "PENDING",
Active = "ACTIVE",
Completed = "COMPLETED"
}
let status: Status = Status.Active;
// Any - opt out of type checking (avoid when possible)
let data: any = "hello";
data = 42; // No error
// Unknown - type-safe any
let input: unknown = getUserInput();
if (typeof input === "string") {
console.log(input.toUpperCase()); // Safe
}
// Void - no return value
function logMessage(message: string): void {
console.log(message);
}
// Never - function never returns
function throwError(message: string): never {
throw new Error(message);
}
Type Inference
// TypeScript infers types automatically
let message = "Hello"; // inferred as string
let count = 42; // inferred as number
let items = [1, 2, 3]; // inferred as number[]
// Contextual typing
const names = ["Alice", "Bob", "Charlie"];
names.forEach(name => {
// name is inferred as string
console.log(name.toUpperCase());
});
// Best practice: Let TypeScript infer when obvious
let user = { name: "Alice", age: 25 };
// Explicit when needed for clarity
function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
Interfaces
// Define object shape
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional property
readonly createdAt: Date; // Cannot be modified
}
// Using the interface
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
createdAt: new Date()
};
// Extending interfaces
interface AdminUser extends User {
role: "admin";
permissions: string[];
}
// Function interface
interface SearchFunction {
(query: string, limit?: number): Promise<Result[]>;
}
// Index signatures
interface Dictionary {
[key: string]: string;
}
const translations: Dictionary = {
hello: "hola",
goodbye: "adiós"
};
Type Aliases
// Create custom types
type ID = string | number;
type Point = { x: number; y: number };
// Union types
type Status = "pending" | "active" | "completed";
type Result = string | null;
// Intersection types
type Employee = Person & { employeeId: string };
// Function types
type Callback = (data: string) => void;
type AsyncHandler = (req: Request) => Promise<Response>;
// Template literal types
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/api/${string}`;
// Utility types
type UserPreview = Pick<User, "id" | "name">;
type UserWithoutEmail = Omit<User, "email">;
type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;
Generics
// Generic function
function identity<T>(value: T): T {
return value;
}
const str = identity("hello"); // type: string
const num = identity(42); // type: number
// Generic interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
const userResponse: ApiResponse<User> = {
data: { id: 1, name: "Alice", email: "a@b.com", createdAt: new Date() },
status: 200,
message: "Success"
};
// Generic constraints
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length);
}
logLength("hello"); // OK - strings have length
logLength([1, 2, 3]); // OK - arrays have length
// logLength(123); // Error - numbers don't have length
// Multiple type parameters
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
// Generic classes
class DataStore<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(index: number): T | undefined {
return this.items[index];
}
getAll(): T[] {
return [...this.items];
}
}
Type Guards
// Type narrowing with typeof
function process(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
// instanceof guard
class Dog { bark() { console.log("Woof!"); } }
class Cat { meow() { console.log("Meow!"); } }
function speak(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
// in operator guard
interface Fish { swim(): void; }
interface Bird { fly(): void; }
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim();
} else {
animal.fly();
}
}
// Custom type guard
interface User { type: "user"; name: string; }
interface Admin { type: "admin"; permissions: string[]; }
function isAdmin(person: User | Admin): person is Admin {
return person.type === "admin";
}
const person: User | Admin = getUser();
if (isAdmin(person)) {
console.log(person.permissions); // TypeScript knows it's Admin
}
Interface vs Type Alias
| Feature | Interface | Type Alias |
|---|---|---|
| Object shapes | ✅ | ✅ |
| Extends/Inheritance | ✅ extends | ✅ intersection (&) |
| Declaration merging | ✅ | ❌ |
| Union types | ❌ | ✅ |
| Primitives | ❌ | ✅ |
| Tuples | ❌ | ✅ |
Use interfaces for object shapes, types for unions and primitives.
Working with Functions
// Function with typed parameters and return
function greet(name: string, greeting = "Hello"): string {
return `${greeting}, ${name}!`;
}
// Optional and default parameters
function createUser(
name: string,
email: string,
age?: number, // Optional
role = "user" // Default value
): User {
return { name, email, age, role };
}
// Rest parameters
📚 Learn More
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
// Function overloads
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
if (typeof value === "string") {
return value.trim();
}
return value.toFixed(2);
}
// Arrow functions with generics
const fetchData = async <T>(url: string): Promise<T> => {
const response = await fetch(url);
return response.json();
};
💡 Getting Started
- • Install:
npm install -D typescript - • Initialize:
npx tsc --init - • Use strict mode for maximum type safety
- • Start by renaming .js files to .ts
- • Add types incrementally, use 'any' sparingly
- • Enable noImplicitAny to catch untyped code