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
Practical TypeScript upgrades
- • Add types to one API module and turn on
strict; note any runtime bugs caught during the refactor. - • Introduce
ReturnTypeandParametersutility types to keep your APIs aligned. - • Define discriminated unions for server responses (success/error) and exhaustively switch on
kind. - • Configure ESLint to forbid implicit
anyand to require explicit return types on public functions.
📚 Continue Learning
Expand your advanced JavaScript skills with these related tutorials:
TypeScript Tutorial
Add type safety to your JavaScript — learn generics, utility types, and strict typing.
JavaScript Performance
Optimize rendering, memory, and runtime performance in JS apps.
React Tutorial
Apply your advanced JS knowledge to build modern React applications.
Algorithms & Data Structures
Master algorithms and ace technical interviews with JavaScript.