7 Function Signatures Every JavaScript and TypeScript Developer Should Master
Code that runs is common. Code that teaches is rare.
Most developers reach for code to fix problems.
But the best developers write code that does more than fix. It teaches.
Good code doesn't demand an explanation. Its structure reveals intent, its boundaries define the purpose, and its behavior remains clear even in silence.
This guide is not just a pattern catalog. It's a mental toolkit.
Every function signature here carries a principle of decision rooted in clarity, discipline, and care.
As Epictetus reminds us: Don't explain your philosophy. Embody it.
Let these signatures do the same. Let them say what you mean without apology, without noise.
Quick Reference: Function Shapes That Matter
(input: T) => U
You pass something in and get something else back.
It doesn't touch anything outside its scope. That's the point.
(value: unknown) => value is T
Before you assume what a thing is, this lets you check.
It's not magic. Just careful verification.
() => void
There is no input, no return, just something happening.
You use it when the result doesn't matter. Only the effect does.
(input: T) => Promise<U>
This one waits. It could be a fetch, a delay, or anything that takes time.
You're telling your code: don't expect this right away.
(config: C) => () => T
You give it set up, and it gives you back something ready.
It is helpful when the same logic runs under different conditions.
(fn: F) => G
Wrap a function, change its behavior, or layer something on top.
It is cleaner than rewriting and easier to reason about.
(fn: () => T) => Result<T>
Instead of crashing, you return what happened, good or bad.
It's not about hiding failure. It's about handling it with intent.
1. Pure Function
Signature:
(input: T) => U
Purpose:
Transform input into output. No side effects. No surprises.
Example:
function double(n: number): number {
return n * 2;
}
Why it matters:
A pure function is an anchor. It relies on nothing but its inputs and returns nothing but its result.
In a noisy world, it's a calm center that is always predictable, always safe to test, and always safe to trust.
Before (Anti-pattern):
function addToGlobalSum(n: number): number {
sum += n; // ❌ relies on external state
return sum;
}
2. Type Guard
Signature:
(value: unknown) => value is T
Purpose:
Turn uncertain data into certainty. Narrow unknown to something useful.
Example:
function isUser(value: unknown): value is { id: string } {
return (
typeof value === 'object' &&
value !== null &&
typeof (value as any).id === 'string'
);
}
Why it matters:
APIs can lie. So can user input. Even your code sometimes isn't as predictable as you'd like to think.
A type guard gives you a way to check, not assume. You're not guessing anymore. You're proving it.
Jr Tip:
Type guards use what's called a type predicate. It's how you tell TypeScript, "I've checked this, and here's what I know for sure."
3. Void Function (Procedure)
Signature:
() => void
Purpose:
Trigger an action. The function doesn't return anything. Its job is to cause a change.
Example:
function resetForm(): void {
formState = { name: '', email: '' };
}
Why it matters:
Some functions don't compute how they act.
The void signature tells readers: "This will do something. That's the point."
Before (Anti-pattern):
function saveData(): boolean {
db.save(); return true; // ❌ misleading return value
}
4. Async Function
Signature:
(input: T) => Promise<U>
Purpose:
Work with time, latency, and delay without blocking everything else.
Example:
async function fetchData(url: string): Promise<string> {
const res = await fetch(url);
return res.text();
}
Why it matters:
Async functions accept the world as unreliable, slow, and error-prone.
You return a promise, not a certainty. It's a humble code. Honest code.
Leadership Insight:
When you see async, expect failure. Build for it from the start.
5. Factory Function
Signature:
(config: Config) => () => T
Purpose:
Create a configured function. Keep setup separate from execution.
Example:
function createGreeter(greeting: string) {
return () => console.log(greeting);
}
const greet = createGreeter('Hello');
greet(); // Hello
Why it matters:
A factory delays execution but prepares everything in advance.
It separates concern from effect. It gives you flexibility without cost.
6. Higher-Order Function
Signature:
(fn: Function) => Function
Purpose:
Take a function. Return a function. Change behavior without changing intent.
Example:
function compose<A, B, C>(
f: (b: B) => C,
g: (a: A) => B
): (a: A) => C {
return (a) => f(g(a));
}
Why it matters:
You don't need conditionals to handle every edge. Sometimes, you wrap and reuse.
This is an abstraction with restraint. Reuse without noise.
Mid-Level Tip:
Use this to decorate logic, not to hide it. Clarity first.
7. Error-Handling Wrapper
Signature:
(fn: () => T) => Result<T>
Purpose:
Catch errors and convert them into structured return values.
Example:
type Result<T> =
| { success: true; value: T }
| { success: false; error: Error };
function safe<T>(fn: () => T): Result<T> {
try {
return { success: true, value: fn() };
} catch (e) {
return { success: false, error: e as Error };
}
}
Why it matters:
This is a function that refuses to blow up. It catches the blast and hands you the pieces.
No exceptions. Just data.
Senior Tip:
Use this to isolate risk, especially around parsing, file IO, or legacy code.
Final Thought
A good function solves a problem.
A great function reveals the thinking behind it.
Every shape you choose, every boundary you draw, tells a story.
Discipline lives in those details. Intention lives in the design.
Ten years from now, someone will read your code. Maybe it's you, or perhaps it's not.
Will it explain itself? Or will it leave someone guessing?
Write it now as if no one will be there to explain it.
Let the function speak and say only what it means.