I really dislike functions which accept more than 2 or 3 positional arguments, especially if all of those arguments are of the same type. Something like this:

def send_request(
    url: str,
    method: str,
    content_type: str,
    body: str,
) -> None:
    ...


send_request(
    "http://example.com",
    "GET",
    "text/plain",
    "This is a test!",
)

Positional arguments are the arguments of a function who’s position in the list is significant. Put another way, if you flipped two arguments in a function the meaning would be different. In the example code, all of the arguments can be thought of as positional and if you flipped the url and method arguments things would break because you would be trying to use the URL as an HTTP method and vice versa.

It would be super easy for someone to mistakenly reorder the arguments (if you were switching from Go to Python this would common since http.Client.get puts the method before the URL) and you wouldn’t get type checking help since everything is a string. Depending on the data you may not even be able to obviously pick out that something was entered wrong. Sure, in this example you can probably tell a URL apart from an HTTP method but if these were opaque strings representing an ID then good luck.

Opaque string types are when you have a string who’s value explicitly tells you nothing about what it represents and you shouldn’t read into it’s meaning. We see this a lot when we use UUID’s as identifiers. A UUID isn’t going to tell you if this is an ID for a user, a part number, or just a random jumble of characters.

The bigger this list of arguments gets to the function the more unwieldy things become and sometimes it makes more sense to have more arguments than to switch to a factory pattern or something like it.

It’s also riskier to make changes to the signature of a function (this is more of a problem for some languages over others but it can happen anywhere). For example, if we were working in JavaScript and we removed the content_type argument (maybe we can always infer the content type is going to be application/json now). JavaScript allows for the caller to give more arguments than the signature calls for and the excess is just ignored. In our case that means that the HTTP request wouldn’t actually send the content for the body that we expect. It would send text/plain as the request body and the actual body you gave would be ignored!

But what’s the alternative?

Keyword arguments (kwargs)

I was first introduced to the concept of keyword arguments when I started learning Python. The basic idea is that instead of just listing arguments and referencing them by their position, you instead explicitly give the name of the argument and it’s value. Let’s look at an alternative way we can call the send_request function using keyword arguments instead of positional arguments.

send_request(
    url="http://example.com",
    method="GET",
    content_type="text/plain",
    body="This is a test!",
)

This looks very similar to what we did in the first example but the big difference is that each argument is labeled with exactly what it is. It would be really hard at this point to confuse what each string is for (assuming that the argument names are good) and if something was put in as the wrong argument you could seeit much more easily.

url = "http://example.com"

send_request(
    url="GET",
    method=url,
    content_type="text/plain",
    body="This is a test!",
)

But wait! I use Jetbrains for my IDE and it tells me what each positional argument is already!

Cool, but not everyone is going to be looking at the code in a Jetbrains editor. Maybe they’re using VSCode, maybe they are using Jetbrains but with different settings, or maybe they’re looking at the code in the browser as part of a PR. You just don’t know so you should try and account for that.

One of the other cool things you can do with keyword arguments is that you can move them around without breaking anything! Keyword arguments don’t depend on the position so if you think that a different order for the values makes more sense or better conveys it’s purpose, you can reorder them however you like. That’s a huge convenience that can be overlooked.

OK but that’s Python…

Sure, kwargs is a Python concept but it can be used in other languages without much difficulty. Here’s a few examples in other languages.

Before you say anything, yeah some of these are going to be longer than if you just used positional arguments but you’re adding a little bit of extra code but in exchange you’re getting more clarity and more flexibility.

JavaScript/Typescript

interface SendRequestOptions {
    url: string;
    method: string;
    contentType: string;
    body: string;
}

function sendRequest({
    url,
    method,
    contentType,
    body,
}: SendRequestOptions) {
    // Snip...
}

sendRequest({
    url: "http://example.com",
    method: "GET",
    contentType: "text/plain",
    body: "This is a test!",
});

Go

type SendRequestOptions struct {
    URL string
    Method string
    ContentType string
    Body string
}

func SendRequest(options SendRequestOptions) error {
    // Snip...
}

Java

class Example {
    public static final class SendRequestOptions {
        public final String url;
        public final String method;
        public final String contentType;
        public final String body;
    }

    public static void sendRequest(options SendRequestOptions) {
        // Snip
    }
}

You can see with how verbose Java is and how limiting the syntax is why factories/builders became popular. Creating a new type is super heavy and requires a lot of ceremony.

Summary

Hopefully it’s clear why having keyword arguments is a super helpful tool when you have more than a couple of arguments on a function or where the typing on the arguments doesn’t help the consumer of your function. In most languages it’s easy enough to do. I don’t think I would suggest using kwargs everywhere but definitely any situation where there could be ambiguity.