The Curse of Skipped Parameters in TypeScript
\nHave you ever written a function with ten optional parameters, and then tried to pass only the fifth and tenth? And immediately got a compilation error, or worse, a silent logical bug? Sound familiar?
\nOn Stack Overflow, this question has gathered thousands of views. TypeScript developers constantly stumble over the same rake: how to pass one optional parameter while skipping another? After all, the standard syntax doesn't allow this – parameters are passed by position, not by name.
\nToday, we will finally sort out this pain once and for all. I will show you 4 working ways to solve the problem: from simple refactoring to advanced techniques with partial objects. Let's go!
\n\nWhy is this even a problem? The Positional Curse
\nIn TypeScript (as in JavaScript), function parameters are passed in order. If you have a function:
\nfunction createUser(\n name: string,\n age?: number,\n email?: string,\n isAdmin?: boolean\n) { /* ... */ }\nYou cannot call it like this: createUser('John', , 'john@mail.com'). Skipping a parameter leaves a hole that TypeScript won't digest. You'll have to pass undefined or null, which clutters the code and logic.
But there is good news: we can bypass this limitation in several elegant ways. Let's break down each one.
\n\nMethod 1: Parameter Object (The Most Correct)
\nThis is the recommended pattern in serious projects. Instead of a bunch of positional parameters, we use a single object with optional fields:
\ninterface UserOptions {\n name: string;\n age?: number;\n email?: string;\n isAdmin?: boolean;\n}\n\nfunction createUser(options: UserOptions): void {\n const { name, age, email, isAdmin } = options;\n console.log(`User ${name} created, age: ${age ?? 'not specified'}`);\n}\n\n// Now you can pass only what's needed!\ncreateUser({ name: 'Anna', email: 'anna@mail.com' });\ncreateUser({ name: 'Peter', isAdmin: true });\n\nThe advantages are obvious:
\n- \n
- No matter how many parameters – we pass only what's needed \n
- The code is self-documenting: it's immediately clear what each field is for \n
- Easy to add new fields without changing the signature \n
This approach solves 90% of problems with optional parameters. But if you're working with legacy code or cannot change the API – read on.
\n\nMethod 2: Function Overloading
\nTypeScript allows you to declare multiple signatures for one function. This is convenient when you have specific combinations of parameters:
\n// Declare overloads\nfunction getData(url: string): Promise<any>;\nfunction getData(url: string, timeout: number): Promise<any>;\nfunction getData(url: string, timeout: number, retries: number): Promise<any>;\n\n// Implementation (must be the most general)\nfunction getData(\n url: string,\n timeout?: number,\n retries?: number\n): Promise<any> {\n // logic\n return fetch(url, { signal: AbortSignal.timeout(timeout ?? 5000) });\n}\n\n// Now you can call it like this:\ngetData('/api/users'); // timeout = 5000 (default)\ngetData('/api/users', 3000); // timeout = 3000\n// But like this – you cannot (skipping timeout is not allowed without overloading):\n// getData('/api/users', undefined, 2); // bad\n\nDownside: overloads don't solve the problem of skipping middle parameters. They only provide strict combinations. For flexible passing, it's better to use the first method.
\n\nMethod 3: Rest Parameters + Destructuring (Exotic)
\nIf you want to preserve positionality but add flexibility, you can use tuples with rest:
\ntype OptionalParams = [number, string?, boolean?];\n\nfunction processData(\n required: string,\n ...opt