Lab 02: Functions & Type Signatures
Objective
Write typed functions with parameters, return types, overloads, generics, and higher-order functions. Understand arrow functions, optional/rest parameters, and function type signatures.
Background
TypeScript's function types are its most powerful feature for eliminating bugs. Typed parameters catch wrong argument orders, optional parameters document intent, and generic functions work across types without sacrificing safety. TypeScript also supports function overloads — multiple signatures for one implementation.
Time
25 minutes
Prerequisites
Lab 01 (Hello World)
Tools
Docker image:
zchencow/innozverse-ts:latest
Lab Instructions
Step 1: Function Types & Signatures
💡 Function types are just type annotations for functions:
(a: number, b: number) => numberdescribes any function that takes two numbers and returns a number. TypeScript uses structural typing — any function with this shape is assignable, regardless of what it's called.
📸 Verified Output:
Step 2: Optional, Default & Rest Parameters
💡 Optional vs default:
param?: stringmeans the caller may omit it (receivesundefined).param: string = "default"means the caller may omit it (receives"default"). Prefer defaults over optional when a sensible default exists — it makes the function easier to call and the code cleaner.
📸 Verified Output:
Step 3: Generics
💡 Generics let you write once, use for any type. Without generics, you'd need
numberFirst,stringFirst,userFirst, etc. Withfirst<T>, one function works for all types while preserving type information. The type parameterTis inferred from the argument — you rarely need to writefirst<number>(arr)explicitly.
📸 Verified Output:
Step 4: Function Overloads
💡 The implementation signature is not visible to callers — only the overload signatures are. The implementation signature must be compatible with all overloads (usually using union types). Keep overloads minimal — if the function has wildly different behavior per type, consider separate functions.
📸 Verified Output:
Step 5: Higher-Order Functions
💡 TypeScript's generic
pipepreserves types through transformations. If the functions have different input/output types, you need a more complex variadic generic type (like fp-ts'spipe). For same-type pipelines (string → string), this pattern is clean and fully typed.
📸 Verified Output:
Step 6: Async Functions
💡
asyncfunctions always returnPromise<T>— even if you writereturn 42, TypeScript infersPromise<number>. Theawaitkeyword unwraps the promise to getT. TypeScript tracks this automatically, soconst x = await fetchData(1)has type{ id: number; title: string }, notPromise<...>.
📸 Verified Output:
Step 7: Type Guards
💡
value is Typereturn type makes a function a type guard — afterif (isCat(animal)), TypeScript narrowsanimal's type toCatin the if-branch andDogin the else-branch. This is how TypeScript achieves safe type narrowing from union types without casting (as).
📸 Verified Output:
Step 8: Complete — Typed Pipeline
💡 The Result type makes errors explicit in the type system — callers must handle both
okanderrorcases. Compared to exceptions, Result types are visible in function signatures (Result<number>vsnumber) and force callers to handle errors at the call site. TypeScript's discriminated unions make this pattern ergonomic.
📸 Verified Output:
Summary
TypeScript's function system is expressive and type-safe. You've covered typed parameters, optional/default/rest args, generics, overloads, higher-order functions, async/await types, type guards, and the Result pattern. These skills make every function self-documenting and IDE-friendly.
Further Reading
Last updated
