Alex Edwards offers eleven pragmatic tips for structuring Go codebases, emphasizing flexibility over rigid conventions and effectiveness over perfection.
In the landscape of Go development, one of the most persistent challenges newcomers and experienced developers alike face is how to effectively organize their codebases. Alex Edwards, a seasoned Go developer and author, addresses this very concern in his recent article, offering eleven thoughtful tips that balance practical advice with philosophical wisdom about code organization.
The Core Philosophy: Flexibility Over Dogma
Edwards' central argument challenges the notion that there exists a single "right" way to structure Go projects. Instead, he advocates for a pragmatic approach where structure serves the project's specific needs rather than adhering to rigid conventions. This perspective acknowledges Go's minimalist philosophy while recognizing that real-world projects come in diverse shapes and sizes.
The author begins by dispelling the myth that Go has strong opinions about project structure. Unlike frameworks such as Ruby on Rails or Django that prescribe specific directory layouts, Go's community has relatively few widely-adopted conventions. As Edwards points out, successful Go projects like mkcert and Kubernetes exhibit vastly different structures, reflecting their distinct scales and purposes.
Practical Guidelines for Effective Organization
1. Structure as Evolution, Not Blueprint
One of the most refreshing aspects of Edwards' approach is his emphasis on organic growth over upfront perfection. He advises developers to start with a simple skeleton—often just two files, go.mod and main.go—and let the structure evolve naturally as the project develops. This approach acknowledges that our understanding of a project's requirements often deepens over time, and premature structuring can lead to unnecessary complexity.
The author recommends using one of Go's standard layouts as an initial skeleton:
- Basic layout for small projects, keeping everything in the root directory
- Supporting packages layout when some code needs to be broken into supporting packages within an
internaldirectory - Server project layout for larger projects with multiple main packages, separating executables in
cmdand packages ininternal
2. The Art of Package Creation
A particularly insightful section addresses the common tendency to over-engineer package boundaries. Edwards argues that many Go developers fall into the trap of creating too many small packages, adding unnecessary complexity and increasing the likelihood of import cycle problems.
He provides clear guidelines for when package creation is warranted:
- When code needs to be reused across different parts of a project or other codebases
- When isolation is required to enforce architectural boundaries
- When moving code to a standalone package reduces cognitive overhead
This advice aligns with Go's principle of simplicity, encouraging developers to resist premature abstraction and only introduce complexity when demonstrably needed.
3. Rethinking File Size and Cohesion
Perhaps counterintuitively, Edwards reassures developers that large files aren't inherently problematic in Go. He points to examples in the Go standard library itself, such as runtime/proc.go with 6,548 lines, and Kubernetes' validation files with over 8,000 lines. The key consideration isn't file size but cohesion—keeping related functionality together unless doing so creates practical difficulties.
Similarly, the principle of "keeping related things close" encourages developers to prioritize code locality over artificial separation. Constants, variables, custom types, and utility functions should reside near the code they support, and methods for a custom struct should typically be defined in the same file as the struct itself.
Warning Signs of Structural Problems
Edwards concludes by identifying practical warning signs that indicate a project structure may need refinement:
- Persistent import cycle problems
- Difficulty locating code, especially after time away or for new contributors
- Small changes frequently impacting multiple packages or files
- Overly "jumpy" control flow that's hard to follow during debugging
- Excessive duplication that's difficult to refactor
- Challenges in error management
- A sense of "fighting the language" through non-idiomatic patterns
These concrete indicators provide developers with actionable criteria for evaluating their project's structure, moving beyond subjective preferences toward objective assessment.
Implications for Go Development Practices
Edwards' recommendations carry several important implications for how developers approach Go projects:
Reduced Cognitive Load: By starting simple and evolving structure organically, developers can focus on solving immediate problems rather than anticipating future needs that may never materialize.
Improved Onboarding: Natural, code-driven structures tend to be more intuitive for new contributors than artificially imposed organizational schemes.
Enhanced Maintainability: Structures that reflect actual code relationships rather than abstract principles tend to be easier to modify and extend over time.
Language Alignment: Following Go's minimalist philosophy leads to codebases that feel more natural to work in and leverage the language's strengths effectively.
Counter-perspectives and Limitations
While Edwards' advice is generally sound, it's worth considering some counterpoints:
Team Coordination: In larger teams with multiple developers, some initial structural guidelines may be necessary to prevent inconsistent approaches across different parts of the codebase.
Long-term Evolution: Projects that grow significantly over time may eventually benefit from more deliberate architectural planning, even if they started organically.
Tooling Expectations: Some Go tools and IDE features may work better with certain structural patterns, potentially influencing the optimal organization for specific development workflows.
Organizational Standards: Enterprises with established technology standards may need to balance Go's flexibility with internal governance requirements.
Conclusion
Alex Edwards' article offers a refreshing perspective on Go project structure that emphasizes practicality over dogma. By acknowledging the diversity of successful Go projects and providing concrete guidelines for organic growth, he empowers developers to create structures that serve their specific needs rather than forcing their code into predefined molds.
The wisdom contained in these eleven tips—particularly the emphasis on starting simple, keeping related code together, and creating packages judiciously—reflects Go's overall philosophy of simplicity and pragmatism. For developers transitioning to Go from more opinionated languages or frameworks, this guidance provides valuable insight into how to think about code organization in a way that aligns with Go's design principles.
Ultimately, Edwards' approach encourages developers to view project structure not as a fixed blueprint but as an evolving reflection of the code's purpose and relationships—a perspective that serves Go projects well at every scale and complexity level.
For those interested in deeper exploration of Go development practices, Edwards' books Let's Go and Let's Go Further offer comprehensive guidance on building production-ready web applications and APIs in Go.

Comments
Please log in or register to join the discussion