ZetCode

Go flag Package: Parsing Command-Line Arguments

last modified May 1, 2026

In this article, we demonstrate how to parse command-line arguments in Go using the built-in flag package. Command-line flags are a standard way to configure program behavior at runtime, and Go's flag package provides a clean, type-safe API for defining and parsing them.

The flag package implements command-line flag parsing. While command-line arguments are available in the os.Args slice, the flag package offers more flexible and structured handling, including automatic help generation, type conversion, and error handling.

For more advanced use cases—such as subcommands, automatic completion, or complex configuration—third-party packages like Cobra or urfave/cli provide additional features. However, for most applications, the standard flag package is sufficient and recommended.

Flag Function Patterns

The flag package provides two patterns for defining flags of each supported type:

// Pattern 1: Returns a pointer to the flag value
func String(name string, value string, usage string) *string
func Int(name string, value int, usage string) *int
func Bool(name string, value bool, usage string) *bool

// Pattern 2: Accepts a pointer to store the flag value
func StringVar(p *string, name string, value string, usage string)
func IntVar(p *int, name string, value int, usage string)
func BoolVar(p *bool, name string, value bool, usage string)

The first pattern returns a pointer to a newly allocated variable containing the flag's value. You must dereference this pointer to access the value. The second pattern lets you provide your own variable, and the flag value is stored directly in it. Choose the pattern that best fits your code organization.

After defining flags, you must call flag.Parse() to process the command-line arguments. This function parses os.Args[1:] by default and populates the flag variables. Any non-flag arguments that follow the flags are accessible via flag.Args().

Flag Syntax

Go supports several syntaxes for specifying flags. For non-boolean flags:

-count=10
-count 10
--count=10
--count 10

Boolean flags support additional forms, including omitting the value to imply true:

-verbose
--verbose
-verbose=true
--verbose=false

Note: When using the short form without a value (e.g., -verbose), the flag is set to true. To set a boolean flag to false explicitly, use -verbose=false.

Simple Example: Integer Flag

The following example demonstrates a simple program that parses an integer flag to control the number of iterations.

simple.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    // Define an integer flag with name "n", default value 5, and usage text
    num := flag.Int("n", 5, "number of iterations")
    
    // Parse command-line flags
    flag.Parse()

    // Dereference the pointer to get the actual value
    n := *num
    
    for i := 0; i < n; i++ {
        fmt.Println("falcon")
    }
}

The program prints the word "falcon" n times, where n is provided via the -n flag. If the flag is omitted, the default value of 5 is used.

num := flag.Int("n", 5, "number of iterations")

This registers an integer flag. The function returns a pointer to an int, so we dereference it with *num to access the value.

flag.Parse()

This processes the command-line arguments and populates the flag variables. It must be called after all flags are defined and before accessing their values.

$ go run simple.go 
falcon
falcon
falcon
falcon
falcon

$ go run simple.go -n 3
falcon
falcon
falcon

Using Multiple Flags: String and Int

This example shows how to define and use both string and integer flags. The flags are defined in an init() function, which is executed before main() and is a common place to initialize application state.

string_int.go
package main

import (
    "flag"
    "fmt"
)

var (
    env  *string
    port *int
)

func init() {
    // Define flags in init() for early registration
    env = flag.String("env", "development", "current environment (development, staging, production)")
    port = flag.Int("port", 3000, "port number to listen on")
}

func main() {
    flag.Parse()

    fmt.Printf("Environment: %s\n", *env)
    fmt.Printf("Port: %d\n", *port)
}

In this example, we define two flags: -env for the environment and -port for the port number. Both flags have default values, and the program prints their values after parsing the command-line arguments.

$ go run string_int.go 
Environment: development
Port: 3000

$ go run string_int.go -env production -port 8080
Environment: production
Port: 8080

StringVar Example

The StringVar function allows you to bind a flag directly to an existing variable, avoiding pointer dereferencing.

stringvar.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    var name string
    // Bind the flag directly to the 'name' variable
    flag.StringVar(&name, "name", "guest", "your name")
    flag.Parse()

    fmt.Printf("Hello, %s!\n", name)
}

In this example, we define a string flag -name that defaults to "guest". The flag value is stored directly in the name variable,so we can use it without dereferencing.

$ go run stringvar.go 
Hello, guest!

$ go run stringvar.go -name Maria
Hello, Maria!

Full List of Flag Types

The flag package provides constructor and Var variants for eight built-in types. The constructor returns a pointer to a newly allocated variable; the Var version binds the flag to an existing variable you supply.

Type Constructor Var version
string flag.String flag.StringVar
int flag.Int flag.IntVar
bool flag.Bool flag.BoolVar
float64 flag.Float64 flag.Float64Var
int64 flag.Int64 flag.Int64Var
uint flag.Uint flag.UintVar
uint64 flag.Uint64 flag.Uint64Var
time.Duration flag.Duration flag.DurationVar

For any type not listed here — enums, slices, validated strings, and so on — implement the flag.Value interface and register it with flag.Var, as shown in the custom flag types section above.

flag_types.go
package main

import (
    "flag"
    "fmt"
    "time"
)

func main() {
    var (
        rate     float64
        maxBytes int64
        workers  uint
        timeout  time.Duration
    )

    flag.Float64Var(&rate, "rate", 0.5, "sampling rate between 0.0 and 1.0")
    flag.Int64Var(&maxBytes, "max-bytes", 1<<20, "maximum request body size in bytes")
    flag.UintVar(&workers, "workers", 4, "number of worker goroutines")
    flag.DurationVar(&timeout, "timeout", 10*time.Second, "request timeout")
    flag.Parse()

    fmt.Printf("Rate:      %.2f\n", rate)
    fmt.Printf("Max bytes: %d\n", maxBytes)
    fmt.Printf("Workers:   %d\n", workers)
    fmt.Printf("Timeout:   %v\n", timeout)
}

This example demonstrates several different flag types in a single program. Each flag has a default value and a usage string that describes its purpose.

Displaying Help: flag.PrintDefaults

The flag.PrintDefaults() function prints the usage information for all defined flags, including their names, default values, and usage strings. This is useful for implementing custom help messages or validating required flags.

defaults.go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    var name string
    flag.StringVar(&name, "name", "", "your name (required)")
    flag.Parse()

    // Check if required flag was provided
    if name == "" {
        fmt.Println("Error: -name flag is required")
        fmt.Println("Usage: defaults.go -name <your_name>")
        fmt.Println("\nAvailable flags:")
        flag.PrintDefaults()
        os.Exit(1)
    }

    fmt.Printf("Hello, %s!\n", name)
}

In this example, we define a required string flag -name. If the user runs the program without providing this flag, we print an error message,a usage hint, and then call flag.PrintDefaults() to show all available flags and their descriptions.

$ go run defaults.go 
Error: -name flag is required
Usage: defaults.go -name <your_name>

Available flags:
  -name string
        your name (required)
exit status 1

$ go run defaults.go -name Alex
Hello, Alex!

Boolean Flags: BoolVar Example

Boolean flags are commonly used for enabling or disabling features. This example demonstrates a program that optionally converts output to uppercase based on a boolean flag.

boolean.go
package main

import (
    "flag"
    "fmt"
    "strings"
)

func main() {
    var name string
    var upper bool

    flag.StringVar(&name, "name", "guest", "your name")
    flag.BoolVar(&upper, "u", false, "display output in uppercase")
    flag.Parse()

    msg := fmt.Sprintf("Hello, %s!", name)
    
    if upper {
        msg = strings.ToUpper(msg)
    }
    
    fmt.Println(msg)
}

In this example, we define two flags:

When the user runs the program with -u, the output is converted to uppercase. If -u is omitted, the output remains in normal case.

$ go run boolean.go -name Peter
Hello, Peter!

$ go run boolean.go -name Peter -u
HELLO, PETER!

$ go run boolean.go -u -name Peter
HELLO, PETER!

Note that boolean flags can be specified in any order relative to other flags.

Handling Non‑Flag Arguments with flag.Args()

After all defined flags are processed by flag.Parse(), the Go flag package leaves any remaining command‑line values untouched. These leftover values are called positional or non‑flag arguments. They are useful when your program accepts a variable number of inputs, such as filenames, search terms, or commands.

You can retrieve them using:

The following example implements a tiny text‑processing tool. It accepts an optional -u flag to convert text to uppercase and then processes any number of words supplied after the flags.

nonflags.go
package main

import (
    "flag"
    "fmt"
    "os"
    "strings"
)

func main() {
    var uppercase bool
    flag.BoolVar(&uppercase, "u", false, "convert words to uppercase")
    flag.Parse()

    // All remaining arguments after flags are positional words.
    words := flag.Args()

    if len(words) == 0 {
        fmt.Println("Usage: nonflags.go [-u] <word> [<word> ...]")
        flag.PrintDefaults()
        os.Exit(1)
    }

    for _, word := range words {
        if uppercase {
            fmt.Println(strings.ToUpper(word))
        } else {
            fmt.Println(word)
        }
    }
}

This program demonstrates how flag.Args() cleanly separates flag‑controlled behavior from free‑form input. The user may pass any number of words, and the program processes them according to the -u flag.

$ go run nonflags.go sky blue falcon
sky
blue
falcon

$ go run nonflags.go -u sky blue falcon
SKY
BLUE
FALCON

Accessing Specific Positional Arguments

While flag.Args() returns all positional arguments as a slice, sometimes your program expects a fixed number of them in a specific order. In such cases, flag.Arg(i) provides direct indexed access to each argument.

The example below implements a simple backup command. It accepts an optional -dry-run flag and then requires exactly two positional arguments: a source directory and a target directory.

backup.go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    var dryRun bool
    flag.BoolVar(&dryRun, "dry-run", false, "show what would be done, but do not copy")
    flag.Parse()

    // Expect exactly two positional arguments.
    if flag.NArg() != 2 {
        fmt.Fprintf(os.Stderr,
            "Usage: backup [-dry-run] <source-dir> <target-dir>\n")
        os.Exit(1)
    }

    sourceDir := flag.Arg(0) // first positional argument
    targetDir := flag.Arg(1) // second positional argument

    if dryRun {
        fmt.Printf("[DRY RUN] Would back up '%s' to '%s'\n", sourceDir, targetDir)
    } else {
        fmt.Printf("Backing up '%s' to '%s'...\n", sourceDir, targetDir)
        // Actual backup logic would go here.
    }
}

This pattern mirrors many classic Unix tools that take a fixed set of positional parameters, such as cp <src> <dst> or mv <old> <new>. Using flag.Arg(i) makes it easy to retrieve each argument directly without manually indexing into flag.Args().

$ go run backup.go ./data ./backup
Backing up './data' to './backup'...

$ go run backup.go -dry-run ./data ./backup
[DRY RUN] Would back up './data' to './backup'

Creating Custom Flag Sets with flag.NewFlagSet

By default, flags are registered with the global flag.CommandLine flag set. However, you can create custom flag sets using flag.NewFlagSet. This is useful for:

custom_flagset.go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    // Create a new flag set with a custom name and error handling behavior
    fs := flag.NewFlagSet("query", flag.ExitOnError)
    
    // Define flags specific to this flag set
    db := fs.String("db", "localhost", "database host")
    verbose := fs.Bool("v", false, "enable verbose output")
    
    // Parse a custom argument slice (not os.Args)
    args := []string{"-db", "production.server.com", "-v", "extra", "arguments"}
    fs.Parse(args)
    
    // Access flag values from the custom flag set
    fmt.Printf("Database: %s\n", *db)
    fmt.Printf("Verbose: %t\n", *verbose)
    
    // Access non-flag arguments parsed by this flag set
    remaining := fs.Args()
    fmt.Printf("Remaining arguments: %v\n", remaining)
    
    // Display help for this specific flag set
    if len(os.Args) > 1 && os.Args[1] == "-h" {
        fs.PrintDefaults()
    }
}
$ go run custom_flagset.go 
Database: production.server.com
Verbose: true
Remaining arguments: [extra arguments]

The second argument to NewFlagSet specifies the error handling behavior:

Iterating Over Defined Flags

You can iterate over all defined flags using flag.Visit (for flags that were explicitly set) or flag.VisitAll (for all defined flags). This is useful for logging, debugging, or dynamic configuration.

visit_flags.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    var (
        name  = flag.String("name", "default", "user name")
        count = flag.Int("count", 10, "number of items")
        debug = flag.Bool("debug", false, "enable debug mode")
    )

    flag.Parse()

    fmt.Printf("Name: %s, Count: %d, Debug: %t\n", *name, *count, *debug)

    fmt.Println("\nFlags explicitly set by user:")
    flag.Visit(func(f *flag.Flag) {
        fmt.Printf("  -%s = %v\n", f.Name, f.Value)
    })

    fmt.Println("\nAll defined flags:")
    flag.VisitAll(func(f *flag.Flag) {
        fmt.Printf("  -%s: %s (default: %v, current: %v)\n",
            f.Name, f.Usage, f.DefValue, f.Value)
    })
}

In this example, we define three flags and then iterate over them to display information about each flag. The Visit function shows only flags that were explicitly set by the user, while VisitAll shows all defined flags.

Duration Flags

The flag package has built-in support for time.Duration via flag.Duration and flag.DurationVar. Duration strings use the standard Go format: 300ms, 1.5s, 2m, 1h30m. This is especially useful for timeouts, intervals, and retry delays.

duration.go
package main

import (
    "flag"
    "fmt"
    "time"
)

func main() {
    var timeout time.Duration
    var interval time.Duration

    flag.DurationVar(&timeout, "timeout", 30*time.Second, "request timeout (e.g. 500ms, 2s, 1m)")
    flag.DurationVar(&interval, "interval", 5*time.Second, "polling interval")
    flag.Parse()

    fmt.Printf("Timeout:  %v\n", timeout)
    fmt.Printf("Interval: %v\n", interval)
    fmt.Printf("Ratio:    %.1fx\n", float64(timeout)/float64(interval))
}
flag.DurationVar(&timeout, "timeout", 30*time.Second, "request timeout (e.g. 500ms, 2s, 1m)")

The default value is a time.Duration constant. The usage string should document accepted formats so users know what inputs are valid.

$ go run duration.go
Timeout:  30s
Interval: 5s
Ratio:    6.0x

$ go run duration.go -timeout 2m30s -interval 500ms
Timeout:  2m30s
Interval: 500ms
Ratio:    300.0x

$ go run duration.go -timeout invalid
invalid value "invalid" for flag -timeout: time: invalid duration "invalid"

Providing an invalid duration string triggers an error and exits the program.

Custom Flag Types: Implementing flag.Value

For flags that don't fit any of the built-in types, you can define your own by implementing the flag.Value interface. This requires two methods:

type Value interface {
    String() string   // returns the default or current value as a string
    Set(string) error // parses the string and stores the value; returns an error if invalid
}

A common use case is accepting a flag multiple times to build a list. The example below defines a StringSlice type that collects repeated -tag flags into a slice.

custom_type.go
package main

import (
    "flag"
    "fmt"
    "strings"
)

// StringSlice implements flag.Value to collect repeated flag values.
type StringSlice []string

func (s *StringSlice) String() string {
    return strings.Join(*s, ", ")
}

func (s *StringSlice) Set(val string) error {
    *s = append(*s, val)
    return nil
}

func main() {
    var tags StringSlice
    flag.Var(&tags, "tag", "build tag (may be specified multiple times)")

    var output string
    flag.StringVar(&output, "output", "binary", "output file name")
    flag.Parse()

    fmt.Printf("Output: %s\n", output)

    if len(tags) == 0 {
        fmt.Println("Tags:   (none)")
    } else {
        fmt.Printf("Tags:   %s\n", tags.String())
    }
}

flag.Var registers a flag backed by a custom flag.Value implementation. The pointer receiver on Set is essential — it allows the method to mutate the slice.

$ go run custom_type.go -output myapp -tag linux -tag amd64 -tag production
Output: myapp
Tags:   linux, amd64, production

$ go run custom_type.go
Output: binary
Tags:   (none)

This pattern generalises to any custom type: validated IP addresses, log level enums, comma-separated lists, or structured values parsed from a string.

Subcommands with flag.NewFlagSet

Many CLI tools use a subcommand pattern where the first positional argument selects a command, and each command has its own independent set of flags — similar to git commit, git push, and so on. flag.NewFlagSet is the standard way to implement this in Go.

subcommands.go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Fprintln(os.Stderr, "Usage: subcommands <command> [options]")
        fmt.Fprintln(os.Stderr, "Commands: serve, migrate")
        os.Exit(1)
    }

    switch os.Args[1] {
    case "serve":
        runServe(os.Args[2:])
    case "migrate":
        runMigrate(os.Args[2:])
    default:
        fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
        os.Exit(1)
    }
}

func runServe(args []string) {
    fs := flag.NewFlagSet("serve", flag.ExitOnError)
    host := fs.String("host", "0.0.0.0", "host to bind")
    port := fs.Int("port", 8080, "port to listen on")
    fs.Parse(args)

    fmt.Printf("[serve] listening on %s:%d\n", *host, *port)
}

func runMigrate(args []string) {
    fs := flag.NewFlagSet("migrate", flag.ExitOnError)
    db := fs.String("db", "postgres://localhost/app", "database DSN")
    dry := fs.Bool("dry-run", false, "print SQL without executing")
    fs.Parse(args)

    if *dry {
        fmt.Printf("[migrate] dry run against %s\n", *db)
    } else {
        fmt.Printf("[migrate] applying migrations to %s\n", *db)
    }
}

Each subcommand creates its own FlagSet, which is parsed against only the arguments that follow the subcommand name. This keeps flag namespaces isolated — a -port flag in serve has no relation to anything in migrate.

$ go run subcommands.go
Usage: subcommands <command> [options]
Commands: serve, migrate

$ go run subcommands.go serve -port 9000
[serve] listening on 0.0.0.0:9000

$ go run subcommands.go migrate -db postgres://prod/mydb -dry-run
[migrate] dry run against postgres://prod/mydb

$ go run subcommands.go unknown
unknown command: unknown

For applications with many subcommands, consider extracting each into its own file or package and registering them in a map rather than a switch statement. Third-party libraries like Cobra build on this same pattern and handle registration, help generation, and shell completion automatically.

Best Practices and Error Handling

Common Pitfalls

Reference

Official Go flag package documentation

In this article, we explored how to parse command-line arguments in Go using the flag package. We covered basic flag definition, parsing, help generation, boolean flags, non-flag arguments, custom flag sets with NewFlagSet, and advanced iteration techniques. With these tools, you can build robust, user-friendly command-line interfaces in Go.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Go tutorials.