TypeScript is smart! It can often figure out types automatically through type inference. Generics allow you to write flexible, reusable code that works with multiple types while maintaining type safety.
TypeScript automatically infers types based on values and context:
// TypeScript infers type from initial value
let message = "Hello TypeLand"; // inferred as string
let count = 42; // inferred as number
let isActive = true; // inferred as boolean
// Type inference with arrays
let numbers = [1, 2, 3]; // inferred as number[]
let mixed = [1, "two", true]; // inferred as (string | number | boolean)[]
// Type inference with objects
let player = {
name: "Hero",
level: 5
}; // inferred as { name: string; level: number; }
// Function return type inference
function add(a: number, b: number) {
return a + b; // Return type inferred as number
}
const result = add(5, 3); // result inferred as number
While inference is powerful, explicit types improve readability and catch errors earlier:
// Good: Inferred type is obvious
let age = 25;
// Better: Explicit type for clarity and safety
let userAge: number; // Declared but not initialized
userAge = 30;
// Good: Function parameters MUST be annotated
function greet(name: string) {
return `Hello, ${name}`;
}
// Better: Explicit return type for documentation
function greet(name: string): string {
return `Hello, ${name}`;
}
// Variables that will change type context
let data; // type: any (no inference)
data = "string";
data = 123; // No error with 'any'
// Better: Specify expected type
let data: string;
data = "string";
// data = 123; // ✗ Error!
Generics let you write code that works with multiple types while preserving type information:
// Generic function - works with any type
function identity<T>(value: T): T {
return value;
}
// TypeScript infers the type parameter
const num = identity(42); // T is number
const str = identity("hello"); // T is string
const bool = identity(true); // T is boolean
// Or specify explicitly
const result = identity<string>("TypeScript");
// Without generics, you'd need:
function identityNumber(value: number): number { return value; }
function identityString(value: string): string { return value; }
// ... repetitive!
Arrays already use generics behind the scenes:
// Generic function that works with any array type
function getFirstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const numbers = [1, 2, 3];
const first = getFirstElement(numbers); // type: number | undefined
const names = ["Alice", "Bob"];
const firstName = getFirstElement(names); // type: string | undefined
// Generic function to reverse an array
function reverseArray<T>(arr: T[]): T[] {
return arr.slice().reverse();
}
const reversed = reverseArray([1, 2, 3]); // [3, 2, 1], type: number[]
Interfaces can also be generic:
// Generic interface
interface Box<T> {
value: T;
}
// Use with different types
const numberBox: Box<number> = { value: 42 };
const stringBox: Box<string> = { value: "hello" };
const playerBox: Box<{ name: string; level: number }> = {
value: { name: "Hero", level: 5 }
};
// Generic interface with methods
interface Container<T> {
items: T[];
add(item: T): void;
remove(item: T): boolean;
find(predicate: (item: T) => boolean): T | undefined;
}
// Implementing generic interface
class Inventory<T> implements Container<T> {
items: T[] = [];
add(item: T): void {
this.items.push(item);
}
remove(item: T): boolean {
const index = this.items.indexOf(item);
if (index > -1) {
this.items.splice(index, 1);
return true;
}
return false;
}
find(predicate: (item: T) => boolean): T | undefined {
return this.items.find(predicate);
}
}
const stringInventory = new Inventory<string>();
stringInventory.add("sword");
stringInventory.add("shield");
// API response wrapper
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
type User = { id: number; name: string; };
type Product = { id: number; title: string; price: number; };
function fetchData<T>(url: string): Promise<ApiResponse<T>> {
// Fetch implementation
return fetch(url).then(res => res.json());
}
// Type-safe API calls
const userResponse = fetchData<User>("/api/user/1");
const productResponse = fetchData<Product>("/api/product/5");
// Key-value storage
class Storage<T> {
private items = new Map<string, T>();
set(key: string, value: T): void {
this.items.set(key, value);
}
get(key: string): T | undefined {
return this.items.get(key);
}
}
const stringStorage = new Storage<string>();
stringStorage.set("name", "Alice");
const name = stringStorage.get("name"); // string | undefined
<T> to work with any type