In the world of software development, particularly when moving towards functional programming patterns, few concepts are as high-leverage as the Pure Function. It sounds academic, but the practical benefits—easier debugging, bulletproof testing, and predictable behavior—are immediate.
A function is considered "pure" if it adheres strictly to two rules:
For a function to be pure, it cannot depend on hidden values like time, random numbers, or database content.
This function is impure because it depends on Date.now(). If you run this twice with the same argument, you get different results.
const createUser = (name: string) => ({
id: Date.now(), // ⚠️ Hidden dependency on time
name: name,
});To make it pure, we move the dependency into the arguments. Now the output is strictly tied to the inputs.
type User = {
id: number;
name: string;
};
const createUser = (id: number, name: string): User => ({
id,
name
});A side effect occurs when a function changes something other than its return value. In TypeScript, objects and arrays are passed by reference, making it easy to accidentally cause side effects via mutation.
const cart = [{ item: "Apple", price: 1.2 }];
const addToCart = (currentCart: any[], newItem: any) => {
currentCart.push(newItem); // ⚠️ Mutates the original array
return currentCart; // return is needed here because push() returns length
};Instead of modifying the input, we return a new version of the data. Using readonly helps us enforce this.
type CartItem = {
item: string;
price: number;
};
const addToCart = (
currentCart: readonly CartItem[],
newItem: CartItem
): CartItem[] => [...currentCart, newItem]; // ✅ Returns a new arrayYou cannot build a useful app with 100% pure functions. You need to save to databases and call APIs. The goal is to isolate your logic.

The Sandwich Architecture: Pure Core wrapped in an Impure Shell.
Pure functions make your TypeScript code easier to test, move, and reason about.