Think about playlists for a sec. Some have a strict order, like a band's setlist for a concert. Others are just a pile of songs you hit shuffle on. TypeScript has tools for both kinds.
Typed arrays are your shuffle playlists, tuples are your strict setlists, and utility types are shortcuts for patterns you'd otherwise type out over and over (and over).
Typed Arrays
You've already seen the basics of typed arrays. Let's go a lil deeper.
Tuples are arrays with a set number of items, where every position has its own specific type.
Example
// A tuple with exactly [string, number]
let coordinates: [number, number] = [40.7128, -74.0060];
// Each position has its own type
let userInfo: [string, number, boolean] = ["Alice", 28, true];
// TypeScript enforces the exact types at each position
coordinates = [10, 20]; // OK
coordinates = [10, "twenty"]; // Error! string is not number
coordinates = [10, 20, 30]; // Error! Too many elements
// Accessing tuple elements gives you the right type
const lat = coordinates[0]; // type is number
const lng = coordinates[1]; // type is number
Named Tuples (for clarity)
Example
// Named tuple elements make the code self-documenting
type Point = [x: number, y: number];
type HttpResponse = [status: number, body: string, headers: Record<string, string>];
const point: Point = [10, 25];
const response: HttpResponse = [200, "OK", { "content-type": "application/json" }];
The Record Type
Record makes an object type where every key is one type and every value is another.
Example
// Record<KeyType, ValueType>
type Scores = Record<string, number>;
const playerScores: Scores = {
alice: 150,
bob: 200,
charlie: 175
};
// Useful for dictionaries and lookup tables
type StatusMessages = Record<number, string>;
const httpMessages: StatusMessages = {
200: "OK",
404: "Not Found",
500: "Internal Server Error"
};
Utility Types: Partial, Required, Pick, Omit
TypeScript ships with built-in utility types that reshape types you already have. They save you from writing nearly-identical interfaces again and again, which gets old FAST.
Partial - Make all properties optional
Example
interface User {
name: string;
email: string;
age: number;
}
// Partial<User> makes everything optional
type UpdateData = Partial<User>;
// Now you can update just one field
function updateUser(id: number, updates: Partial<User>) {
// updates could be { name: "New Name" } or { age: 30 } or both
}
updateUser(1, { name: "Alice" }); // OK - only updating name
updateUser(1, { age: 30, email: "a@b" }); // OK - updating age and email
Required - Make all properties required
Example
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
// Required<Config> makes everything mandatory
type FullConfig = Required<Config>;
const config: FullConfig = {
host: "localhost", // Must provide all three
port: 3000,
debug: false
};
Pick - Select specific properties
Example
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Describe the properties a public response should contain
type PublicUser = Pick<User, "id" | "name" | "email">;
// PublicUser only describes id, name, and email
const displayUser: PublicUser = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
Pick and Omit change TypeScript types, not objects at runtime. Before sending a user to a browser, build a new object with only approved fields. Do not cast a full database row to PublicUser and assume its password disappeared.
// Remove properties you don't want
type CreateUserInput = Omit<User, "id" | "createdAt">;
// CreateUserInput has name, email, password (no id or createdAt)
const newUser: CreateUserInput = {
name: "Bob",
email: "bob@example.com",
password: "secure123"
};
Mapped Types (Preview)
Mapped types let you transform every single property in a type. Fun fact, the utility types up above are actually built with mapped types.
Example
// This is how Partial works under the hood
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Make all properties readonly (TypeScript already ships this one as Readonly<T>)
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Make all property values strings
type Stringify<T> = {
[K in keyof T]: string;
};
interface User {
name: string;
age: number;
}
type StringUser = Stringify<User>;
// { name: string; age: string }
Putting It All Together
Here's a realistic example that mixes all of this together.
Example
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
inStock: boolean;
}
// For creating products (no id yet)
type CreateProduct = Omit<Product, "id">;
// For updating products (all fields optional)
type UpdateProduct = Partial<Omit<Product, "id">>;
// For product listings (just what the user sees)
type ProductCard = Pick<Product, "id" | "name" | "price" | "inStock">;
// For a price lookup table
type PriceLookup = Record<string, number>;
const prices: PriceLookup = {
"widget": 9.99,
"gadget": 19.99,
"doohickey": 4.99
};
TL;DR
Typed arrays make sure every item matches a specific type
Tuples have a fixed length and a specific type at each position
Record<K, V> makes dictionary-style object types
Partial<T> makes every property optional (handy for updates)
Pick<T, Keys> grabs specific properties out of a type
Omit<T, Keys> drops specific properties from a type
Utility types save you from rewriting nearly the same interface again
What's Next?
You've got a solid handle on TypeScript's type system now. Next up you'll set up a real TypeScript project from scratch, configure the compiler, and learn the tsconfig.json options that actually matter.