---
title: "The Functional Programming Habits That Actually Make Your TypeScript Better"
url: https://shakeebkhan.com/the-functional-programming-habits-that-actually-make-your-typescript-better/
type: post
date: 2026-09-17
modified: 2026-09-17
markdown_url: https://shakeebkhan.com/the-functional-programming-habits-that-actually-make-your-typescript-better.md
author: "Shakeeb Khan"
---

# The Functional Programming Habits That Actually Make Your TypeScript Better

![Functional programming in TypeScript illustrated with pure functions and composition](https://shakeebkhan.com/wp-content/uploads/2026/09/fp_16x9_2048x1152-1024x576.jpg)

Getting functional programming in TypeScript right is less about theory and more about a handful of practical habits. I learned that the hard way.

A while back I was debugging a function that was supposed to just calculate a discount. Simple, right? Except it also updated a shared cart object, logged something to a global array, and quietly mutated an argument that three other functions downstream depended on. I spent an hour figuring out why the total was wrong, and the answer was: the function was doing five things when its name promised one.

That's usually the moment someone tells you to "just learn functional programming." And honestly, the term scares people off more than it should. It sounds academic, like something you'd need a math degree to appreciate. But strip away the jargon and functional programming is really just a handful of habits that make your code easier to reason about. You don't need Haskell for this. Plain TypeScript gets you most of the way there.

Let me walk through the ideas that actually matter for functional programming in TypeScript, day to day, with real examples.

## Pure functions: the same input, always the same output

A pure function doesn't touch anything outside itself. No mutating arguments, no reaching out to a database or a global variable, no side effects. Give it the same input, get the same output, every single time.

```
// impure — depends on and mutates external state
let taxRate = 0.19;
function applyTax(cart: { total: number }) {
 cart.total = cart.total + cart.total * taxRate;
 return cart.total;
}

// pure — no hidden dependencies, nothing mutated
function applyTax(total: number, taxRate: number): number {
 return total + total * taxRate;
}
```

The second version is boring in the best way. You can test it without mocking anything. You can call it a hundred times in a row and never worry about state drifting underneath you. That predictability is the whole point — it's what lets you reason about one function without holding the entire codebase in your head.

## Immutability: stop changing things in place

Related idea: instead of mutating data, you create a new version of it. This feels wasteful at first — why copy an array instead of just pushing to it? But mutation is exactly what causes those "wait, who changed this?" bugs, especially once multiple parts of your app hold a reference to the same object.

```
// mutating — anyone else holding `items` gets surprised
function addItem(items: string[], item: string) {
 items.push(item);
 return items;
}

// immutable — original array is untouched
function addItem(items: string[], item: string): string[] {
 return [...items, item];
}
```

TypeScript's readonly arrays and object spread make this pattern almost free to write. And once you get used to it, mutation starts to feel risky in a way it never used to.

## First-class and higher-order functions

In functional programming, functions are just values. You pass them around, return them from other functions, store them in variables — the same as you would a string or a number. A higher-order function is simply a function that takes another function as an argument or returns one.

You already use this constantly if you've written `array.map()`, `.filter()`, or `.reduce()`. Those are higher-order functions doing exactly what a manual for-loop would do, just without you having to manage the loop state yourself.

## Composition: building big things out of small things

This is the one I find most useful in practice. Instead of writing one large function that does validation, transformation, and formatting all at once, you write three small, pure functions and compose them together.

```
const trim = (s: string) => s.trim();
const toLowerCase = (s: string) => s.toLowerCase();
const removeSpaces = (s: string) => s.replace(/\s+/g, "-");

const slugify = (s: string) =>
 removeSpaces(toLowerCase(trim(s)));

slugify(" Hello World "); // "hello-world"
```

Each piece does one thing and is trivially testable on its own. When something breaks, you know exactly which link in the chain to check. Compare that to debugging one 60-line function where the bug could be hiding anywhere. It's the same instinct behind [structuring a Laravel app around the service-repository pattern](https://shakeebkhan.com/building-a-todo-app-with-the-service-repository-pattern-in-laravel/) — small, focused pieces you can test and swap out on their own.

## Why functional programming in TypeScript is worth the habit change

None of this means you need to rewrite your codebase in a purely functional style, or avoid classes forever, or feel guilty every time you mutate a local variable inside a function. TypeScript isn't Haskell, and it shouldn't pretend to be. What's worth stealing from functional programming in TypeScript is the mindset: prefer small, pure, composable pieces over large functions with hidden dependencies. It makes testing easier, it makes bugs easier to trace, and honestly it just makes code more pleasant to come back to six months later.

If you want to go deeper on the theory side, the [Wikipedia page on functional programming](https://en.wikipedia.org/wiki/Functional_programming) is a decent map of where these ideas come from — lambda calculus, Lisp, all the way through to how modern languages borrowed pieces of it.

Next up, I want to dig into composition over inheritance specifically, since it's one of those FP ideas that quietly reshapes how you design entire systems, not just individual functions. More on that soon.

Happy coding, and go forth and write smaller functions.
