Skip to content

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 }
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.