Typing Common JS Patterns¶
JavaScript is flexible, expressive, and sometimes chaotic. TypeScript’s job is not to restrict that flexibility, but to formalize it so your code becomes safer, clearer, and easier to maintain.
In this lesson, we’ll take common JavaScript patterns and show how to type them idiomatically in TypeScript.
Objects‑as‑Dictionaries¶
JavaScript developers frequently use objects as “maps” or “dictionaries”:
const scores = {
alice: 10,
bob: 20
}
In TypeScript, you express this pattern using index signatures.
Basic dictionary¶
const scores: { [key: string]: number } = {
alice: 10,
bob: 20
}
More readable version using Record¶
const scores: Record<string, number> = {
alice: 10,
bob: 20
}
Dictionary of objects¶
type User = { id: number; name: string }
const users: Record<string, User> = {
a1: { id: 1, name: "Alice" },
b2: { id: 2, name: "Bob" }
}
Why this matters¶
- Prevents accidental wrong value types
- Gives autocomplete for dictionary values
- Makes dynamic key access safe
Functions Returning Objects¶
JavaScript functions often return objects—especially factory functions, configuration builders, and API wrappers.
Basic typed return object¶
function createUser(name: string, age: number) {
return { name, age }
}
TypeScript infers the return type automatically:
// { name: string; age: number }
Explicit return type (recommended for public APIs)¶
type User = {
name: string
age: number
}
function createUser(name: string, age: number): User {
return { name, age }
}
Functions returning different shapes¶
Use union types:
type Success = { ok: true; data: string }
type Failure = { ok: false; error: string }
function load(): Success | Failure {
if (Math.random() > 0.5) {
return { ok: true, data: "Loaded!" }
}
return { ok: false, error: "Failed" }
}
This is the foundation of discriminated unions, a powerful TS pattern you’ll use later.
Higher‑Order Functions¶
Higher‑order functions (HOFs) are everywhere in JavaScript: map, filter, middleware, decorators, event wrappers, etc.
TypeScript handles them beautifully.
Typing a simple HOF¶
function withLogging<T>(fn: (value: T) => T) {
return (value: T) => {
console.log("Calling with:", value)
return fn(value)
}
}
Usage:
const double = (n: number) => n * 2
const loggedDouble = withLogging(double)
loggedDouble(10) // OK
loggedDouble("hi") // ❌ Error
HOF with multiple parameters¶
function wrap<A, B>(fn: (a: A, b: B) => void) {
return (a: A, b: B) => {
console.log("Wrapped call")
fn(a, b)
}
}
Why this matters¶
- HOFs preserve parameter and return types
- TypeScript infers generics automatically
- You get full autocomplete inside wrapped functions
Callback Patterns¶
Callbacks are everywhere in JS: event handlers, async operations, array methods, custom utilities.
TypeScript lets you type them precisely.
Basic callback type¶
function repeat(n: number, callback: (i: number) => void) {
for (let i = 0; i < n; i++) {
callback(i)
}
}
Usage:
repeat(3, i => console.log(i))
Callback returning a value¶
function mapNumbers<T>(arr: number[], fn: (n: number) => T): T[] {
return arr.map(fn)
}
Callback with object parameters¶
type User = { id: number; name: string }
function forEachUser(users: User[], fn: (user: User) => void) {
users.forEach(fn)
}
Callback with multiple arguments¶
function filterWithIndex<T>(
arr: T[],
fn: (value: T, index: number) => boolean
): T[] {
return arr.filter((value, index) => fn(value, index))
}
Why this matters¶
- Prevents incorrect callback signatures
- Ensures callback return values are used correctly
- Makes array methods safer and more expressive
Putting It All Together¶
Here’s a realistic example combining all four patterns:
type User = { id: number; name: string }
// Dictionary of users
const userMap: Record<string, User> = {}
// Higher-order function
function withTiming<T extends (...args: any[]) => any>(fn: T) {
return (...args: Parameters<T>): ReturnType<T> => {
const start = performance.now()
const result = fn(...args)
console.log("Took", performance.now() - start, "ms")
return result
}
}
// Function returning objects
function createUser(id: number, name: string): User {
return { id, name }
}
// Callback pattern
function processUsers(users: User[], fn: (u: User) => void) {
users.forEach(fn)
}
This is the kind of code you’ll write constantly in real TypeScript projects.
Summary¶
In this lesson, you learned how to type four extremely common JavaScript patterns:
1. Objects‑as‑dictionaries¶
- Use index signatures or
Record<K, V> - Great for dynamic key/value maps
2. Functions returning objects¶
- Type inference works, but explicit return types improve clarity
- Supports unions for flexible APIs
3. Higher‑order functions¶
- Use generics to preserve parameter and return types
- Essential for decorators, middleware, and functional utilities
4. Callback patterns¶
- Type callback signatures precisely
- Works beautifully with array methods and async code
These patterns form the backbone of real‑world TypeScript development.