Fix

"too many arguments" or "not enough arguments" in Go

Fix Go argument count errors by ensuring the number of arguments in your function call matches the function's defined parameters.

When the call site breaks the contract

You are refactoring a service. You add a new parameter to a helper function to pass through a configuration flag. You update the definition. You save. The build fails with a cascade of errors across five files. The compiler lists every call site that missed the update. The error message is blunt: not enough arguments in call to helper. You passed two values. The function now expects three. Go refuses to guess what you meant. It forces you to update every caller.

This error is the compiler enforcing the contract between code. Go functions have a fixed signature. The signature lists the parameters and the return values. Every call must match that signature exactly. No optional arguments. No keyword arguments. If the function asks for three values, you provide three. If it returns two values, you handle two. The compiler checks this at compile time. This strictness prevents runtime surprises where a missing argument defaults to nil or zero in a way you did not expect.

The signature is the law

A function signature defines the shape of data flow. Parameters go in. Return values come out. The count must match. Go does not support default values for parameters. If you need optional behavior, you must design the signature to handle it explicitly, usually with a struct or a variadic pattern.

When the compiler reports too many arguments or not enough arguments, it is comparing the call site against the definition. The mismatch can happen in the input parameters or the output return values. The error message tells you which side failed.

Convention aside: Go does not have named parameters. Arguments are positional. The first value you pass goes to the first parameter. The second value goes to the second parameter. If you swap the order, the types might still match, but the logic will be wrong. The compiler cannot catch semantic errors, only type and count errors. Read the parameter names in the definition to understand the expected order.

Minimal example: counting arguments

package main

import "fmt"

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

func main() {
	// This call matches the signature: two ints in, one int out.
	result := Add(1, 2)
	fmt.Println(result)
}

The function Add declares two parameters: a and b. Both are int. The call Add(1, 2) provides two arguments. The compiler sees two arguments. It checks the types. int matches int. It sees one return value. result captures it. The program compiles.

Break the count and the compiler stops you.

// Error: too many arguments in call to Add
Add(1, 2, 3)

The compiler rejects this with too many arguments in call to Add. You passed three values. The function only has slots for two. There is no third parameter to receive the 3.

// Error: not enough arguments in call to Add
Add(1)

The compiler rejects this with not enough arguments in call to Add. You passed one value. The function needs two. The parameter b has no value.

Goroutines are cheap. Channels are not magic. Signatures are contracts. Break the contract and the compiler stops the build.

Walkthrough: how the compiler checks

When the compiler encounters a function call, it performs a resolution step. It looks up the function name in the current scope. It finds the definition. It reads the parameter list. It counts the parameters. It then looks at the arguments in the call. It counts the arguments.

If the counts differ, the compiler emits an error immediately. It does not attempt to infer missing values. It does not attempt to ignore extra values. The check is structural.

The same logic applies to return values. If a function returns two values, the call site must assign to two variables, or assign to one variable and discard the rest, or ignore all returns. If you assign to one variable but the function returns two, the compiler complains with assignment mismatch: 1 variable but Func returns 2 values. This is the return-side equivalent of an argument count error. The structure must match.

Realistic patterns: returns, variadics, and context

Real code rarely uses simple single-return functions. You will encounter multiple returns, variadic parameters, and context propagation. Each pattern has specific rules for argument counts.

Multiple return values

Go functions can return multiple values. This is common for returning a result and an error.

// Divide returns the quotient and the remainder.
func Divide(a, b int) (int, int) {
	return a / b, a % b
}

func main() {
	// Correct: capture both return values.
	q, r := Divide(10, 3)
	fmt.Println(q, r)
}

If you try to capture only one value, the compiler rejects the code.

// Error: assignment mismatch: 1 variable but Divide returns 2 values
q := Divide(10, 3)

Use the blank identifier _ to discard values you do not need. This explicitly tells the compiler and the reader that you saw the second value and chose to drop it.

// Discard the remainder. The underscore satisfies the count requirement.
q, _ := Divide(10, 3)
fmt.Println(q)

Convention aside: if err != nil { return err } is verbose by design. The community accepts the boilerplate because it makes the unhappy path visible. When a function returns a value and an error, always check the error. Do not discard the error with _ unless you have a specific reason and a comment explaining why.

Variadic functions

Variadic functions accept a variable number of arguments. The last parameter is prefixed with .... This parameter acts as a slice of the specified type.

// Sum adds all integers in the list.
func Sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

func main() {
	// Variadic functions accept zero or more arguments.
	fmt.Println(Sum(1, 2, 3))
	fmt.Println(Sum())
}

The compiler allows any count of arguments for a variadic parameter. Sum(1, 2, 3) passes three arguments. Sum() passes zero arguments. Both are valid.

The trap appears when you have a slice and want to pass it to a variadic function.

nums := []int{1, 2, 3}

// Error: cannot use nums (variable of type []int) as int value in argument to Sum
Sum(nums)

The compiler sees nums as a single value of type []int. The function expects individual int arguments. You must use the splat operator ... to expand the slice.

// Use the ... operator to expand a slice into individual arguments.
Sum(nums...)

Convention aside: Variadic parameters must be the last parameter. You cannot define func Log(args ...interface{}, level string). The compiler requires the variadic to be at the end so it knows where the variable arguments stop and the fixed arguments begin.

Context propagation

Functions that perform I/O or long-running work should accept a context.Context as the first parameter. This allows callers to control cancellation and deadlines.

import "context"

// FetchData retrieves data from the API.
func FetchData(ctx context.Context, id string) ([]byte, error) {
	// Implementation checks ctx.Done() for cancellation.
	return nil, nil
}

func main() {
	ctx := context.Background()
	// Correct: context is the first argument.
	data, err := FetchData(ctx, "123")
	_ = data
	_ = err
}

If you forget the context, you get a count error.

// Error: not enough arguments in call to FetchData
data, err := FetchData("123")

The compiler reports not enough arguments in call to FetchData. You passed one argument. The function expects two. The missing value is the context.

Convention aside: context.Context always goes as the first parameter, conventionally named ctx. Functions that take a context should respect cancellation and deadlines. If you see a count error on a function that does I/O, check if you missed the context argument.

Context is plumbing. Run it through every long-lived call site.

Pitfalls: receivers, interfaces, and hidden counts

Argument count errors can hide behind method syntax and interface satisfaction.

Method receivers

Methods are functions with a receiver. The receiver acts like an implicit first argument. When you call a method, you do not pass the receiver as an argument.

type Counter struct {
	count int
}

// Increment adds one to the counter.
func (c *Counter) Increment() {
	c.count++
}

func main() {
	c := &Counter{}
	// Correct: method call syntax. The receiver is implicit.
	c.Increment()
}

If you treat the method like a standalone function, you get an error.

// Error: undefined: Increment
Increment(c)

The compiler reports undefined: Increment because Increment is a method bound to Counter, not a top-level function. You must call it on an instance.

If you have a function and a method with similar names, mixing them up causes count errors.

// GlobalFunction takes a Counter.
func GlobalFunction(c *Counter) {
	c.count++
}

func main() {
	c := &Counter{}
	// Function call: pass the receiver explicitly.
	GlobalFunction(c)

	// Method call: receiver is implicit.
	c.Increment()
}

The receiver is part of the signature. Method calls hide the receiver. Function calls expose it.

Interface satisfaction

Interfaces define a set of methods. A type implements an interface if it has all the methods with the correct signatures. If a method has the wrong number of arguments or return values, the type does not implement the interface.

type Writer interface {
	Write(p []byte) (n int, err error)
}

type MyWriter struct{}

// Write implements Writer.Write.
func (m MyWriter) Write(p []byte) (int, error) {
	return len(p), nil
}

If you change the method signature, the implementation breaks.

// Write no longer matches the interface.
func (m MyWriter) Write(p []byte) int {
	return len(p)
}

The error might appear far away where you pass MyWriter to something expecting Writer.

// Error: MyWriter does not implement Writer (Write method has wrong number of outputs)
var w Writer = MyWriter{}

The compiler reports MyWriter does not implement Writer (Write method has wrong number of outputs). The count mismatch is in the method definition, not the call site. Check the interface definition and the method signature.

Convention aside: "Accept interfaces, return structs" is the most common Go style mantra. If you are writing a function that takes a Writer, you are accepting an interface. The implementation must match the interface signature exactly. Do not add parameters to an interface method unless you update all implementations.

Decision: choosing the right signature shape

Argument count errors often stem from a signature that fights the use case. Choose the signature shape based on the data.

Use a fixed-argument function when the number of inputs is known and stable.

Use a variadic function when the inputs are homogeneous and the count varies, like a list of items to process.

Use a struct argument when you have many optional parameters or the arguments form a logical group.

Use multiple return values when a function naturally produces two distinct results, like a value and an error.

Use a single return value with a pointer receiver when you need to modify the receiver state.

Use a context parameter as the first argument for any function that performs I/O or long-running work.

Don't fight the type system. Wrap the value or change the design.

Where to go next