Quick Start: Functional Programming
Get started with immutable builders and functional programming in just 5 minutes!
TL;DR - Show Me the Code!
import { createImmutableBuilder, pipe } from '@noony-serverless/type-builder';
interface User {
id: number;
name: string;
email: string;
}
// 1. Create the builder
const userBuilder = createImmutableBuilder<User>(['id', 'name', 'email']);
// 2. Build using pipe
const user = userBuilder.build(
pipe<User>(
userBuilder.withId(1),
userBuilder.withName('Alice'),
userBuilder.withEmail('alice@example.com')
)(userBuilder.empty())
);
console.log(user);
// { id: 1, name: 'Alice', email: 'alice@example.com' }
That's it! You just built an object using functional programming.
What's Different from OOP?
OOP Builder (Mutable)
import { builder } from '@noony-serverless/type-builder';
const createUser = builder<User>(['name', 'email']);
const user = createUser().withName('Alice').withEmail('alice@example.com').build();
Characteristics:
- ✅ Simple and fast
- ✅ Familiar method chaining
- ❌ Mutable state
- ❌ Hard to compose
FP Builder (Immutable)
import { createImmutableBuilder, pipe } from '@noony-serverless/type-builder';
const userBuilder = createImmutableBuilder<User>(['id', 'name', 'email']);
const user = userBuilder.build(
pipe<User>(
userBuilder.withName('Alice'),
userBuilder.withEmail('alice@example.com')
)(userBuilder.empty())
);
Characteristics:
- ✅ Immutable state (safer)
- ✅ Composable functions
- ✅ Easy to test
- ⚠️ Slightly more verbose
- ⚠️ 2-3x slower (still very fast!)
Why Use Functional Programming?
1. Guaranteed Immutability
Every transformation returns a new object. The original never changes.
const state1 = userBuilder.empty(); // {}
const state2 = userBuilder.withName('Alice')(state1); // { name: 'Alice' }
console.log(state1); // Still {} - never changed!
console.log(state2); // { name: 'Alice' }
console.log(state1 !== state2); // true - different objects
Why this matters:
- ✅ No accidental mutations
- ✅ Easier debugging (inspect any state)
- ✅ Safe for React/Redux
- ✅ Time-travel debugging possible
2. Composability
Extract and reuse transformation patterns:
// Define reusable patterns
const adminDefaults = pipe<User>(userBuilder.withRole('admin'), userBuilder.withActive(true));
const guestDefaults = pipe<User>(userBuilder.withRole('guest'), userBuilder.withActive(false));
// Compose with specific data
const admin = userBuilder.build(
pipe<User>(
adminDefaults, // Reuse pattern
userBuilder.withId(1),
userBuilder.withName('Admin User')
)(userBuilder.empty())
);
const guest = userBuilder.build(
pipe<User>(
guestDefaults, // Reuse pattern
userBuilder.withId(2),
userBuilder.withName('Guest User')
)(userBuilder.empty())
);
3. Testability
Pure functions are incredibly easy to test:
// Custom transformation
const normalizeEmail = (state: BuilderState<User>) => {
if (state.email) {
return Object.freeze({
...state,
email: state.email.toLowerCase().trim(),
});
}
return state;
};
// Test it (no mocks needed!)
describe('normalizeEmail', () => {
it('should lowercase and trim', () => {
const input = { email: ' ALICE@EXAMPLE.COM ' };
const result = normalizeEmail(input);
expect(result.email).toBe('alice@example.com');
});
it('should not mutate original', () => {
const input = { email: ' TEST ' };
const result = normalizeEmail(input);
expect(input).not.toBe(result);
expect(input.email).toBe(' TEST '); // Original unchanged
});
});
Installation
npm install @noony-serverless/type-builder
The functional API is included in the main package with unified imports:
import {
createImmutableBuilder,
pipe,
compose,
partialApply,
} from '@noony-serverless/type-builder';
Basic Workflow
Every functional builder follows this pattern:
Step 1: Create the Builder
const userBuilder = createImmutableBuilder<User>(['id', 'name', 'email']);
Step 2: Apply Transformations
const transform = pipe<User>(
userBuilder.withId(1),
userBuilder.withName('Alice'),
userBuilder.withEmail('alice@example.com')
);
Step 3: Build the Final Object
const user = userBuilder.build(transform(userBuilder.empty()));
Or combine steps 2 and 3:
const user = userBuilder.build(
pipe<User>(
userBuilder.withId(1),
userBuilder.withName('Alice'),
userBuilder.withEmail('alice@example.com')
)(userBuilder.empty())
);
Common Patterns
Pattern 1: Simple Object Construction
const product = productBuilder.build(
pipe<Product>(
productBuilder.withId('p1'),
productBuilder.withName('Laptop'),
productBuilder.withPrice(999.99)
)(productBuilder.empty())
);
Pattern 2: With Custom Transformations
const normalizeEmail = (state: BuilderState<User>) => ({
...state,
email: state.email?.toLowerCase().trim(),
});
const user = userBuilder.build(
pipe<User>(
userBuilder.withEmail(' ALICE@EXAMPLE.COM '),
normalizeEmail // Custom transformation
)(userBuilder.empty())
);
// email: 'alice@example.com'
Pattern 3: With Defaults
import { partialApply } from '@noony-serverless/type-builder';
const defaults = partialApply<User>({
role: 'user',
active: true,
});
const user = userBuilder.build(
pipe<User>(
defaults, // Apply defaults first
userBuilder.withId(1),
userBuilder.withName('Alice')
)(userBuilder.empty())
);
// { id: 1, name: 'Alice', role: 'user', active: true }
Pattern 4: Conditional Building
const buildUser = (isAdmin: boolean) =>
pipe<User>(
userBuilder.withId(1),
userBuilder.withName('Alice'),
isAdmin ? userBuilder.withRole('admin') : userBuilder.withRole('user')
);
const admin = userBuilder.build(buildUser(true)(userBuilder.empty()));
const regular = userBuilder.build(buildUser(false)(userBuilder.empty()));
Performance Considerations
How Fast Is It?
// Benchmark results (operations per second)
Interface Builder (OOP): ~400,000 ops/sec ← Fastest
Class Builder (OOP): ~300,000 ops/sec
Immutable Builder (FP): ~150,000 ops/sec ← Still very fast!
Zod Builder (OOP): ~100,000 ops/sec
Is 150k ops/sec slow? No! That's 6.6 microseconds per operation.
When to Use FP vs OOP
Use Functional Programming:
- ✅ Complex state transformations
- ✅ React/Redux state management
- ✅ Reusable transformation patterns
- ✅ Guaranteed immutability needed
Use OOP Builder:
- ✅ Simple object construction
- ✅ Maximum performance critical
- ✅ Hot paths (10,000+ calls/sec)
Use Both:
// FP for complex validation
const validated = pipe(normalizeEmail, validateAge, checkRequired)(input);
// OOP for fast construction
const createUser = builder<User>(['id', 'name']);
const user = createUser().withId(validated.id!).withName(validated.name!).build();
Next Steps
Now that you understand the basics, explore:
- 📖 Immutable Builder Guide - Deep dive into immutable state
- 🔄 Pipe and Compose - Function composition patterns
- 🎨 Higher-Order Functions - Map, filter, fold, and more
- ⚡ Transducers - High-performance transformations
- 🎯 Real-World Examples - Practical use cases
Frequently Asked Questions
Can I mix FP and OOP?
Yes! They work great together:
// FP for validation
const validated = pipe(normalizeEmail, ensureAdult)(rawInput);
// OOP for construction
const createUser = builder<User>(['email', 'age']);
const user = createUser().withEmail(validated.email!).withAge(validated.age!).build();
Do I need to learn all FP concepts?
No! Start with:
createImmutableBuilder- Create builderspipe- Chain transformationspartial- Set defaults
That covers 80% of use cases.
What about TypeScript types?
Full type safety! All FP functions are fully typed:
const userBuilder = createImmutableBuilder<User>(['id', 'name', 'email']);
// TypeScript knows all available methods
userBuilder.withName('Alice'); // ✅ OK
userBuilder.withFoo('bar'); // ❌ Error: Property 'withFoo' doesn't exist
// Pipe is fully typed
pipe<User>(
userBuilder.withId(1), // ✅ OK
userBuilder.withName(123) // ❌ Error: Expected string
);
Is this compatible with Zod?
Yes! Pass a Zod schema for validation:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
email: z.string().email(),
});
const userBuilder = createImmutableBuilder<User>(
['id', 'email'],
UserSchema // Validates on build()
);
const user = userBuilder.build(
pipe<User>(
userBuilder.withId(1),
userBuilder.withEmail('invalid') // ❌ Throws on build()
)(userBuilder.empty())
);
Ready to dive deeper? Check out the Immutable Builder Guide next!