AlgoMaster Logo

Function Basics

Last Updated: May 22, 2026

High Priority
5 min read

A function packages a piece of work, gives it a name, and lets you run it whenever you need to. Without functions, every cart total, discount calculation, and stock check would have to be written inline every time it's used. Go's function syntax is small and regular. This lesson covers the declaration, parameters, a single return value, scoping rules, and the visibility rules that decide whether other packages can call your function.

Declaring a Function

Every Go function starts with the func keyword, followed by the function name, a parameter list in parentheses, the return type, and the body inside braces.

The function greet takes no parameters and returns nothing. The parentheses after the name are required even when the parameter list is empty, and so are the braces around the body. Go is strict about this on purpose: the shape of every function declaration is identical, which makes the file easy to scan.

The opening brace { has to sit on the same line as the closing parenthesis of the signature. Put it on the next line and the compiler will reject the file with a parse error. This rule is the same one gofmt applies everywhere else, and it's why Go code from any team tends to look uniform.

Parameters

A parameter is a named input to the function. Each parameter has a name and a type, written in that order (the opposite of C-style languages).

When main calls printPrice("Wireless Mouse", 24.99), Go binds the first argument to the parameter name and the second to price. The values are copied in, so the function works on its own copies.

When two or more consecutive parameters share a type, you can write the type once at the end. This is purely a shorthand and changes nothing about how the function behaves.

func discount(price, percent float64) float64 reads as "two float64 parameters named price and percent, returning a float64." The longer form func discount(price float64, percent float64) float64 means the same thing. The shorthand only applies when adjacent parameters share a type. As soon as the types differ, you have to spell each one out.

Returning a Value

A function that produces a result declares a return type after the parameter list, then uses the return keyword to send a value back.

The return type goes after the parameter list and before the opening brace. Inside the function, return ends execution and hands the value back to the caller. The expression after return has to match the declared return type, or the compiler refuses to build the file. Mixing int and float64 here forces the explicit float64(quantity) conversion.

A function with a non-void return type must return on every path. The compiler tracks this and complains if any branch falls off the end without a return.

The compiler error is missing return and it points at the closing brace of the function. The fix is to either return on every branch or add a final return after the chain.

This lesson sticks to a single return value. Go also supports multiple return values, which is one of the language's defining features.

Calling a Function and the Order of Declarations

A function call uses the function name followed by parentheses holding the arguments, in the same order as the parameters.

main calls formatLine even though formatLine is defined further down in the file. In some languages this would be an error, since the call appears before the declaration. Go reads the whole file before compiling any function body, so the order of top-level declarations doesn't matter. You can define functions in whatever order makes the file easiest to read, and there's no need for forward declarations or header files.

main calls stockMessage before the source file declares it. Go handles this fine. A common convention is to put main at the top of main.go and the helpers below it, but the compiler doesn't care.

Function Scope and Shadowing

Variables declared inside a function are local to that function. They're created when the function runs and destroyed when it returns. The parameters are also local: each call gets its own copy.

Even though the function modifies its parameter, the caller's variable is untouched. The parameter productName inside describe is a copy, so writing to it doesn't reach back into main. Functions that need to mutate a caller's value take a pointer instead.

Local variables can also shadow names from the enclosing package. Shadowing happens when you declare a new variable with the same name as one in an outer scope, and inside the inner scope the new variable wins.

Inside priceWithTax, the := introduces a brand-new local taxRate that hides the package-level one. The function uses 0.20, but the package-level variable stays at 0.10. Shadowing isn't a bug by itself, but accidental shadowing is, and it's something to watch for. If you meant to update the outer variable, use = instead of :=. The compiler will then refuse to assign because the types or scopes don't line up the way you expected.

Exported vs Unexported Names

Go uses capitalization to decide whether a function is visible outside its package. A name that starts with an uppercase letter is exported and can be called from any package that imports yours. A name that starts with a lowercase letter is unexported and can only be used inside the same package.

PrintReceipt is exported because it starts with P. Code in another package can write cart.PrintReceipt("Alex", 59.97) after importing the package. formatAmount is unexported because it starts with f, so it's a private helper that other packages can't even see.

There's no public or private keyword in Go. The case of the first letter is the only signal. The same rule applies to types, variables, and struct fields. Keep helper functions unexported by default and only export what other packages genuinely need to call.