Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/typescript-exercises/typescript-exercises/llms.txt

Use this file to discover all available pages before exploring further.

When using JavaScript libraries that lack TypeScript declarations, you need to write .d.ts files to describe their types. These exercises teach you how to provide type safety for external modules.

Exercise 11: Basic Module Declarations

Scenario

You’re using str-utils, a JavaScript library for string manipulation, but it has no TypeScript declarations. You need to create type definitions so TypeScript understands the library’s API.

The Problem

import {
    strReverse,
    strToLower,
    strToUpper,
    strRandomize,
    strInvertCase
} from 'str-utils';  // ❌ Error: Cannot find module 'str-utils'

export const nameDecorators = [
    strReverse,    // any
    strToLower,    // any
    strToUpper,    // any
    strRandomize,  // any
    strInvertCase  // any
];
Without type declarations, TypeScript treats all imports as any, losing type safety.

Understanding Declaration Files

Declaration files (.d.ts) describe the shape of JavaScript code without implementation:
// declarations/str-utils/index.d.ts
declare module 'str-utils' {
    // Type declarations only, no implementation
    export const strReverse: (input: string) => string;
    export const strToUpper: (input: string) => string;
    // ...
}

Key Concepts

Ambient Modules

declare module tells TypeScript about external modules

Type Aliases

Avoid duplication by creating reusable type definitions

Solution

Create a declaration file at declarations/str-utils/index.d.ts:
// declarations/str-utils/index.d.ts
declare module 'str-utils' {
    // export const ...
    // export function ...
}
All the functions have the same signature: (input: string) => stringWithout alias:
export const strReverse: (input: string) => string;
export const strToLower: (input: string) => string;
export const strToUpper: (input: string) => string;
// Repetitive and error-prone
With alias:
type StrUtil = (input: string) => string;
export const strReverse: StrUtil;
export const strToLower: StrUtil;
export const strToUpper: StrUtil;
// DRY and maintainable

Using the Typed Library

Now TypeScript understands the library:
import { strReverse, strToUpper } from 'str-utils';

function logPerson(person: Person) {
    const randomDecorator = nameDecorators[
        Math.round(Math.random() * (nameDecorators.length - 1))
    ];
    const name = randomDecorator(person.name);  // Type-safe!
    console.log(`${name}, ${person.age}`);
}

// Type checking works
strReverse('hello');      // ✅ OK
strReverse(123);          // ❌ Error: Argument of type 'number' is not assignable
const result = strToUpper('test');  // result: string

Exercise 12: Advanced Generic Declarations

Scenario

You’re using stats, a library with functions for finding max, min, median, and average values. These functions are generic and work with any type, using comparator functions.

The Problem

import {
    getMaxIndex,
    getMaxElement,
    getMinIndex,
    getMinElement,
    getMedianIndex,
    getMedianElement,
    getAverageValue
} from 'stats';  // ❌ Error: Cannot find module 'stats'

const compareUsers = (a: User, b: User) => a.age - b.age;
const youngestUser = getMinElement(users, compareUsers);  // any

Understanding the Library

The library has three categories of functions:
1

Index Functions

Return the index of an element
getMaxIndex(users, compareUsers)    // Returns: number
getMinIndex(users, compareUsers)    // Returns: number
getMedianIndex(users, compareUsers) // Returns: number
2

Element Functions

Return the element itself or null
getMaxElement(users, compareUsers)    // Returns: User | null
getMinElement(users, compareUsers)    // Returns: User | null
getMedianElement(users, compareUsers) // Returns: User | null
3

Value Function

Return a computed numeric value
getAverageValue(users, (u) => u.age)  // Returns: number | null

Solution

Create a declaration file with generic types:
// declarations/stats/index.d.ts
declare module 'stats' {
    export function getMaxIndex(input: unknown, comparator: unknown): unknown;
}

Breaking Down the Solution

type Comparator<T> = (a: T, b: T) => number;
A comparator takes two items of the same type and returns:
  • Negative number if a < b
  • Zero if a === b
  • Positive number if a > b
Example:
const compareUsers: Comparator<User> = (a, b) => a.age - b.age;
const compareAdmins: Comparator<Admin> = (a, b) => a.age - b.age;
type GetIndex = <T>(input: T[], comparator: Comparator<T>) => number;
This is a generic function type that:
  1. Takes an array of any type T[]
  2. Takes a comparator for that type
  3. Returns the index as a number
All three index functions share this signature, so we define it once.
type GetElement = <T>(input: T[], comparator: Comparator<T>) => T | null;
Similar to GetIndex, but returns the element itself (or null if the array is empty).

Using the Typed Library

const users: User[] = [
    { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
    { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
    { type: 'user', name: 'Moses', age: 70, occupation: 'Desert guide' }
];

const compareUsers = (a: User, b: User) => a.age - b.age;

// All properly typed now
const youngestUser = getMinElement(users, compareUsers);  // User | null
const oldestIndex = getMaxIndex(users, compareUsers);     // number
const medianUser = getMedianElement(users, compareUsers); // User | null

// Type checking works
if (youngestUser) {
    console.log(youngestUser.name, youngestUser.age);  // ✅ OK
}

// Errors caught at compile time
getMinElement(users, (a, b) => a.name);  // ❌ Error: Type 'string' not assignable to 'number'

Generic Value Extraction

export const getAverageValue: <T>(
    input: T[], 
    getValue: (item: T) => number
) => number | null;

// Usage
const avgUserAge = getAverageValue(users, (u) => u.age);
// Type: number | null

const avgAdminAge = getAverageValue(admins, (a) => a.age);
// Type: number | null

console.log(`Average user age: ${avgUserAge} years`);

Declaration File Best Practices

Avoid Duplication

Use type aliases for repeated signatures

Match Runtime Behavior

Declarations should accurately reflect the JavaScript API

Be Specific

Use precise types rather than any or unknown

Document Edge Cases

Use | null or | undefined when functions can return these

Common Declaration Patterns

declare module 'my-lib' {
    export const VERSION: string;
    export const CONFIG: { timeout: number; retries: number; };
}

What You Learned

Use declare module 'name' to define types for external JavaScript modules
Write generic type definitions that work with any type while maintaining safety
Reduce duplication by defining reusable type aliases
Use T | null to represent functions that might not return a value
For popular libraries, check DefinitelyTyped before writing your own declarations. Many community-maintained types are available via @types/* packages.

Next Steps

Move on to Module Augmentation to learn how to extend existing type declarations.

Additional Resources