JavaScript Core/
Lesson

You've learned about types, interfaces, and type aliases. Now let's apply them to functions, the workhorses of any program. Then we'll explore genericsWhat is generics?A way to write code that works with any type of data (numbers, strings, etc.) without duplicating the logic for each type., one of TypeScript's most powerful features.

Typing function parameters and returns

In TypeScript, functions can specify types for their parameters and return values.

// Function declaration with types
function greet(name: string): string {
  return `Hello, ${name}!`;
}

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

Explicit return types act as a contract the function must fulfill:

// Without explicit return type - what does this return?
function fetchUser(id: number) {
  return database.getUser(id);
}

// With explicit return type - crystal clear
function fetchUser(id: number): Promise<User> {
  return database.getUser(id);
}
SyntaxUse CaseExample
function fn(): TypeNamed functionsfunction add(a: number, b: number): number
const fn = (): Type =>Arrow functionsconst add = (a: number, b: number): number => ...
type Fn = () => TypeReusable function typestype Handler = (e: Event) => void
02

Optional and default parameters

// Optional parameter - might be undefined
function greet(name: string, greeting?: string): string {
  const g = greeting || 'Hello';
  return `${g}, ${name}!`;
}

// Default parameter - always has a value
function greetDefault(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}!`;
}

greet('Alice');                     // "Hello, Alice!"
greetDefault('Bob', 'Hi');          // "Hi, Bob!"
03

Introduction to genericsWhat is generics?A way to write code that works with any type of data (numbers, strings, etc.) without duplicating the logic for each type.

Generics let you write flexible, reusable code that works with any type while maintaining type safety. They're like templates that adapt to whatever data you provide.

// Without generics - use any (loses type safety)
function identity(arg: any): any {
  return arg;
}

// With generics - reusable AND type-safe
function identity<T>(arg: T): T {
  return arg;
}

const result1 = identity('hello');  // TypeScript infers: string
const result2 = identity(42);       // TypeScript infers: number

Generic constraints

Sometimes you need to limit what types can be used with a generic. Constraints let you specify requirements:

// T must have a 'length' property
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length > b.length ? a : b;
}

longest([1, 2, 3], [1, 2]);     // OK - arrays have length
longest('hello', 'world!');      // OK - strings have length
// longest(1, 2);               // Error: number doesn't have 'length'

The keyof constraint

keyof creates a union of all property keys. Combined with generics, it enables type-safe property access:

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

const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name');  // TypeScript knows: string
const age = getProperty(user, 'age');    // TypeScript knows: number
// getProperty(user, 'email');           // Error: 'email' is not a key
04

Generic interfaces and classes

// Generic interface - reusable API response shape
interface APIResponse<T> {
  data: T;
  status: number;
  error: string | null;
}

const userResponse: APIResponse<User> = {
  data: { id: 1, name: 'John' },
  status: 200,
  error: null
};

// Generic class - type-safe data structure
class Stack<T> {
  private items: T[] = [];

  push(item: T): void { this.items.push(item); }
  pop(): T | undefined { return this.items.pop(); }
}

const nums = new Stack<number>();
nums.push(1);
// nums.push('hello');  // Error: string not assignable to number
ConstraintWhat it requiresExample
T extends { length: number }Has length propertyArrays, strings
T extends objectIs an object (not primitive)Objects, arrays
K extends keyof TK is a key of TProperty names
T extends UT is assignable to UType inheritance
05

Quick reference

ConceptSyntaxPurpose
Typed functionfunction fn(x: number): stringType-safe parameters and return
Optional paramfunction fn(x?: string)Parameter can be omitted
Default paramfunction fn(x: string = 'hi')Fallback value if omitted
Generic functionfunction fn<T>(x: T): TWorks with any type, stays safe
Constraint<T extends HasId>Limits what types T can be
keyofK extends keyof TK must be a property name of T
AI pitfall, wrong generic constraints. AI tools sometimes generate generic functions with constraints that are too loose or too strict. A common mistake is <T extends object> when the function actually needs <T extends { id: number }>, or <T extends any> which is the same as no constraint at all. Always check that the constraint matches what the function body actually requires, if it accesses .id, the constraint must include id.