Lesson 4.1: Functions with Typed Parameters
Duration: 55 minutes
Learning Objectives
By the end of this lesson, you will be able to:
- Declare functions with typed parameters
- Use optional parameters with the
?syntax - Set default parameter values
- Work with rest parameters for variable argument lists
- Understand function overloading basics
Why Type Function Parameters?
In JavaScript, functions accept any arguments without checking:
// JavaScript - no type safety
function greet(name) {
return 'Hello, ' + name.toUpperCase();
}
greet('Alice'); // Works: "Hello, ALICE"
greet(42); // Crashes at runtime! 42.toUpperCase is not a function
greet(); // Crashes! undefined.toUpperCase is not a function
TypeScript catches these errors before you run the code:
function greet(name: string): string {
return 'Hello, ' + name.toUpperCase();
}
greet('Alice'); // Works: "Hello, ALICE"
greet(42); // Error: Argument of type 'number' is not assignable to 'string'
greet(); // Error: Expected 1 arguments, but got 0
Basic Function Parameter Types
Single Parameter
Add a type annotation after the parameter name:
function double(value: number) {
return value * 2;
}
console.log(double(5)); // 10
console.log(double(3.5)); // 7
Multiple Parameters
Each parameter gets its own type:
function createUser(name: string, age: number, isAdmin: boolean) {
return {
name: name,
age: age,
isAdmin: isAdmin,
};
}
const user = createUser('Alice', 30, false);
console.log(user); // { name: "Alice", age: 30, isAdmin: false }
Array Parameters
Use the array type syntax:
function sum(numbers: number[]) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
console.log(sum([1, 2, 3, 4, 5])); // 15
Object Parameters
Define the object shape inline or use an interface:
// Inline object type
function printPerson(person: { name: string; age: number }) {
console.log(`${person.name} is ${person.age} years old`);
}
printPerson({ name: 'Bob', age: 25 });
// Using an interface (cleaner for complex objects)
interface Product {
id: number;
name: string;
price: number;
}
function displayProduct(product: Product) {
console.log(`${product.name}: $${product.price}`);
}
displayProduct({ id: 1, name: 'Laptop', price: 999 });
Optional Parameters
Sometimes a parameter is not required. Use ? to mark it as optional:
function greet(name: string, greeting?: string) {
if (greeting) {
return `${greeting}, ${name}!`;
}
return `Hello, ${name}!`;
}
console.log(greet('Alice')); // "Hello, Alice!"
console.log(greet('Alice', 'Welcome')); // "Welcome, Alice!"
Rules for Optional Parameters
- Optional parameters must come after required parameters:
// Correct
function example(required: string, optional?: number) {}
// Error: A required parameter cannot follow an optional parameter
function wrong(optional?: string, required: number) {}
- Optional parameters are
undefinedwhen not provided:
function showInfo(name: string, email?: string) {
console.log(`Name: ${name}`);
console.log(`Email: ${email}`); // Could be undefined
}
showInfo('Alice'); // Email: undefined
- Check for undefined before using:
function formatName(first: string, last?: string) {
if (last !== undefined) {
return `${first} ${last}`;
}
return first;
}
console.log(formatName('Alice')); // "Alice"
console.log(formatName('Alice', 'Smith')); // "Alice Smith"
Default Parameters
Default parameters provide a fallback value when the argument is not passed:
function greet(name: string, greeting: string = 'Hello') {
return `${greeting}, ${name}!`;
}
console.log(greet('Alice')); // "Hello, Alice!"
console.log(greet('Alice', 'Hi')); // "Hi, Alice!"
console.log(greet('Alice', 'Welcome')); // "Welcome, Alice!"
Default Parameters with Types
TypeScript infers the type from the default value:
// TypeScript knows 'count' is a number from the default
function repeat(text: string, count = 1) {
return text.repeat(count);
}
repeat('ha', 3); // "hahaha"
repeat('ha'); // "ha"
repeat('ha', '3'); // Error: Argument of type 'string' is not assignable to 'number'
Complex Default Values
function createConfig(
name: string,
options: { debug: boolean; timeout: number } = { debug: false, timeout: 5000 }
) {
return { name, ...options };
}
console.log(createConfig('app'));
// { name: "app", debug: false, timeout: 5000 }
console.log(createConfig('app', { debug: true, timeout: 10000 }));
// { name: "app", debug: true, timeout: 10000 }
Rest Parameters
Rest parameters collect multiple arguments into an array:
function sum(...numbers: number[]) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum()); // 0
Rest Parameters with Other Parameters
Rest parameters must be last:
function log(level: string, ...messages: string[]) {
console.log(`[${level}]`, ...messages);
}
log('INFO', 'Server started');
log('ERROR', 'Connection failed', 'Retrying in 5 seconds');
Typed Rest Parameters
interface LogEntry {
timestamp: Date;
message: string;
}
function logAll(...entries: LogEntry[]) {
for (const entry of entries) {
console.log(`${entry.timestamp.toISOString()}: ${entry.message}`);
}
}
logAll(
{ timestamp: new Date(), message: 'Started' },
{ timestamp: new Date(), message: 'Processing' },
{ timestamp: new Date(), message: 'Completed' }
);
Function Overloading
Function overloading lets you define multiple ways to call a function:
// Overload signatures
function combine(a: string, b: string): string;
function combine(a: number, b: number): number;
// Implementation signature
function combine(a: string | number, b: string | number): string | number {
if (typeof a === 'string' && typeof b === 'string') {
return a + b;
}
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
throw new Error('Arguments must be both strings or both numbers');
}
const text = combine('Hello', ' World'); // Type: string
const sum = combine(10, 20); // Type: number
// Error: No overload matches this call
// combine("Hello", 42);
When to Use Overloading
Use overloading when:
- The return type depends on the input types
- You want precise types for different argument combinations
// Finding elements - overloaded for single vs multiple results
function find(id: number): User | undefined;
function find(ids: number[]): User[];
function find(idOrIds: number | number[]): User | undefined | User[] {
if (Array.isArray(idOrIds)) {
return users.filter((u) => idOrIds.includes(u.id));
}
return users.find((u) => u.id === idOrIds);
}
const single = find(1); // Type: User | undefined
const multiple = find([1, 2]); // Type: User[]
Practical Examples
Example 1: Calculator Functions
function add(a: number, b: number) {
return a + b;
}
function subtract(a: number, b: number) {
return a - b;
}
function calculate(operation: string, a: number, b: number, precision: number = 2) {
let result: number;
switch (operation) {
case 'add':
result = a + b;
break;
case 'subtract':
result = a - b;
break;
case 'multiply':
result = a * b;
break;
case 'divide':
result = a / b;
break;
default:
throw new Error(`Unknown operation: ${operation}`);
}
return Number(result.toFixed(precision));
}
console.log(calculate('add', 5, 3)); // 8
console.log(calculate('divide', 10, 3)); // 3.33
console.log(calculate('divide', 10, 3, 4)); // 3.3333
Example 2: User Search Function
interface User {
id: number;
name: string;
email: string;
role: string;
}
function findUsers(users: User[], searchTerm?: string, role?: string) {
let results = users;
if (searchTerm !== undefined) {
const term = searchTerm.toLowerCase();
results = results.filter(
(u) => u.name.toLowerCase().includes(term) || u.email.toLowerCase().includes(term)
);
}
if (role !== undefined) {
results = results.filter((u) => u.role === role);
}
return results;
}
const users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
{ id: 2, name: 'Bob', email: 'bob@example.com', role: 'user' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'user' },
];
console.log(findUsers(users)); // All users
console.log(findUsers(users, 'ali')); // Alice
console.log(findUsers(users, undefined, 'user')); // Bob, Charlie
console.log(findUsers(users, 'b', 'user')); // Bob
Example 3: Event Logger
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
function log(level: LogLevel, message: string, ...data: unknown[]) {
const timestamp = new Date().toISOString();
const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
if (data.length > 0) {
console.log(prefix, message, ...data);
} else {
console.log(prefix, message);
}
}
log('info', 'Application started');
log('debug', 'User data:', { id: 1, name: 'Alice' });
log('error', 'Failed to connect', 'timeout', 5000);
Exercises
Exercise 1: Greeting Function
Create a function personalGreeting that:
- Takes a required
nameparameter (string) - Takes an optional
timeOfDayparameter (string) - Returns a greeting like "Good morning, Alice!" or just "Hello, Alice!" if no time provided
Solution
function personalGreeting(name: string, timeOfDay?: string): string {
if (timeOfDay !== undefined) {
return `Good ${timeOfDay}, ${name}!`;
}
return `Hello, ${name}!`;
}
console.log(personalGreeting('Alice')); // "Hello, Alice!"
console.log(personalGreeting('Bob', 'morning')); // "Good morning, Bob!"
console.log(personalGreeting('Charlie', 'evening')); // "Good evening, Charlie!"
Exercise 2: Array Processor
Create a function processNumbers that:
- Takes an array of numbers
- Takes an optional
operationparameter with default value "sum" - Supports operations: "sum", "average", "max", "min"
- Returns the result
Solution
function processNumbers(
numbers: number[],
operation: 'sum' | 'average' | 'max' | 'min' = 'sum'
): number {
if (numbers.length === 0) {
return 0;
}
switch (operation) {
case 'sum':
return numbers.reduce((a, b) => a + b, 0);
case 'average':
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
case 'max':
return Math.max(...numbers);
case 'min':
return Math.min(...numbers);
}
}
console.log(processNumbers([1, 2, 3, 4, 5])); // 15
console.log(processNumbers([1, 2, 3, 4, 5], 'average')); // 3
console.log(processNumbers([1, 2, 3, 4, 5], 'max')); // 5
console.log(processNumbers([1, 2, 3, 4, 5], 'min')); // 1
Exercise 3: Contact Builder
Create a function createContact that:
- Takes required parameters:
firstName,lastName,email - Takes optional parameters:
phone,company - Returns a contact object with all provided information
Solution
interface Contact {
firstName: string;
lastName: string;
email: string;
phone?: string;
company?: string;
}
function createContact(
firstName: string,
lastName: string,
email: string,
phone?: string,
company?: string
): Contact {
const contact: Contact = {
firstName,
lastName,
email,
};
if (phone !== undefined) {
contact.phone = phone;
}
if (company !== undefined) {
contact.company = company;
}
return contact;
}
console.log(createContact('Alice', 'Smith', 'alice@example.com'));
// { firstName: "Alice", lastName: "Smith", email: "alice@example.com" }
console.log(createContact('Bob', 'Jones', 'bob@example.com', '555-1234', 'Acme Inc'));
// { firstName: "Bob", lastName: "Jones", email: "bob@example.com", phone: "555-1234", company: "Acme Inc" }
Exercise 4: String Joiner
Create a function joinStrings that:
- Uses rest parameters to accept any number of strings
- Takes an optional
separatorparameter (default: ", ") - Returns all strings joined with the separator
Solution
function joinStrings(separator: string = ', ', ...strings: string[]): string {
return strings.join(separator);
}
console.log(joinStrings(', ', 'apple', 'banana', 'cherry'));
// "apple, banana, cherry"
console.log(joinStrings(' - ', 'one', 'two', 'three'));
// "one - two - three"
console.log(joinStrings(' | ', 'a', 'b'));
// "a | b"
Key Takeaways
- Type parameters with colon:
function greet(name: string)defines a typed parameter - Optional parameters use ?: They must come after required parameters
- Default parameters provide fallbacks:
function greet(name = "World") - Rest parameters collect arguments:
function sum(...numbers: number[]) - TypeScript catches type errors: Wrong argument types are caught at compile time
- Function overloading: Define multiple call signatures for flexible APIs
Resources
| Resource | Type | Description |
|---|---|---|
| TypeScript Handbook: Functions | Documentation | Official guide to functions |
| TypeScript Handbook: More on Functions | Documentation | Function overloads and advanced patterns |
| MDN: Default Parameters | Documentation | JavaScript default parameters |
Next Lesson
Now that you can type function parameters, let us learn how to specify what functions return.