> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Functions & Types

> Function Types, Overloads, and Type Guards

# Functions & Types

Functions are the building blocks of any application. TypeScript makes functions safer by typing parameters, return values, and function signatures themselves. Think of typed function parameters as a form with labeled fields -- instead of accepting "anything" and hoping the caller passes the right data, you specify exactly what each slot expects. The compiler then rejects any call that does not match the form.

***

## 1. Function Type Annotations

### Parameter and Return Types

```typescript theme={null}
// Annotate parameters and return type
function add(a: number, b: number): number {
  return a + b;
}

// Arrow function
const multiply = (a: number, b: number): number => a * b;

// Return type is often inferred
const divide = (a: number, b: number) => a / b; // Returns number
```

### void and never

```typescript theme={null}
// void - function completes but does not return a meaningful value
// The caller should not use the return value for anything
function log(message: string): void {
  console.log(message);
}

// never - function NEVER reaches its end -- it either throws or loops forever
// This is a signal to both humans and the compiler: "code after this is unreachable"
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {} // Execution never moves past this point
}
```

**Practical tip**: The `never` type is especially useful in exhaustive switch statements. If you handle all cases, the default branch's variable becomes `never` -- and if you add a new case later without handling it, the compiler errors. This is a powerful safety net for growing codebases.

***

## 2. Optional and Default Parameters

### Optional Parameters

```typescript theme={null}
function greet(name: string, greeting?: string): string {
  return `${greeting || 'Hello'}, ${name}!`;
}

greet('Alice');           // 'Hello, Alice!'
greet('Alice', 'Hi');     // 'Hi, Alice!'
```

### Default Parameters

```typescript theme={null}
function greet(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}!`;
}

greet('Alice');           // 'Hello, Alice!'
greet('Alice', 'Hi');     // 'Hi, Alice!'
```

<Tip>
  **Optional vs Default**: Optional parameters can be `undefined`. Default parameters have a fallback value. Default parameters are usually preferred.
</Tip>

### Rest Parameters

```typescript theme={null}
function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2, 3);        // 6
sum(1, 2, 3, 4, 5);  // 15
```

***

## 3. Function Type Expressions

Define the type of a function itself -- not what it does, but what its shape looks like. This is like defining a "job description" that any function matching the signature can fill.

```typescript theme={null}
// Type alias for a function -- any function with this signature qualifies
type MathOperation = (a: number, b: number) => number;

const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;

// As a parameter
function calculate(a: number, b: number, operation: MathOperation): number {
  return operation(a, b);
}

calculate(10, 5, add);      // 15
calculate(10, 5, subtract); // 5
```

### Call Signatures

```typescript theme={null}
// Object type with call signature
type DescribableFunction = {
  description: string;
  (x: number): number;
};

function double(x: number): number {
  return x * 2;
}
double.description = 'Doubles the input';

const fn: DescribableFunction = double;
```

### Construct Signatures

```typescript theme={null}
// For classes/constructors
type UserConstructor = {
  new (name: string): User;
};

class User {
  constructor(public name: string) {}
}

function createUser(ctor: UserConstructor, name: string): User {
  return new ctor(name);
}

const user = createUser(User, 'Alice');
```

***

## 4. Function Overloads

Define multiple function signatures for different parameter combinations. Overloads let you express something that a single signature cannot: "if you pass a string, you get back X; if you pass a number, you get back Y." The compiler uses the overload signatures (the declarations) to check your call sites, while the implementation signature (the body) handles the actual logic.

```typescript theme={null}
// Overload signatures
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;

// Implementation signature
function format(value: string | number | Date): string {
  if (typeof value === 'string') {
    return value.toUpperCase();
  } else if (typeof value === 'number') {
    return value.toFixed(2);
  } else {
    return value.toISOString();
  }
}

format('hello');           // 'HELLO'
format(3.14159);          // '3.14'
format(new Date());       // '2024-01-01T00:00:00.000Z'
```

### More Complex Overloads

```typescript theme={null}
// Return different types based on input
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'span'): HTMLSpanElement;
function createElement(tag: 'input'): HTMLInputElement;
function createElement(tag: string): HTMLElement;

function createElement(tag: string): HTMLElement {
  return document.createElement(tag);
}

const div = createElement('div');    // HTMLDivElement
const span = createElement('span');  // HTMLSpanElement
const input = createElement('input'); // HTMLInputElement
const custom = createElement('custom'); // HTMLElement
```

<Warning>
  **Overloads Rule**: The implementation signature must be compatible with all overload signatures but is not visible to callers.
</Warning>

***

## 5. Generic Functions

Write functions that work with any type while maintaining type safety. Generics are like templates or molds -- you define the shape once, and the caller fills in the specific type. Without generics, you would have to choose between type safety (writing separate functions for each type) and flexibility (using `any` and losing all type information).

### Basic Generics

```typescript theme={null}
// Without generics -- the return type is 'any', so TypeScript forgets what went in
function identity(value: any): any {
  return value;
}

// With generics -- T is a placeholder that the caller fills in
// Whatever type goes in, the same type comes out. Type information is preserved!
function identity<T>(value: T): T {
  return value;
}

const num = identity(42);        // number
const str = identity('hello');   // string
const obj = identity({ x: 1 });  // { x: number }
```

### Multiple Type Parameters

```typescript theme={null}
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const p1 = pair('hello', 42);           // [string, number]
const p2 = pair(true, { name: 'Alice' }); // [boolean, { name: string }]
```

### Generic Constraints

```typescript theme={null}
// Constrain T to have a length property.
// "extends" here means "must satisfy this shape" -- it is a contract requirement.
// Without this constraint, TypeScript would error on value.length because
// not every type T has a .length property.
function getLength<T extends { length: number }>(value: T): number {
  return value.length;
}

getLength('hello');     // 5 -- strings have .length
getLength([1, 2, 3]);   // 3 -- arrays have .length
getLength({ length: 10 }); // 10 -- any object with .length works
// getLength(123);      // Error: number does not have .length
```

### keyof Constraint

```typescript theme={null}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'Alice', age: 25 };

getProperty(user, 'name'); // string
getProperty(user, 'age');  // number
// getProperty(user, 'email'); // ❌ Error: 'email' is not a key of user
```

***

## 6. Type Guards

Functions that narrow types at runtime. Type guards bridge the gap between TypeScript's compile-time type system and JavaScript's runtime behavior. They let you write conditional logic that both the runtime *and* the compiler understand.

### typeof Guards

```typescript theme={null}
function processValue(value: string | number): string {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return value.toString();
}
```

### instanceof Guards

```typescript theme={null}
class Dog {
  bark() { console.log('Woof!'); }
}

class Cat {
  meow() { console.log('Meow!'); }
}

function makeSound(animal: Dog | Cat): void {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}
```

### Custom Type Guards (Type Predicates)

Custom type guards are one of the most powerful patterns in TypeScript. The `is` keyword in the return type tells the compiler: "if this function returns true, treat the parameter as the specified type." This lets you encapsulate complex runtime checks in a reusable function.

```typescript theme={null}
interface Fish {
  swim: () => void;
}

interface Bird {
  fly: () => void;
}

// The return type "animal is Fish" is a type predicate.
// It tells TypeScript: "when this returns true, narrow 'animal' to Fish"
function isFish(animal: Fish | Bird): animal is Fish {
  return (animal as Fish).swim !== undefined;
}

function move(animal: Fish | Bird): void {
  if (isFish(animal)) {
    animal.swim(); // TypeScript knows it is Fish -- .swim() is safe
  } else {
    animal.fly();  // TypeScript knows it is Bird -- .fly() is safe
  }
}
```

**Practical tip**: Custom type guards are essential for validating API responses. You can write an `isUser(data: unknown): data is User` function that validates the shape of external data, giving you both runtime safety and compile-time narrowing in one step.

### Discriminated Unions

```typescript theme={null}
// Discriminated unions use a common "tag" property (here: 'kind') to distinguish variants.
// Each branch of the switch narrows the type to the specific variant.
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      // TypeScript narrows: shape is { kind: 'circle'; radius: number }
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      // TypeScript narrows: shape is { kind: 'rectangle'; width: number; height: number }
      return shape.width * shape.height;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    // If you add a new shape variant later (e.g., 'pentagon') and forget to handle it,
    // TypeScript will error here if you add exhaustiveness checking (see Advanced Types chapter).
  }
}

const circle: Shape = { kind: 'circle', radius: 5 };
getArea(circle); // 78.54
```

***

## 7. Assertion Functions

Functions that throw if a condition is false, narrowing the type for all code that follows. Unlike type predicates (which narrow inside an `if` block), assertion functions narrow the type for the *rest of the function scope* -- everything below the assertion call benefits from the narrowed type.

```typescript theme={null}
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Value must be a string');
  }
}

function processInput(input: unknown): string {
  assertIsString(input);
  // TypeScript knows input is string after assertion
  return input.toUpperCase();
}
```

### Non-null Assertion

```typescript theme={null}
function assertDefined<T>(value: T | null | undefined): asserts value is T {
  if (value === null || value === undefined) {
    throw new Error('Value must be defined');
  }
}

function getUser(id: string): User | null {
  // ... fetch user
  return null;
}

const user = getUser('123');
assertDefined(user);
// TypeScript knows user is User (not null) after this point
console.log(user.name);
```

***

## 8. this Parameter

Explicitly type the `this` context.

```typescript theme={null}
interface User {
  name: string;
  greet(this: User): string;
}

const user: User = {
  name: 'Alice',
  greet() {
    return `Hello, ${this.name}!`;
  }
};

user.greet(); // 'Hello, Alice!'

// This would error because 'this' context is wrong
const greet = user.greet;
// greet(); // ❌ Error: 'this' context of type 'void' is not assignable
```

### This Parameter in Classes

```typescript theme={null}
class Counter {
  count = 0;

  // Arrow function preserves 'this'
  increment = () => {
    this.count++;
  };

  // Regular method with explicit this type
  decrement(this: Counter): void {
    this.count--;
  }
}
```

***

## 9. Callback Types

Type callbacks precisely.

```typescript theme={null}
// Simple callback
type Callback = (result: string) => void;

function fetchData(callback: Callback): void {
  setTimeout(() => {
    callback('Data loaded');
  }, 1000);
}

// Callback with error handling
type NodeCallback<T> = (error: Error | null, result: T | null) => void;

function readFile(path: string, callback: NodeCallback<string>): void {
  // ...
}

// Event handler
type EventHandler<E extends Event> = (event: E) => void;

const handleClick: EventHandler<MouseEvent> = (event) => {
  console.log(event.clientX, event.clientY);
};
```

***

## 10. Practical Examples

### API Function Types

```typescript theme={null}
interface User {
  id: number;
  name: string;
  email: string;
}

type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

async function fetchUsers(): Promise<ApiResponse<User[]>> {
  const response = await fetch('/api/users');
  return response.json();
}
```

### Event Emitter Pattern

```typescript theme={null}
type EventMap = {
  login: { userId: string };
  logout: { userId: string };
  error: { message: string; code: number };
};

type EventCallback<T> = (data: T) => void;

class EventEmitter<T extends Record<string, any>> {
  private listeners = new Map<keyof T, Set<EventCallback<any>>>();

  on<K extends keyof T>(event: K, callback: EventCallback<T[K]>): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(callback);
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    this.listeners.get(event)?.forEach((callback) => callback(data));
  }
}

const emitter = new EventEmitter<EventMap>();

emitter.on('login', (data) => {
  console.log(`User ${data.userId} logged in`);
});

emitter.emit('login', { userId: '123' }); // Type-safe!
```

***

## Summary

| Concept         | Example                                            |
| :-------------- | :------------------------------------------------- |
| Parameter Types | `function add(a: number, b: number)`               |
| Return Types    | `function add(a, b): number`                       |
| Optional Params | `function greet(name: string, greeting?: string)`  |
| Default Params  | `function greet(name: string, greeting = 'Hello')` |
| Rest Params     | `function sum(...nums: number[])`                  |
| Function Types  | `type Fn = (x: number) => number`                  |
| Overloads       | Multiple signatures + implementation               |
| Generics        | `function identity<T>(value: T): T`                |
| Type Guards     | `function isFish(x): x is Fish`                    |
| Assertions      | `function assert(x): asserts x is string`          |

Next, we'll explore objects and interfaces in depth!

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="Q: When would you use function overloads versus a union return type? What trade-offs are you making?">
    **Strong Answer:**

    This is a design decision that many TypeScript developers get wrong because they default to overloads when simpler alternatives exist.

    * **Use overloads when**: The return type depends on the input type, and you want the caller to get the precise return type based on what they passed. Classic example: `createElement('div')` returns `HTMLDivElement`, `createElement('input')` returns `HTMLInputElement`. A single signature `createElement(tag: string): HTMLElement` loses this precision -- the caller always gets the generic `HTMLElement` and must cast. Overloads preserve the mapping from specific input to specific output.
    * **Use union return types when**: The function legitimately returns different types regardless of input, or the relationship between input and output is not 1-to-1. For example, `function parse(input: string): number | Error` -- every input is a string, and the output depends on the content, not the type. Overloads would not help here because there is no type-level distinction between "a string that parses" and "a string that does not."
    * **The trade-off**: Overloads add complexity. The implementation signature must be compatible with all overload signatures, which often means the implementation body is full of type assertions and conditional logic. If you have 5 overloads, you have 5 signatures to maintain. Generics with conditional types often achieve the same result with less code: `function identity<T extends string | number>(value: T): T extends string ? string : number` gives you input-dependent return types without overloads.
    * **My rule of thumb**: If you have 2-3 distinct input-output mappings, overloads are clear and readable. If you have more, generics with conditional types are usually cleaner. If the return type does not depend on the input type, just use a union.

    **Follow-up: What is the gotcha with the implementation signature in overloads?**

    The implementation signature is not visible to callers -- only the overload signatures are. This means if the implementation accepts `string | number | Date`, but you only have overloads for `string` and `number`, callers cannot pass a `Date` even though the implementation handles it. The overload signatures act as the public API; the implementation signature is an internal detail. A common mistake is writing an implementation signature that is too narrow, causing one of the overloads to be incompatible, which TypeScript flags as an error.
  </Accordion>

  <Accordion title="Q: Explain assertion functions (asserts keyword) versus type predicates (is keyword). When do you reach for each?">
    **Strong Answer:**

    Both are tools for narrowing types, but they work in fundamentally different scopes and have different failure modes.

    * **Type predicates (`is`)**: Used in `if` conditions. `function isString(x: unknown): x is string` narrows the type inside the `if` block where the return value is `true`. The function returns a boolean -- it does not throw. The caller decides what to do when the check fails (return early, show an error, use a default). Narrowing is limited to the `if` block.
    * **Assertion functions (`asserts`)**: Narrow the type for the entire remaining scope. `function assertIsString(x: unknown): asserts x is string` throws if the condition fails. After the call, TypeScript treats `x` as `string` for every line that follows -- not just inside an `if` block. The function either succeeds (and narrows) or throws (and execution stops). There is no "else" path.
    * **When to use type predicates**: Filtering, branching, optional handling. `const users = items.filter(isUser)` uses a type predicate to narrow the array type. `if (isAdmin(user)) { showAdminPanel(); }` -- the non-admin path is a valid execution path, not an error.
    * **When to use assertion functions**: Validation at boundaries where invalid data is a bug, not an expected condition. `assertDefined(config.apiKey)` at application startup -- if the API key is missing, crashing immediately with a clear error message is the correct behavior. Assertion functions are also useful in test setup: `assertIsHTMLElement(element)` before interacting with it.
    * **The danger of assertion functions**: If your assertion is wrong (the function returns without throwing when it should have thrown), TypeScript trusts you and narrows the type incorrectly. This is a silent type safety violation. Type predicates are safer because they return a boolean that can be unit-tested; assertion functions are harder to test for the "should have thrown but did not" case.

    **Follow-up: Can you use assertion functions with array methods like filter?**

    No. `Array.prototype.filter` expects a function returning `boolean`, and assertion functions return `void` (they assert or throw). You must use type predicates with `filter`: `items.filter((item): item is User => isUser(item))`. Assertion functions are for imperative code paths where you want to "gate" execution, not for declarative operations like filtering or mapping.
  </Accordion>

  <Accordion title="Q: What are generic constraints in functions, and what problem does 'keyof' solve that a plain generic cannot?">
    **Strong Answer:**

    Generic constraints are how you tell TypeScript "this type parameter is not completely open -- it must satisfy certain requirements." Without constraints, generics are too permissive; with them, you get precision.

    * **The problem without constraints**: `function getLength<T>(value: T): number { return value.length; }` fails because TypeScript does not know that `T` has a `length` property. `T` could be `number`, which has no `length`. The compiler correctly rejects this.
    * **The solution with `extends`**: `function getLength<T extends { length: number }>(value: T): number` tells TypeScript: "T can be any type, as long as it has a `length` property of type `number`." Now strings, arrays, and any object with `length` work, but numbers are rejected at the call site.
    * **Where `keyof` elevates this**: `function getProperty<T, K extends keyof T>(obj: T, key: K): T[K]` constrains `K` to be one of the actual keys of `T`. If `T` is `{ name: string, age: number }`, then `K` can only be `'name'` or `'age'`. The return type `T[K]` is automatically the type of that specific property -- if you pass `'name'`, you get `string`; if you pass `'age'`, you get `number`. Without `keyof`, you would have to accept `key: string` and return `any`, losing all type safety.
    * **Real-world example**: A type-safe `pick` function: `function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>`. This guarantees at compile time that you can only pick keys that exist on the object, and the return type precisely reflects which keys you picked. This is impossible without the `keyof` constraint.

    **Follow-up: What is the difference between `T extends keyof U` and `K extends keyof T` in terms of how the types are resolved?**

    `K extends keyof T` constrains `K` to be a union of string literal types representing the keys of `T`. When you call `getProperty(user, 'name')`, TypeScript infers `T = typeof user` and `K = 'name'`. The return type `T[K]` resolves to `T['name']` which is `string`. If `T` is generic (not yet resolved), `T[K]` remains a deferred type that resolves when `T` is known. The key insight is that `keyof` produces a union of literal types at the type level, not a runtime value -- it is purely a compile-time constraint that prevents invalid property access.
  </Accordion>

  <Accordion title="Q: How does TypeScript handle the 'this' parameter in functions, and why does it matter for event handlers and class methods?">
    **Strong Answer:**

    The `this` parameter is TypeScript's solution to one of JavaScript's most notorious footguns: the value of `this` depends on how a function is called, not how it is defined.

    * **The problem**: In JavaScript, if you extract a method from an object and call it standalone, `this` is `undefined` (strict mode) or the global object (sloppy mode). `const greet = user.greet; greet();` -- `this.name` is `undefined` inside `greet` because the method lost its binding. This is the root cause of countless bugs in event handlers: `button.addEventListener('click', user.handleClick)` -- inside `handleClick`, `this` is the button element, not the user object.
    * **TypeScript's solution**: The `this` parameter is a fake first parameter that is erased at compile time (like type annotations). `greet(this: User): string` tells TypeScript: "this function must be called with `this` bound to a `User`." If you try to call it without the right context, TypeScript errors. This catches the detached method bug at compile time.
    * **In classes, arrow functions are the pragmatic fix**: `increment = () => { this.count++; }` captures `this` lexically from the class instance. It is safe to pass as a callback because `this` always refers to the instance. The trade-off is that arrow methods are per-instance (each instance gets its own copy), not on the prototype. For a class with 10,000 instances, that is 10,000 copies of the function instead of one shared prototype method. For UI components (where instances are few), this is irrelevant. For data classes in tight loops, it can matter.
    * **`strictBindCallApply` config**: When enabled, TypeScript checks that `Function.prototype.bind`, `.call`, and `.apply` are called with the correct types. `greet.call({ name: 42 })` would error because `{ name: 42 }` does not match the `this: User` parameter. Without this flag, these calls are unchecked.

    **Follow-up: When would you choose an explicit `this` parameter over an arrow function in a class?**

    When you want the method on the prototype (shared across instances) and you control all call sites. For a library API where you document "call this method on the object, do not extract it," the `this` parameter provides compile-time enforcement without the per-instance memory cost. Arrow functions are better when the method will be passed as a callback (event handlers, React callbacks, Promise chains) because you cannot control how the caller invokes it. The judgment call is: "will this method ever be detached from its object?" If yes, arrow function. If no, prototype method with `this` parameter.
  </Accordion>
</AccordionGroup>
