Fix

"not enough arguments in call to X"

This error occurs when you call a function with fewer arguments than its definition requires, often due to a missing parameter, a mismatched variadic argument, or calling a method without the receiver.

The contract is broken

You copy a function call from a tutorial. You paste it into your code. You run go build. The compiler stops you with not enough arguments in call to fmt.Println. You stare at the line. It looks fine. You count the arguments. You count the parameters. They match. Or do they? The compiler is right. You missed something subtle. Maybe a variadic expansion. Maybe a method receiver. Maybe a type mismatch that shifted everything. This error is the compiler's way of saying the contract between the caller and the callee is broken. The function promised to take three things. You handed it two. Or you handed it one thing that looked like three but wasn't.

Function signatures are strict contracts

A function signature is a contract. It lists exactly what the function needs to do its job. The call site is where you invoke the function. The call site must provide every item on the list, in the exact order, with the exact types. Go checks this at compile time. It does not guess. If the signature says func Add(a int, b int), the call Add(5) is a breach of contract. The compiler rejects it immediately. This strictness prevents runtime crashes where a missing value causes undefined behavior. You fix the mismatch before the program runs.

The signature defines the parameter list. The call defines the argument list. The compiler compares them position by position. It checks the count first. If the argument list is shorter than the parameter list, it emits not enough arguments in call to X. It does not tell you which argument is missing. It tells you the function name and the line number. You have to look at the signature and count.

Minimal example

package main

import "fmt"

// Add takes two integers and returns their sum.
func Add(a int, b int) int {
    return a + b
}

// Log takes a message and optional values.
// The message is required. The values are variadic.
func Log(msg string, vals ...int) {
    fmt.Println(msg, vals)
}

func main() {
    // ERROR: not enough arguments in call to Add
    // Add expects two ints. You provided one.
    // result := Add(10)

    // FIX: Provide both arguments.
    result := Add(10, 20)
    fmt.Println(result)

    // Variadic functions require all non-variadic args.
    // The ... part is optional, but the string is not.
    Log("Status", 1, 2, 3)
    Log("Status") // Valid: variadic part can be empty.
}

The compiler counts the items in the parentheses. Add(10) has one item. Add expects two. The count is wrong. Log("Status") has one item. Log expects at least one item because msg is mandatory. The variadic part vals ...int can be zero items. The call satisfies the minimum count.

How the compiler checks

When you run go build, the compiler parses your code into an abstract syntax tree. It resolves function calls by looking up the symbol in the package scope. It compares the argument list in the call against the parameter list in the definition. It checks count and type. If the count is lower, it emits not enough arguments in call to X. This happens before code generation. No binary is produced.

The error message usually points to the line and the function name. It does not suggest a fix. You must open the definition of the function or check the documentation. In an IDE, hovering over the function shows the signature. In the terminal, go doc fmt.Println prints the signature. Compare the list. Count the items. The difference is the missing argument.

Realistic scenarios

Methods add a hidden layer. A method is a function with a receiver. The receiver counts as the first argument. If you call a method on a value, the value is the receiver. If you call it as a function, you must pass the receiver explicitly.

package main

import (
    "fmt"
    "context"
)

// Server holds configuration for an HTTP handler.
type Server struct {
    Port int
}

// Listen starts the server on the configured port.
// The receiver s is implicit when called as s.Listen().
func (s Server) Listen() {
    fmt.Printf("Starting server on port %d\n", s.Port)
}

// Fetch retrieves data from a URL.
// It follows the convention of taking context.Context as the first parameter.
func Fetch(ctx context.Context, url string) {
    fmt.Printf("Fetching %s with context %v\n", url, ctx)
}

// FormatMessage joins strings with a separator.
// It accepts a separator and a variadic list of strings.
func FormatMessage(sep string, parts ...string) string {
    result := ""
    for i, part := range parts {
        if i > 0 {
            result += sep
        }
        result += part
    }
    return result
}

func main() {
    srv := Server{Port: 8080}

    // Calling as a method. The receiver srv is bound implicitly.
    srv.Listen()

    // Calling as a function. You must pass the receiver explicitly.
    // This is equivalent to srv.Listen().
    Server.Listen(srv)

    // Context is always the first parameter.
    // Forgetting it triggers a count error.
    // Fetch("http://example.com") // ERROR: not enough arguments

    // FIX: Pass context as the first argument.
    Fetch(context.Background(), "http://example.com")

    // Variadic expansion with slices.
    tags := []string{"go", "faq", "tutorial"}

    // ERROR: not enough arguments in call to FormatMessage
    // FormatMessage expects a string separator first.
    // Passing tags directly provides a []string, not a string separator.
    // msg := FormatMessage(tags)

    // FIX: Provide the separator, then expand the slice.
    // The ... operator unpacks the slice into individual arguments.
    msg := FormatMessage(", ", tags...)
    fmt.Println(msg)
}

The Server example shows two ways to call a method. srv.Listen() uses the method syntax. The receiver srv is bound to the call. The compiler treats it as the first argument. Server.Listen(srv) uses the function syntax. You pass the receiver explicitly. Both are valid. If you call Listen() without a receiver, the compiler reports not enough arguments in call to Listen. You must provide the receiver.

The Fetch example highlights a common convention. Functions that perform I/O or long-running tasks take context.Context as the first parameter. If you define func Fetch(ctx context.Context, url string), the call Fetch("http://example.com") triggers not enough arguments in call to Fetch. The compiler sees one argument. The signature requires two. You missed the context. Always pass context.Background() or a derived context as the first argument. This convention ensures cancellation and deadlines propagate through your call stack.

The FormatMessage example shows the variadic slice trap. tags is a slice of strings. FormatMessage expects a string separator followed by zero or more strings. Passing tags directly provides one argument of type []string. The function expects a string as the first argument. The compiler sees one argument where it expects at least one string. It reports not enough arguments in call to FormatMessage. You must use the expansion operator .... Write FormatMessage(", ", tags...). The ... tells the compiler to unpack the slice elements as individual arguments.

Pitfalls and compiler messages

The most common trap is the variadic slice. If you have a slice []string and a function func Join(sep string, parts ...string), passing the slice directly fails. The function expects a string separator. The slice is a single argument of type []string. The compiler sees one argument where it expects at least one string. It reports not enough arguments in call to Join. You must use the expansion operator .... Write Join(", ", slice...). The ... tells the compiler to unpack the slice elements as individual arguments.

Another trap is the method receiver. If you define func (c *Client) Get(url string), the receiver c is an argument. If you call Get(url) without a receiver, the compiler cannot find the method context. It reports not enough arguments in call to Get. You must call it on an instance: client.Get(url). If you call it as a function, you must pass the receiver: Client.Get(client, url).

Type mismatches can masquerade as argument count errors. If the signature is func Process(name string, count int) and you call Process(10), the compiler complains about count. It sees one argument. It expects two. If you call Process(10, "hello"), the compiler complains about types. It sees two arguments. The first is an int. It expects a string. It reports cannot use 10 (untyped int constant) as string value in argument to Process. Fix the first argument type, and the count error often resolves. The compiler checks arguments left to right. A type error on an early argument can prevent the compiler from parsing the rest, leading to confusing messages.

Receiver naming follows a community convention. Use short names, usually one or two letters matching the type. Write (c *Client) or (cl *Client). Avoid (this *Client) or (self *Client). The community standard keeps code concise. The receiver name does not affect the argument count, but following conventions makes code easier to read.

Decision matrix

Use a direct function call when the function takes fixed arguments and you have all values available. Use the variadic expansion operator ... when passing a slice to a variadic parameter to unpack elements individually. Use a method call on a receiver when the function is bound to a type and you have an instance of that type. Use the function syntax with explicit receiver when you need to pass the method as a value or call it without an instance variable. Use context.Background() as the first argument when calling functions that require a context. Trust the signature. It tells you exactly what is needed.

Where to go next