Please purchase the course to watch this video.

Full Course
Command-line interface (CLI) applications often require both global and subcommand-specific flags to provide flexible user input and functionality. Using the Go standard library's flag
and FlagSet
types, developers can cleanly separate global flags, which apply to all subcommands (such as a precision
flag for setting decimal output), from flags relevant only to particular commands (such as an absolute
flag for the subtract
subcommand, returning only positive results regardless of argument order). This structure allows for intuitive interfaces similar to popular tools like Git, where each subcommand can offer unique flag options. Implementation involves defining dedicated FlagSet
instances for subcommands, providing granular argument parsing and targeted help messages without polluting the global flag space. Key takeaways include the importance of thoughtful flag scope, the limitations of flag parsing order with the standard library, and the advantages of frameworks like Cobra for building scalable, user-friendly CLI tools. Upcoming exercises extend these concepts by introducing mutually exclusive global flags for output formatting and a subcommand-specific flag for the division remainder, reinforcing best practices in CLI design.
A common pattern when it comes to using subcommands in CLI applications is to couple them with command line flags. We've already taken a look at how to add flags to an application globally by making use of the flag package.
Quick Review: Global Flags
As a quick reminder, I've gone ahead and implemented a new flag into the calculation application called precision. This is an integer flag that will allow us to specify the number of decimal places to print out.
Testing the Precision Flag
Let's see how it works:
go build
./calc add 1.15 2.2 # Output: 3.35
./calc -precision 1 add 1.15 2.2 # Output: 3.4
./calc -precision 0 add 1.15 2.2 # Output: 3
This precision flag was achieved similar to how we've added flags in the past:
// Global flag definition
var precision = flag.Int("precision", -1, "Number of decimal places to display")
func printNumber(num float64) {
if *precision >= 0 {
result := strconv.FormatFloat(num, 'f', *precision, 64)
fmt.Println(result)
} else {
fmt.Println(num)
}
}
The major thing of note here is that I'm using a new function called printNumber
which uses the FormatFloat
function of the strconv
package, passing in:
- The number
- The
'f'
format (float, not exponent notation) - The precision value
- 64-bit size
Two Types of Flags in CLI Applications
When it comes to CLI applications, there are typically two use cases when adding flags, especially when dealing with subcommands:
1. Global Flags
In our case, the precision flag makes sense to apply to all of our commands because every command we're using currently works with floating point numbers. Having the precision flag apply to each subcommand makes sense.
2. Subcommand-Specific Flags
However, there are times when building a CLI application that you may only want to define a flag for a single subcommand, rather than all of them.
Example: Git Commands
Let's look at how git handles this:
git commit -help # Shows flags like -m for message
git status -help # Different set of flags, no -m flag
You can see there are a number of flags that apply to the commit
subcommand itself, such as the -m
flag for specifying a message. However, the status
command has its own set of flags applicable to printing out the current status.
Our Use Case: The Absolute Flag
When it comes to our own application, whilst the precision flag is good for all commands, there's going to be some different options we want for specific subcommands. Specifically, let's focus on the subtract command, which has different behavior compared to add.
The Problem with Subtraction Order
./calc add 5 10 # Output: 15
./calc add 10 5 # Output: 15 (same result)
./calc subtract 5 10 # Output: -5
./calc subtract 10 5 # Output: 5 (different result!)
This is because in mathematics, subtraction is a non-commutative operation. This means that the order of operations when it comes to the subtract command matters.
Whilst this is expected behavior in mathematics, in some cases when it comes to a CLI application, this may be undesirable, such as when you want to get the delta or difference between two values. In this case, the difference between 5 and 10 should be 5 regardless of order.
Solution: The Absolute Flag
We're going to extend our subtract function to make use of a new flag: the absolute flag, which will return the positive value of the number regardless of the order of operations we pass in.
Why Not Add a Global Flag?
We could just add a flag using the flag package underneath where we're defining the precision
variable. However, the problem with doing this is that it would add this flag to all of our commands.
The absolute flag doesn't make sense for the add command. For instance, if we're adding -10 and -5, we expect this to be -15, never 15 in an add context. So it doesn't make sense to add absolute to every command in our application.
Introducing FlagSets
The Go standard library's flag package provides a type called a FlagSet, which allows us to:
- Define a set of flags for a specific command
- Parse certain arguments for that flag only
- Only parse arguments that apply to a specific subcommand
Implementation
Step 1: Define the FlagSet
func subtractCmd(args []string) {
// Create a new FlagSet for this subcommand
flagSet := flag.NewFlagSet("subtract", flag.ExitOnError)
// Define the absolute flag
var isAbsolute bool
flagSet.BoolVar(&isAbsolute, "absolute", false, "determines whether or not to print out the absolute value")
// Parse the arguments for this specific flagset
flagSet.Parse(args)
// Get non-flag arguments after parsing
args = flagSet.Args()
// ... rest of the function
}
Step 2: Understanding FlagSet Parameters
The flag.NewFlagSet()
function takes two parameters:
- Name: Used for usage messages (we'll set this to "subtract")
- Error Handling: How to handle parsing errors
flag.ExitOnError
- Exit the program on errorflag.PanicOnError
- Panic on errorflag.ContinueOnError
- Return error for manual handling
Step 3: Complete Implementation
func subtractCmd(args []string) {
// Create flagset for subtract command
flagSet := flag.NewFlagSet("subtract", flag.ExitOnError)
// Define absolute flag
var isAbsolute bool
flagSet.BoolVar(&isAbsolute, "absolute", false, "determines whether or not to print out the absolute value")
// Parse the arguments
flagSet.Parse(args)
// Get remaining non-flag arguments
args = flagSet.Args()
// Validate we have exactly 2 arguments
if len(args) != 2 {
log.Fatal("incorrect arguments for subtract command")
}
// Parse numbers
num1, err := strconv.ParseFloat(args[0], 64)
if err != nil {
log.Fatal("invalid number")
}
num2, err := strconv.ParseFloat(args[1], 64)
if err != nil {
log.Fatal("invalid number")
}
// Perform subtraction
result := num1 - num2
// Apply absolute value if flag is set
if isAbsolute {
result = math.Abs(result)
}
printNumber(result)
}
Testing the Implementation
Let's test our new absolute flag:
go build
# Standard behavior
./calc subtract 5 10 # Output: -5
# With absolute flag
./calc subtract -absolute 5 10 # Output: 5
./calc subtract -absolute 10 5 # Output: 5 (same result!)
Important: Flag Position Matters
⚠️ Note: Flags must come before the command line arguments when using the standard library:
# ✅ Correct
./calc subtract -absolute 5 10
# ❌ Incorrect - won't work
./calc subtract 5 10 -absolute
This limitation is solved when using the Cobra package, which we'll look at in the next module.
Verifying Flag Scope
The absolute flag only works with the subtract command:
# ✅ Works
./calc subtract -absolute 5 10
# ❌ Doesn't work - flag not available for add
./calc add -absolute 5 10
Error Handling and Help Messages
Invalid Flag Values
If we try to assign an invalid value to a boolean flag:
./calc subtract -absolute=2 5 10
Output:
invalid boolean value 2 for absolute: parse error
Built-in Help
The flag package automatically provides help functionality:
# Global help
./calc -help
# Output: Usage of calc: -precision int ...
# Subcommand help
./calc subtract -help
# Output: Usage of subtract: -absolute ...
Comparison with Cobra
While our implementation works, it's not as powerful as professional CLI tools. For example, if we look at a Cobra-based application:
dreams-of-code --help
# Shows:
# - List of all available commands
# - Global flags
# - Subcommand descriptions
dreams-of-code video --help
# Shows:
# - Available subcommands under 'video'
# - Subcommand-specific flags
# - Global flags
Summary
We've successfully added subcommand-specific flags using FlagSets, which allow us to:
✅ Define flags that only apply to specific subcommands
✅ Maintain clean separation between global and local flags
✅ Parse arguments specific to each subcommand
✅ Get automatic help generation for each subcommand
This was achieved by making use of the FlagSet type, which allows you to work with flags very similar to using the base flag package, but provides a constrained set of flags as a type rather than impacting the global state.
What's Next?
In the next lesson, we're going to build on top of everything we've learned about subcommands and flags and create our own simple subcommand abstraction, similar to how Cobra works, but a very lightweight version.
The reason we're doing this is to understand how building an abstraction works and some of the challenges involved, so we can better appreciate Cobra in the next module.
📝 Homework Assignment
Before moving on to the next lesson, complete these tasks:
Part 1: Global Flags
Add three global flags to the application that should affect every subcommand (add, subtract, divide, multiply):
1. Round Flag
./calc -round add 1.2 1.3 # Output: 3 (rounds to nearest integer)
./calc -round add 1.2 1.2 # Output: 2 (rounds down)
2. Floor Flag
./calc -floor add 1.9 1.1 # Output: 2 (always rounds down)
3. Ceil Flag
./calc -ceil add 1.1 1.1 # Output: 3 (always rounds up)
Implementation Notes:
- Use the
math.Round()
,math.Floor()
, andmath.Ceil()
functions - Make these flags mutually exclusive - only zero or one can be used at a time
- Add validation to ensure conflicting flags aren't used together
Part 2: Subcommand-Specific Flag
Add a remainder flag to the divide command:
# Normal division
./calc divide 10 2 # Output: 5
# With remainder flag
./calc divide -remainder 5 3 # Output: 2 (integer remainder)
Instead of producing the division result, the remainder flag should produce the integer remainder from the division using the modulo operation.
Implementation Hints
// For mutual exclusion check:
flagCount := 0
if *round { flagCount++ }
if *floor { flagCount++ }
if *ceil { flagCount++ }
if flagCount > 1 {
log.Fatal("Cannot use multiple rounding flags together")
}
// For remainder operation:
if isRemainder {
result = math.Mod(num1, num2)
}
Once you've implemented these flags, the homework will be complete and you can move on to the next lesson!
Implement Global Round Flag
Add a global -round
flag that rounds the result to the nearest integer using banker's rounding. This flag should work with all commands (add, subtract, multiply, divide).
Requirements
- Use
math.Round()
function - Apply to all mathematical operations
- Can be combined with existing
-precision
flag
Examples
./calc -round add 1.2 1.3 # Output: 3 (rounds 2.5 to nearest integer)
./calc -round add 1.2 1.2 # Output: 2 (rounds 2.4 down to 2)
./calc -round subtract 5.7 2.2 # Output: 4 (rounds 3.5 to 4)
Implementation Hint
var round = flag.Bool("round", false, "Round result to nearest integer")
// In printNumber function:
if *round {
num = math.Round(num)
}
Implement Global Floor Flag
Add a global -floor
flag that always rounds down to the largest integer less than or equal to the result. This flag should work with all commands.
Requirements
- Use
math.Floor()
function - Apply to all mathematical operations
- Should round down regardless of decimal value
Examples
./calc -floor add 1.9 1.1 # Output: 2 (floors 3.0 to 2)
./calc -floor add 1.1 1.8 # Output: 2 (floors 2.9 to 2)
./calc -floor multiply 2.9 1.1 # Output: 3 (floors 3.19 to 3)
Implementation Hint
var floor = flag.Bool("floor", false, "Round result down to nearest integer")
// In printNumber function:
if *floor {
num = math.Floor(num)
}
Implement Global Ceil Flag
Add a global -ceil
flag that always rounds up to the smallest integer greater than or equal to the result. This flag should work with all commands.
Requirements
- Use
math.Ceil()
function - Apply to all mathematical operations
- Should round up regardless of decimal value
Examples
./calc -ceil add 1.1 1.1 # Output: 3 (ceils 2.2 to 3)
./calc -ceil divide 5 2 # Output: 3 (ceils 2.5 to 3)
./calc -ceil subtract 3.1 1.1 # Output: 2 (ceils 2.0 to 2)
Implementation Hint
var ceil = flag.Bool("ceil", false, "Round result up to nearest integer")
// In printNumber function:
if *ceil {
num = math.Ceil(num)
}
Implement Mutual Exclusion for Rounding Flags
Ensure that only one rounding flag can be used at a time. If multiple rounding flags are provided, the program should exit with an error message.
Requirements
- Check all three rounding flags (round, floor, ceil)
- Display clear error message when multiple flags are used
- Allow zero or one flag to be used
- Call validation early in main function
Examples
./calc -round -floor add 1.1 1.1
# Should output: "Cannot use multiple rounding flags together"
./calc -ceil -round subtract 2.5 1.2
# Should output: "Cannot use multiple rounding flags together"
Implementation Hint
func validateGlobalFlags() {
flagCount := 0
if *round { flagCount++ }
if *floor { flagCount++ }
if *ceil { flagCount++ }
if flagCount > 1 {
log.Fatal("Cannot use multiple rounding flags together")
}
}
// Call this in main() after flag.Parse()
Implement Remainder Flag for Divide Command
Add a -remainder
flag specifically for the divide command that calculates the integer remainder (modulo operation) instead of performing division.
Requirements
- Use FlagSet for the divide command
- Use
math.Mod()
function or%
operator - Only available for divide command
- Normal division should still work when flag is not used
Examples
# Normal division
./calc divide 10 2 # Output: 5
# With remainder flag
./calc divide -remainder 5 3 # Output: 2 (5 % 3 = 2)
./calc divide -remainder 10 4 # Output: 2 (10 % 4 = 2)
./calc divide -remainder 7 2 # Output: 1 (7 % 2 = 1)
Implementation Hint
func divideCmd(args []stri
Implement Global Round Flag