Skip to content

TypeScript Functions — Complete Guide with Types

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about TypeScript Functions. We cover key concepts, practical examples, and best practices to help you master this topic.

TypeScript functions are the heart of any application, and typing them properly — parameters, return values, overloads, and this context — ensures every function call is checked for correctness before runtime.

What You'll Learn

  • Typing function parameters and return values
  • Optional and default parameters
  • Rest parameters and spread
  • Function overloads for multiple call signatures
  • this parameter typing
  • Call signatures in object types

Why It Matters

Functions are where most bugs enter a codebase — wrong argument types, missing parameters, incorrect return values. TypeScript's function typing catches these at compile time. Every undefined is not a function error you've seen in JavaScript is a type error that TypeScript can prevent.

Real-World Use

The Doda Browser extension API exposes several functions like browser.tabs.query() which accepts a filter object and returns a typed Promise. The overloads ensure that calling it with different argument shapes produces the correct return type, and developers get full autocompletion for each variant.

Learning Path

flowchart LR
  A[Type Aliases] --> B[Functions]
  B --> C[Enums]
  B --> D[You Are Here]
  C --> E[Type Assertions]
  E --> F[Generics Basics]
  F --> G[Advanced Types]

Parameter Types

function greet(name: string, age: number): string {
  return `${name} is ${age} years old`;
}

console.log(greet("Alice", 30)); // Alice is 30 years old
// greet(30, "Alice"); // Error: Argument of type 'number' is not assignable to parameter of type 'string'

Each parameter gets a type annotation. TypeScript checks both the type and the position.

Return Types

function add(a: number, b: number): number {
  return a + b;
}

function logMessage(msg: string): void {
  console.log(msg);
  // No return
}

function throwError(msg: string): never {
  throw new Error(msg);
}

TypeScript infers return types, but it's good practice to annotate them explicitly on public functions — it acts as documentation and prevents accidental changes to the return type from rippling through your codebase.

Optional Parameters

Use ? to make parameters optional:

function createUser(name: string, email?: string): void {
  console.log(`Creating user: ${name}`);
  if (email) {
    console.log(`Email: ${email}`);
  }
}

createUser("Alice");           // OK
createUser("Bob", "b@e.com"); // OK
// createUser("Charlie", 123); // Error: number not assignable to string

Optional parameters must come after required ones.

Default Parameters

Default parameters automatically provide a value and make the parameter optional:

function createConfig(mode: string = "production", port: number = 3000): void {
  console.log(`Mode: ${mode}, Port: ${port}`);
}

createConfig();                 // Mode: production, Port: 3000
createConfig("development");    // Mode: development, Port: 3000
createConfig("staging", 8080);  // Mode: staging, Port: 8080

TypeScript infers the parameter type from the default value, so mode: string is automatic.

Rest Parameters

Collect remaining arguments into an array:

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

console.log(sum(1, 2, 3));    // 6
console.log(sum(10, 20, 30, 40, 50)); // 150

With a tuple type for fixed-length rest:

function createTeam(leader: string, ...members: [string, string]): void {
  console.log(`Leader: ${leader}, Members: ${members.join(", ")}`);
}

createTeam("Alice", "Bob", "Charlie"); // OK
// createTeam("Alice", "Bob"); // Error — need exactly 2 members

Function Overloads

Overloads let a function have multiple call signatures with different parameter types:

// Overload signatures
function process(input: string): string;
function process(input: number): number;
function process(input: string | number): string | number {
  if (typeof input === "string") {
    return input.toUpperCase();
  }
  return input * 10;
}

console.log(process("hello")); // HELLO (type: string)
console.log(process(42));      // 420 (type: number)
// process(true); // Error: No overload matches this call

The implementation signature (the third one) is not callable directly — only the overload signatures determine what arguments are valid.

Real-World Overload Example

function getValue(key: string): string | undefined;
function getValue(keys: string[]): (string | undefined)[];
function getValue(input: string | string[]): string | (string | undefined)[] {
  if (typeof input === "string") {
    return localStorage.getItem(input) ?? undefined;
  }
  return input.map(k => localStorage.getItem(k) ?? undefined);
}

const single = getValue("theme");  // type: string | undefined
const multi = getValue(["theme", "lang"]); // type: (string | undefined)[]

Call Signatures in Object Types

Functions can also be described using call signatures:

type Greeter = {
  (name: string): string;
  greeting: string;
};

function createGreeter(greeting: string): Greeter {
  const fn: Greeter = ((name: string) => `${greeting}, ${name}!`) as Greeter;
  fn.greeting = greeting;
  return fn;
}

const helloGreeter = createGreeter("Hello");
console.log(helloGreeter("Alice"));  // Hello, Alice!
console.log(helloGreeter.greeting);  // Hello

The this Parameter

JavaScript's this is notoriously tricky. TypeScript lets you type it explicitly:

type ButtonConfig = {
  label: string;
  onClick: (this: HTMLElement, event: MouseEvent) => void;
};

const button: ButtonConfig = {
  label: "Click me",
  onClick(this: HTMLElement, event) {
    console.log(this.textContent); // this is typed as HTMLElement
  },
};

In a class method, this is implicitly the class instance, but in standalone functions you can specify it as the first parameter (which is compile-time only, not a real parameter).

Common Mistakes

1. Not Annotating Return Types

// Without annotation, TypeScript infers
function getUsers() {
  return fetch("/api/users").then(r => r.json());
}
// Return type: Promise<any>

// With annotation — catches breaking changes
function getUsers(): Promise<User[]> {
  return fetch("/api/users").then(r => r.json());
}

2. Confusing Optional and Default Parameters

function example(opt?: string) { }  // opt can be undefined
function example(opt: string = "default") { } // opt is always string (never undefined)

3. Overloading by Return Type Only

// Invalid — overloads must differ in parameters
function fn(x: number): string;
function fn(x: number): number; // Error

Overloads are distinguished by parameter types, not return types.

4. Putting Optional Parameters Before Required

function bad(opt?: string, required: string) { } // Error: Required parameter after optional
function good(required: string, opt?: string) { }

5. Not Using void When There's No Meaningful Return

// Don't return the result of console.log
function log(msg: string): void {
  console.log(msg);
}

6. Forgetting That Arrow Functions Don't Have Their Own this

Arrow functions capture this from the surrounding scope. You cannot use a this parameter with arrow functions — use regular functions instead.

Practice Questions

  1. Can a function have multiple overload signatures but one implementation? Yes. All overload signatures must be compatible with the implementation signature.

  2. What's the difference between optional ? and default value = val? Optional leaves the value as Type | undefined. Default provides a runtime fallback and the type is always Type.

  3. What does void mean as a return type? The function doesn't return a meaningful value. The return value (if any) should be ignored.

  4. Rest parameters must be which parameter in the function? The last parameter. Only one rest parameter is allowed per function.

Challenge: Write a function formatDate with two overloads: one that takes a Date object and a format string, and one that takes a timestamp (number) and a format string. Implement it to return a formatted date string.

FAQ

Can I use TypeScript overloads with arrow functions?

No, overloads require the function keyword syntax. Arrow functions cannot have overloads.

What happens if I call a function with the wrong number of arguments?

TypeScript reports a type error. In JavaScript, extra arguments are ignored and missing ones are undefined.

How do I type a callback parameter?

Use a function type signature: function fetchData(callback: (data: string) => void): void

Can I have optional parameters in the middle of a function signature?

Only after all required parameters. Use a rest parameter or a single options object for complex cases.

What is the difference between `void` and `undefined` as return types?

void means the return value is not meaningful (should be ignored). undefined is a specific value. A function returning void can return undefined, but the reverse is not necessarily true for type compatibility.

Mini Project: Utility Library

Build a small utility library with typed functions:

// src/utils.ts

type Predicate<T> = (item: T) => boolean;

function find<T>(items: T[], predicate: Predicate<T>): T | undefined {
  for (const item of items) {
    if (predicate(item)) return item;
  }
  return undefined;
}

function groupBy<T, K extends string | number>(items: T[], keyFn: (item: T) => K): Record<K, T[]> {
  const result = {} as Record<K, T[]>;
  for (const item of items) {
    const key = keyFn(item);
    if (!result[key]) result[key] = [];
    result[key].push(item);
  }
  return result;
}

function pipe<T>(value: T, ...fns: Array<(val: any) => any>): any {
  return fns.reduce((acc, fn) => fn(acc), value);
}

const users = [
  { name: "Alice", role: "admin" as const },
  { name: "Bob", role: "user" as const },
  { name: "Charlie", role: "user" as const },
];

const admin = find(users, u => u.role === "admin");
console.log(admin?.name); // Alice

const grouped = groupBy(users, u => u.role);
console.log(Object.keys(grouped)); // ["admin", "user"]

const result = pipe(
  5,
  (x: number) => x * 2,
  (x: number) => x + 1,
);
console.log(result); // 11

What's Next

Now explore enums for defining named constants:

Lesson Description
{{< ref "/programming-languages/typescript/05-type-aliases" >}} Review type aliases
{{< ref "/programming-languages/typescript/07-enums" >}} Numeric, string, and const enums
{{< ref "/programming-languages/typescript/08-type-assertions" >}} Type assertions and type guards

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro