I’m Kayla, and I write a lot of TypeScript. I use optional parameters every week. Some days they save my bacon. Other days… they bite. Here’s my real take, with code I’ve shipped.
If you want an even deeper dive into how I think about this feature, you can skim my standalone deep-dive on optional parameters.
So, what’s an optional parameter?
It’s a function parameter you can skip. You mark it with a question mark. (The official TypeScript Handbook lays out the formal definition and rules if you want the canonical wording.)
For a broader set of practical TypeScript tips, I highly recommend the tutorials over at Improving Code.
function greet(name?: string) {
if (name) {
return `Hi, ${name}!`;
}
return "Hi!";
}
greet(); // "Hi!"
greet("Maya"); // "Hi, Maya!"
Simple, right? It feels like a tiny switch. On or off. Your call.
Where it helped me at work
I built a small pricing tool. Sometimes we pass a tax rate. Sometimes we don’t. I liked that the function stayed short and clean.
function addTax(amount: number, taxRate?: number) {
const rate = taxRate ?? 0.07; // use 7% if not given
return amount * (1 + rate);
}
addTax(100); // 107
addTax(100, 0.2); // 120
I also had a fetch wrapper with an optional callback. When our team did quick tests, we skipped the callback. When we ran full flows, we used it.
async function fetchJson(url: string, onDone?: (data: unknown) => void) {
const res = await fetch(url);
const data = await res.json();
onDone?.(data); // call only if it exists
return data;
}
In a React app, I pass an optional config to a helper. It keeps the prop code readable.
type ToastConfig = {
delayMs?: number;
kind?: "success" | "error" | "info";
};
function showToast(message: string, config?: ToastConfig) {
const delay = config?.delayMs ?? 1500;
const kind = config?.kind ?? "info";
console.log(`[${kind}] ${message}`);
setTimeout(() => {}, delay);
}
You know what? It feels natural. Like saying, “Bring it if you have it. If not, we’re fine.”
The little twist with defaults
Here’s the thing: “optional” is not the same as a default value. You can have both, but they behave a bit different.
// Optional, no default
function ping(timeoutMs?: number) {
const t = timeoutMs ?? 3000;
return `Waiting ${t}ms...`;
}
// Not optional, but has a default
function pong(timeoutMs: number = 3000) {
return `Waiting ${timeoutMs}ms...`;
}
ping(); // okay, uses 3000
pong(); // also okay, uses 3000
ping(undefined); // also okay
pong(undefined); // okay, still 3000 (default kicks in)
I use defaults when I always want a value inside. I use optional when “missing” is part of the meaning.
A tiny pitfall that got me
One time I put an optional parameter before a required one. TypeScript yelled, and it was right.
// Don't do this
function bad(a?: number, b: number) {} // Error
// Do this
function good(a: number, b?: number) {}
Required ones go first. Keep the shape clear.
Another gotcha: undefined vs. empty
Optional means the param can be missing or undefined. But that’s not the same as an empty string or zero. I tripped on this once in a search bar.
function search(query?: string) {
if (!query) {
// This runs for "", undefined, null (if unioned), 0 (not string) — careful
return "No search. Try typing.";
}
return `You searched: ${query}`;
}
I learned to check the exact thing I care about.
function search2(query?: string) {
if (query === undefined) {
return "No search. Try typing.";
}
if (query.trim() === "") {
return "Empty search. Please add text.";
}
return `You searched: ${query}`;
}
When you really mean “trust me, it’s defined,” you might reach for the exclamation mark !—I rant about that in this short piece. (If you want another quick primer with examples, the GeeksforGeeks guide on TypeScript optional parameters is a handy read.)
Feels a bit fussy, but it avoids weird bugs.
Optional with objects (and a small rant)
I love optional fields on config types. But you have to think about defaults.
type EmailOptions = {
cc?: string[];
bcc?: string[];
track?: boolean;
};
function sendEmail(to: string, opts?: EmailOptions) {
const cc = opts?.cc ?? [];
const bcc = opts?.bcc ?? [];
const track = opts?.track ?? true;
// send it...
}
One day I forgot a safe default. A field came through as undefined. The mailer blew up. Logs were not cute. Now I always set a default inside the function.
Destructuring with optional parameters
This one feels neat but can be tricky to read. I used it in a CLI tool.
type Flags = {
silent?: boolean;
retry?: number;
};
function runTask({ silent = false, retry = 2 }: Flags = {}) {
if (!silent) console.log("Running...");
// ...
}
Here the whole object is optional, and each field is optional, with defaults. It looks tidy. But if your team is new to TS, leave a comment. Be kind to future you. That destructuring style feels close to having named constructor arguments in plain functions.
While optional parameters in code aim to reduce friction, consumer apps try to do the same in their sign-up flows. I was recently looking at how casual-dating platforms keep onboarding forms minimal—check out this 2025 roundup of DTF-oriented hookup apps — the write-up breaks down which fields are mandatory and which are deferred, giving you real-world inspiration on progressive disclosure, lean UX, and optional data collection. If you want to see a concrete, location-specific example of how an adult classifieds site balances “just enough” required info with optional search filters, take a peek at AdultLook's Virginia Beach listings — you'll notice how the page trims friction for newcomers while still offering power users advanced filter options, a practical demonstration of the same optional-vs-required dance we juggle in code.
Overloads vs. a single optional
I used overloads when I wanted strict shapes for two call styles. It felt safer.
// overloaded
function readFile(path: string): Promise<string>;
function readFile(path: string, asJson: true): Promise<unknown>;
function readFile(path: string, asJson?: boolean) {
// ...
}
// call sites
await readFile("data.txt"); // string
await readFile("data.json", true); // unknown (JSON)
If you’re curious about why I let the path stay a raw string instead of a branded type, I unpack that decision in my file-path argument deep dive.
If you only need “maybe there, maybe not,” a single optional is fine. If the meaning changes a lot, overloads read better.
Quick checks I use before I ship
- Will missing mean something clear? If yes, use optional. If not, use a default.
- Are defaults set inside the function? Future me will forget, so I add them.
- Did I keep optional params after required ones? Yes? Good.
- Am I checking for undefined when I mean “not given”? I try to be exact.
- Is my team comfy with the syntax? If not, I write a short doc string.
Real bugs I hit (and fixed)
- I treated 0 like “not given.” A sale price of 0 got skipped. Ouch. I switched to a strict check: value === undefined.
- I forgot the question mark on a callback. Callers passed nothing. It crashed. I added the ? and used onDone?.() safely.
- I used null in one place and undefined in another. Type got messy. I picked one and stuck with it. Peace returned.
- I
