Question

How to Organize a Multi-File Go Project During Development

Question

I am building a Go project with multiple files and want to organize it correctly during development.

If my code belongs to a single package called mypack, I assume I should place all .go files for that package inside a mypack directory.

However, I also want to try the package while developing it, which means I need a separate file that declares package main so I can run something like:

package main

import "mypack"

func main() {
    // try mypack here
}

What is the standard way to organize a Go project in this situation?

Should I create a separate main program for testing the package manually?

Do I need to run go install mypack every time I want to try the code, or is there a better development workflow?

Short Answer

By the end of this page, you will understand how Go projects are commonly organized using packages and executable programs, why package main should usually be kept separate from reusable library code, and how to test or run your code during development without reinstalling everything each time.

Concept

In Go, project organization is built around packages.

A package is a directory of .go files that all declare the same package name, except for special test files. If you are writing reusable code, that code should usually live in its own package directory such as:

mypack/

Inside that directory, your files might look like this:

// file1.go
package mypack

// file2.go
package mypack

If you also want an executable program that uses that package, that program should normally live in a different directory and use package main.

Why? Because in Go:

  • A single directory normally represents one package.
  • Files in the same directory must use the same package name.
  • package main creates an executable program.
  • Other package names create reusable libraries.

So your reusable code and your runnable app are usually separated.

This matters because real Go projects often contain:

  • one or more reusable packages
  • one or more command-line programs
  • test files
  • sometimes example programs

A clean structure makes it easier to:

  • run code during development
  • write tests
  • reuse packages in other programs
  • keep executable code separate from library logic

In modern Go development, a common pattern is:

project-root/
  go.mod
  mypack/
    file1.go
    file2.go
    mypack_test.go
  cmd/
    tryapp/
      main.go

Here:

  • mypack/ contains reusable package code.
  • cmd/tryapp/main.go contains a small executable you can run while developing.

You do not need to reinstall the package every time just to test it manually. You can use go run on the main program, and Go will build the package as needed.

Mental Model

Think of a Go package as a toolbox and package main as a worker using the toolbox.

  • The toolbox contains reusable tools: functions, types, and methods.
  • The worker picks up those tools and uses them to do a job.

You do not usually store the worker inside the toolbox.

In the same way:

  • mypack should contain the reusable logic.
  • main should live separately and call into mypack.

Tests are like a checklist for the toolbox. They live alongside the toolbox so you can quickly verify that each tool still works.

Take Quiz

Syntax and Examples

The key rule is: one directory, one package.

Basic package structure

project-root/
  go.mod
  mypack/
    math.go
    format.go
// mypack/math.go
package mypack

func Add(a, b int) int {
    return a + b
}
// mypack/format.go
package mypack

func Name() string {
    return "mypack"
}

Separate runnable program

project-root/
  go.mod
  mypack/
    math.go
  cmd/
    tryapp/
      main.go
// cmd/tryapp/main.go
package main

import (
    "fmt"
    "project-root/mypack"
)

func main() {
    fmt.Println(mypack.Add(2, 3))
}

You can run it with:

go run ./cmd/tryapp

Testing the package

// mypack/math_test.go
package mypack

import "testing"

func TestAdd(t *testing.T) {
    got := Add(2, 3)
    want := 5

    if got != want {
        t.Fatalf("got %d, want %d", got, want)
    }
}

Run tests with:

go test ./...

Important idea

During development, you usually use:

  • go run to try executable programs
  • go test to verify package behavior
  • go build to check that code compiles

You do not need go install every time.

go install is mainly for installing a built executable into your Go bin directory.

Step by Step Execution

Consider this project:

project-root/
  go.mod
  mypack/
    greet.go
  cmd/
    tryapp/
      main.go
// mypack/greet.go
package mypack

func Greet(name string) string {
    return "Hello, " + name
}
// cmd/tryapp/main.go
package main

import (
    "fmt"
    "project-root/mypack"
)

func main() {
    message := mypack.Greet("Sam")
    fmt.Println(message)
}

If you run:

go run ./cmd/tryapp

here is what happens:

  1. Go finds the main package in ./cmd/tryapp.
  2. It sees that main.go imports project-root/mypack.
  3. It loads the source files inside the mypack directory.
  4. It compiles both the package and the main program.
  5. It creates a temporary executable.
  6. It runs that executable.
  7. The output is:
Hello, Sam

Notice that you did not manually install mypack first. Go handled the dependency automatically for this local run.

If you instead run:

go test ./mypack

Go:

  1. loads the package source files
  2. loads *_test.go files
  3. builds a test binary
  4. runs the tests

This is the normal workflow for package development.

Real World Use Cases

This package-and-main separation is used in many real Go projects.

Command-line tools

A CLI tool often has:

  • reusable logic in internal or package directories
  • a small main.go in cmd/toolname

Example:

  • parsing files in a package
  • calling that package from a CLI command

Web services

A web app may separate:

  • HTTP handlers
  • business logic
  • database access
  • executable startup code

The main package sets up the server, while packages contain reusable logic.

SDKs and libraries

If you are building code for reuse by other Go programs, the package should be clean and independent from any particular executable.

Experiments and local demos

During development, many developers create a small runner program to manually try a package without mixing demo code into the package itself.

Take Quiz

Real Codebase Usage

In real codebases, developers usually avoid putting temporary testing code directly into a library package.

Common patterns include:

Small cmd/... programs

A project may include one or more runnable programs:

cmd/
  api-server/
  worker/
  debug-tool/

Each directory contains a separate main package.

Unit tests for behavior

Instead of manually trying everything through main, developers write tests:

  • unit tests for functions
  • table-driven tests for multiple cases
  • benchmarks for performance-sensitive code

Example programs

Sometimes developers add examples for documentation or learning:

func ExampleAdd() {
    fmt.Println(Add(2, 3))
    // Output: 5
}

Guarding library boundaries

A good library package usually avoids:

  • reading command-line flags directly
  • printing too much directly to stdout
  • hardcoding app-specific behavior

That logic belongs in main or in higher-level application code.

Fast development loop

A common workflow is:

go test ./...
go run ./cmd/tryapp

This gives both automated checks and a manual executable for experimentation.

Common Mistakes

Mixing package main and another package in one directory

This is one of the most common mistakes.

Broken example:

// mypack/file1.go
package mypack
// mypack/trypack.go
package main

This does not work because a directory should not contain files from different packages in normal use.

Using go install as the main development workflow

Beginners sometimes think they must install after every change.

Usually better:

go run ./cmd/tryapp
go test ./...

Use go install when you want the executable installed for repeated use.

Putting too much logic in main

Broken style:

package main

import "fmt"

func main() {
    // all logic here
    fmt.Println(2 + 3)
}

This works for tiny programs, but it becomes hard to test and reuse.

Better:

package mypack

func Add(a, b int) int {
    return a + b
}
package main

import (
    "fmt"
    "project-root/mypack"
)

func main() {
    fmt.Println(mypack.Add(2, 3))
}

Forgetting tests

Manual trial programs are useful, but they do not replace tests.

Use both when appropriate:

  • go test for repeatable verification
  • go run for interactive experimentation

Confusion about import paths

Your import path comes from your module path in go.mod, not just the folder name.

For example, if go.mod says:

module github.com/alice/project

then import paths look like:

import "github.com/alice/project/mypack"

not simply:

import "mypack"

Comparisons

ConceptPurposeTypical LocationRunnable?
package mypackReusable library codemypack/No
package mainExecutable programcmd/appname/ or project rootYes
*_test.goAutomated testsSame directory as packageOnly through go test

go run vs go build vs go install

CommandWhat it doesCommon use during development
go runBuilds and runs a program immediatelyQuickly try your app
go buildCompiles code without running itCheck that code compiles
go installBuilds and installs executable binariesInstall a command for repeated use
go testBuilds and runs testsVerify correctness

Package code vs test runner code

ApproachGood forLimitation
Small main programManual experimentationNot automated
Unit testsRepeatable verificationLess interactive
Benchmarks/examplesDocumentation and performance checksNarrower purpose
Take Quiz

Cheat Sheet

Core rules

  • One directory usually contains one package.
  • Reusable code goes in a non-main package.
  • Executable code goes in package main.
  • Keep main in a separate directory from library code.
  • Use *_test.go files for tests.

Common structure

project-root/
  go.mod
  mypack/
    file1.go
    file2.go
    mypack_test.go
  cmd/
    tryapp/
      main.go

Common commands

go run ./cmd/tryapp
go test ./...
go build ./...
go install ./cmd/tryapp

When to use what

  • Use go run to quickly execute your app.
  • Use go test to test package behavior.
  • Use go build to verify compilation.
  • Use go install when you want the executable installed.

Remember

  • Do not mix package main and package mypack in one directory.
  • Import paths come from go.mod module paths.
  • A small development runner is normal and useful.

FAQ

Do I need go install every time I change my Go package?

No. During development, use go run for executable programs and go test for package tests. go install is usually for installing a finished command.

Can I put package main and package mypack in the same folder?

Normally no. Go expects source files in a directory to belong to the same package.

How do I manually try a package while developing it?

Create a small separate program with package main, usually in a directory such as cmd/tryapp, and run it with go run.

Should I test with a manual runner or with go test?

Use both when useful. Manual runners help you experiment, while go test gives repeatable automated checks.

Where should Go test files go?

Usually in the same directory as the package they test, with names ending in _test.go.

What is the purpose of the cmd directory in Go projects?

It is a common convention for storing executable entry points. Each subdirectory usually contains one main package.

Can a Go project have multiple executable programs?

Yes. Many projects have several directories under cmd/, each building a different executable.

Take Quiz

Mini Project

Description

Build a small Go project with a reusable package and a separate runnable program. This demonstrates the standard development layout: package code in one directory, a main package in another directory, and tests alongside the package code.

Goal

Create a reusable greet package, run it from a small command-line program, and verify it with a unit test.

Requirements

  1. Create a package that exposes a function to build a greeting message.
  2. Create a separate main program that imports the package and prints a greeting.
  3. Add at least one automated test for the package.
  4. Run the program with go run instead of reinstalling after every change.
Take Quiz

Keep learning

Related questions

View more