Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're building a small billing module for a freelance project. You need a function that calculates the total of several line items. Your first instinct is probably to pass a slice of floats. It's the logical choice because you don't know if the client has one expense or fifty.

func SumExpenses(amounts []float64) float64 {
    var total float64
    for _, amount := range amounts {
        total += amount
    }
    return total
}

// Calling it looks like this:
total := SumExpenses([]float64{120.50, 45.00, 12.99})

The Friction of Explicit Slices

Now, this works perfectly fine from a technical standpoint. But as you start using this function throughout your codebase, you'll notice a bit of "syntactic noise." Every single time you call SumExpenses, you're forced to wrap your numbers in []float64{...}. It feels clunky. If you only have two numbers to add, writing out the type declaration just to satisfy the function signature feels like unnecessary boilerplate. I've seen a lot of developers stick with this pattern because it's explicit, but in Go, we have a more idiomatic way to handle this when the number of arguments is truly flexible.

Letting the Compiler Handle the Packing

This is where variadic functions come in. By using the ... prefix before the type in your parameter list, you're telling Go: "this function can take zero, one, or many arguments of this type."

func SumExpenses(amounts ...float64) float64 {
    var total float64
    for _, amount := range amounts {
        total += amount
    }
    return total
}

// Now the call is much cleaner:
total := SumExpenses(120.50, 45.00, 12.99)
totalTwo := SumExpenses(10.00, 5.00)
totalEmpty := SumExpenses() // This is perfectly valid too

Here is the secret: inside the function body, amounts is still just a slice ([]float64). Go is doing the heavy lifting for you, packing those individual arguments into a slice before the function logic even starts. You get the clean API of a function call with the power of a slice internally. It's a win-win.

The "Unpacking" Gotcha

There is one point of confusion I always see when people start using variadic functions. What happens if you already have a slice? If you try to pass a []float64 directly into SumExpenses(...float64), the compiler will complain. It expects individual values, not a slice object.

To fix this, you use the ... operator again, but this time on the call side. I like to think of this as "unpacking" or "spreading" the slice. You're telling Go to take the slice and treat it as a list of individual arguments.

expenses := []float64{100.0, 200.0, 300.0}

// This would fail:
// total := SumExpenses(expenses) 

// This works:
total := SumExpenses(expenses...)

It's a small detail, but it's the difference between your code compiling or spending ten minutes wondering why a slice isn't being accepted by a function that's designed to handle slices. Just remember: ... in the function signature packs the arguments; ... in the function call unpacks them.




📋 Practical Task

Build a Flexible Logger for System Events

You need to create a logging utility that can handle an arbitrary number of "tags" (strings) associated with a single log message. Instead of forcing the user to create a slice of tags for every log entry, implement a variadic function.

Requirements:

  • Write a function called LogEvent that takes a message string as the first argument, followed by a variadic number of tags ...string.
  • The function should print the message, and then list all the tags separated by commas (e.g., "User login failed [Tags: auth, critical, network]").
  • If no tags are provided, it should just print the message without the tags section.
  • In your main function, call LogEvent three times: once with no tags, once with three individual strings, and once by "unpacking" a pre-existing slice of strings.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.