# TypeScript Functions & Generics: Syntax, Examples, and Patterns

Source: https://devdocs10.com/articles/javascript/ts-functions  
By Murat Bo · Published 2026-08-03 · DevDocs10 (https://devdocs10.com)

TypeScript functions and generics let you write reusable code that is still strongly typed. In this article, you will learn how to type function parameters and return values, how generic type parameters work, and how to use them to build safer utilities without losing flexibility.

> **Quick answer:** Use function types to describe what a function accepts and returns. Use generics when the same function should work with different types while keeping those types connected and checked by the compiler.

> **Difficulty:** Beginner to Intermediate
>
> **You'll understand this better if you know:** basic JavaScript functions, variables, and simple TypeScript types such as `string`, `number`, and `Array`.

## 1. What Is TypeScript Functions & Generics?

TypeScript functions are JavaScript functions with type annotations. Generics are a TypeScript feature that lets a function, type, or class work with more than one data type while preserving type information.

- A typed function describes the shapes of its inputs and outputs.
- A generic function uses a type parameter such as `T` to keep relationships between values.
- Generics help you avoid `any` while keeping code reusable.
- They are common in helper functions, data processing, and API wrappers.

For example, a regular function can say “I accept a number and return a string,” while a generic function can say “I accept one value and return that same kind of value.”

## 2. Why TypeScript Functions & Generics Matter

Functions are the core building block of JavaScript, and TypeScript becomes most valuable when it can describe function behavior precisely. Generics matter because many real functions do not work with just one specific type.

Without generics, you often end up duplicating code for strings, numbers, and objects, or using overly broad types that hide mistakes until runtime. With generics, the compiler can verify that values passed through a function stay consistent.

This is especially useful when building:

- utility functions such as `first`, `merge`, or `pluck`
- data transformation functions that keep input and output types related
- callbacks and event handlers with exact parameter shapes
- library code that should work for many consumers

## 3. Basic Syntax

Start with a normal typed function. Then add a type parameter when the return type or parameter type should stay linked to the input.

### 3.1 A typed function

This example shows a function that accepts a `string` and returns a `number`:

```typescript
function countLetters(text: string): number {
  return text.length;
}
```

The parameter type comes after the parameter name, and the return type comes after the closing parenthesis. TypeScript checks both sides for consistency.

### 3.2 A generic function

This example accepts a value and returns the same value type:

```typescript
function identity<T>(value: T): T {
  return value;
}
```

The `<T>` part declares a type parameter. TypeScript fills in `T` based on how you call the function.

## 4. Step-by-Step Examples

### 4.1 Example: preserving a string type

When you call a generic function with a string, TypeScript infers `T` as `string`:

```typescript
function identity<T>(value: T): T {
  return value;
}

const username = identity("ada");
```

Here, `username` is inferred as `string`, not `string | number` or `any`. That means later string operations are safe.

### 4.2 Example: working with arrays

Generics are very useful for array helpers that should work with any item type:

```typescript
function firstItem<T>(items: Array<T>): T | undefined {
  return items[0];
}

const firstName = firstItem(["Mina", "Omar"]);
const firstScore = firstItem([10, 20]);
```

TypeScript infers `T` separately for each call, so the function stays flexible and safe.

### 4.3 Example: multiple type parameters

Some functions relate two different types, such as a key and a value:

```typescript
function pair<K, V>(key: K, value: V): Object {
  return { key, value };
}
```

Using more than one type parameter is common when the inputs do not share the same type.

### 4.4 Example: inferring return types from input

Generics can preserve object shapes too:

```typescript
function cloneValue<T>(value: T): T {
  return { ...value };
}

const profile = cloneValue({ name: "Lina", active: true });
```

The returned value keeps the same object shape that went in, which is much better than falling back to an untyped object.

## 5. Practical Use Cases

Typed functions and generics show up in everyday code. Common uses include:

- a fetch helper that parses JSON into a known result type
- a reusable validator that accepts different data shapes
- sorting and filtering helpers for lists of objects
- collection utilities such as map, reduce, and group operations
- shared library functions that should work for many applications

For example, a generic API wrapper can preserve the response type so the caller gets proper autocomplete and compile-time checking.

## 6. Common Mistakes

### Mistake 1: Using `any` instead of a generic

Beginners often reach for `any` because it makes the function easy to write. The problem is that it removes type safety and hides bad calls.

**Problem:** The function accepts and returns `any`, so TypeScript cannot catch mistakes such as calling string methods on a number result.

```typescript
function echo(value: any): any {
  return value;
}

const result = echo(123);
result.toUpperCase();
```

**Fix:** Use a generic so the input and output stay connected.

```typescript
function echo<T>(value: T): T {
  return value;
}

const result = echo(123);
// result is number, so string-only methods are rejected
```

The generic version keeps the exact type, so invalid usage is caught earlier.

### Mistake 2: Forgetting to type the return value when it matters

TypeScript can infer many return types, but inference can become too broad in more complex functions. That can lead to surprising types later.

**Problem:** This function returns a union-like object shape, and the caller may not get the level of precision they expect if the return type is not described clearly.

```typescript
function makeStatus(ok: boolean) {
  if (ok) {
    return { status: "success", code: 200 };
  }

  return { status: "error", code: 500 };
}
```

**Fix:** Add an explicit return type when you want the function contract to stay clear.

```typescript
type Status = {
  status: "success" | "error";
  code: number;
};

function makeStatus(ok: boolean): Status {
  if (ok) {
    return { status: "success", code: 200 };
  }

  return { status: "error", code: 500 };
}
```

The explicit return type documents the contract and prevents accidental changes later.

### Mistake 3: Over-constraining a generic

Sometimes developers add too many restrictions and make the function harder to use than necessary.

**Problem:** This version forces every value to have an `id` property even when the function does not need one.

```typescript
function wrap<T extends { id: string }>(value: T): T {
  return value;
}
```

**Fix:** Use a constraint only when the function actually depends on that property.

```typescript
function wrap<T>(value: T): T {
  return value;
}
```

Keep constraints as narrow as possible so the function stays reusable.

## 7. Best Practices

### 7.1 Let TypeScript infer types when the code is obvious

When the implementation is simple, inference keeps the code shorter and easier to read.

```typescript
const double = (value: number) => value * 2;
```

Use explicit types when they add clarity, but do not repeat information that TypeScript already knows.

### 7.2 Prefer meaningful generic names

`T` is common for a single generic, but names like `TItem` or `TResponse` can improve readability in larger codebases.

```typescript
function takeFirst<TItem>(items: Array<TItem>): TItem | undefined {
  return items[0];
}
```

Readable generic names help other developers understand what the type parameter represents.

### 7.3 Use constraints only when needed

A constraint such as `extends { id: string }` is useful when the function must access a property. Otherwise, it narrows the function too much.

```typescript
function getId<T extends { id: string }>(item: T): string {
  return item.id;
}
```

This is the right time for a constraint because the function actually relies on `id`.

## 8. Limitations and Edge Cases

- Generics are erased at runtime, so you cannot inspect `T` in real JavaScript code after compilation.
- TypeScript infers generic types from usage, but inference can fail for empty arrays or ambiguous values.
- Callbacks may need explicit parameter types when context is not enough for inference.
- Functions with many generic parameters can become difficult to read if the API is not designed carefully.
- Not every reusable function needs a generic; a simple union or a fixed type is sometimes clearer.

**Problem:** A generic type parameter only exists in TypeScript's type system, so you cannot branch on it at runtime as if it were a real value.

That is why generic functions should use ordinary JavaScript logic and let the type system validate the shape, not the runtime type parameter itself.

## 9. Practical Mini Project

Let's build a small typed data helper for a list of products. The goal is to write one generic function that can pick a property from any array of objects.

```typescript
type Product = {
  id: number;
  name: string;
  price: number;
};

function pluck<T, K extends keyof T>(
  items: Array<T>,
  key: K
): Array<T[K]> {
  return items.map((item) => item[key]);
}

const products: Array<Product> = [
  { id: 1, name: "Keyboard", price: 79 },
  { id: 2, name: "Mouse", price: 29 }
];

const names = pluck(products, "name");
const prices = pluck(products, "price");
```

This project shows the main strength of generics: the function stays reusable, but the return type is still exact. If you try to pass a key that does not exist, TypeScript will reject it at compile time.

## 10. Key Points

- TypeScript functions add type safety to normal JavaScript functions.
- Generics make a function reusable across different types without losing type information.
- Type parameters such as `T` connect inputs and outputs.
- Constraints like `extends` should be used only when the function needs them.
- Good generic design improves autocomplete, refactoring, and bug prevention.

## 11. Practice Exercise

- Write a generic function named `wrapInArray` that accepts one value and returns it inside a one-item array.
- Call it with a string, a number, and an object.
- Make sure the returned array keeps the correct item type in each case.

**Expected output:** the function should return arrays such as `["hello"]`, `[42]`, and a typed object array like `[{ id: 1 }]`.

**Hint:** Use one generic type parameter and return `Array<T>`.

```typescript
function wrapInArray<T>(value: T): Array<T> {
  return [value];
}

const textList = wrapInArray("hello");
const numberList = wrapInArray(42);
const objectList = wrapInArray({ id: 1 });
```

## 12. Final Summary

TypeScript functions give you typed, reliable building blocks for everyday JavaScript code. By annotating parameters and return values, you make your intent clear and help the compiler catch mistakes before they become runtime bugs.

Generics take that idea further by letting one function work across many types while preserving the relationship between inputs and outputs. They are especially valuable for helpers, collections, and reusable library code where `any` would be too loose and duplicate implementations would be too costly.

If you want to continue, the next best step is to learn generic constraints, utility types, and function overloading so you can design even more expressive TypeScript APIs.
