Use Named Params, not Positional Params, in TypeScript

Max Greenwald | 2025-09-21

I frequently see functions in TypeScript with a long list of positional params such as doThing(a, b, c, d, e). I grimace a little every time I see an ordered jumble like this, knowing that this will inevitably lead to pain and misery in the future. But, there's a better way, and it's not hard: use named params!

TL;DR? See the summary 👇 of when to use named params.

What's a Positional Param?

Let's say we are calling a function to insert a user into the database.

createUser("Joan of Arc", "joan@arc.atlassian", false, 613, true);

Great, we've got a new user! However, what do true and false mean? And what is that 613? Looking at this function call, it's hard to tell.

This function uses Positional Params. The order of the parameters going into the function call is significant, and we cannot reorder the params when calling the function without changing the result.

What's a Named Param?

Here's that same function, but this time using Named Params:

createUser({
  name: "Joan of Arc",
  email: "joan@arc.atlassian",
  isAdmin: true,
  age: 613,
  sendMarketingEmails: false,
});

Named Params do not have significant ordering, and instead are, you guessed it, named!

Why are Named Params Better?

1. Parameter meaning at the call site is easy to understand

In the createUser example, it would be really easy to accidentally swap name and email, or even isAdmin and sendMarketingEmails. They are of the same type, and the only thing that distinguishes them is their position.

createUser("joan@arc.atlassian", "Joan of Arc", true, 613, false); // oops

By using Named Params, we can very clearly tell which argument is which at the call site, which means there is much less chance we make a mistake.

createUser({
  name: "Joan of Arc", // Very clear
  email: "joan@arc.atlassian",
  isAdmin: false,
  sendMarketingEmails: true,
  age: 613
});

2. Refactoring is so much easier

Let's say we want to add a new required param to this function phoneNumber. If we used positional params, we might be able to add it at the end. However, sendMarketingEmails is already optional, so I guess we'll have to add undefined everywhere we don't pass sendMarketingEmails:

createUser(/*...*/, undefined,  "1207603802"); // 🤮

Instead, if we're using named params, this is easy!

createUser({
  name: "Joan of Arc",
  email: "joan@arc.atlassian",
  isAdmin: true,
  age: 613,
  phoneNumber: "1207603802"
});

And, if we decide to remove sendMarketingEmails later, none of the existing code needs to change.

Real world example: next/router

In the Next.js Pages Router next/router, there is a method to imperatively begin a page navigation.

router.push("/blog/do-more-with-run");

However, at some point a second optional argument was added, as. This was only used before Next.js 9.5.3 for dynamic routes to be specific about which page was being navigated to, and which specific path params were to be used on that page.

router.push(`/blog/[slug]`, `/blog/do-more-with-run`); // ok...

Then, they wanted to add more "options" to this function! But, because there was already something in the second position, it had to be:

router.push(`/blog/do-more-with-run`, undefined, { scroll: false }); // Again, 🤮

In order to fix this, a fully new API, next/navigation, was created that removed that middle param! That middle param also became irrelevant at some point and it became possible to exclude it entirely. Sadly, next/router will live on for quite a while and that pesky unused and unneeded undefined will live on in many codebases.

3. IDE autocomplete is significantly more useful

Many IDEs (including VSCode, which has now been forked so many times that I think it will eventually be treated like the Linux kernel with umpteen distributions) allow typing " or ⌘-space to get a list of named params. While it's possible to use go-to-definition or hover to reveal the type signature when using positional params, the DX here is so much better with named params.

createUser({
  " /* completion shows: name, email, age, isAdmin, sendMarketingEmails ... */
})

...and more!

  • Named params allow the arguments to be extracted as an object type, and satisfies can be used elsewhere to ensure an object matches the type of the params
  • Union types allow different combinations of arguments depending on the situation, which with positional params requires function definition overloading
  • Named params are much easier to spread because there is much less concern about ordering. This is how React props work by default!

Summary: When to Prefer Named Params

  1. Any time there is more than one param. Really!
  2. If there is clearly a "primary" param and then some options (optional), use the fn(primary, options) pattern.