5-minute quickstart.
Install fp-go, write your first program, and learn the core composition patterns — Pipe, Map, Chain, GetOrElse — in one sitting.
Prerequisites
- Go 1.24+ for v2 (recommended)
- Go 1.18+ for v1 (legacy)
- Basic understanding of Go
Install fp-go.
Choose your version. v2 is recommended for any new project.
- v2 (Recommended)
- v1 (Legacy)
# Initialize your Go module (if not already done) go mod init myapp # Install fp-go v2 go get github.com/IBM/fp-go/v2
- Latest features (Result, Effect, idiomatic packages)
- Better type inference
- Actively maintained
- Requires Go 1.24+
# Initialize your Go module (if not already done) go mod init myapp # Install fp-go v1 go get github.com/IBM/fp-go
- Stuck on Go 1.18–1.23
- Existing v1 codebase
- Need Writer monad (v1 only)
Your first program.
A safe divider. Compare the three approaches side-by-side.
- Without fp-go
- With v2
- With v1
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
// Manual error handling
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
doubled := result * 2
fmt.Println("Result:", doubled) // Output: Result: 10
// What if we want to chain more operations?
// More if err != nil checks...
}Repetitive error checking.
Hard to compose operations.
Error handling mixed with business logic.
Automatic error propagation.
Composable pipelines.
Business logic stays clear.
package main
import (
"errors"
"fmt"
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/result"
)
// Pure function that returns Result instead of (value, error)
func divide(a, b int) result.Result[int] {
if b == 0 {
return result.Err[int](errors.New("division by zero"))
}
return result.Ok(a / b)
}
func main() {
// Compose operations with automatic error handling
finalResult := function.Pipe2(
divide(10, 2),
result.Map(func(x int) int { return x * 2 }),
result.GetOrElse(func() int { return 0 }),
)
fmt.Println("Result:", finalResult) // Output: Result: 10
// Error case is handled automatically
errorResult := function.Pipe2(
divide(10, 0), // Returns Err
result.Map(func(x int) int { return x * 2 }), // Skipped!
result.GetOrElse(func() int { return 0 }), // Returns default
)
fmt.Println("Error result:", errorResult) // Output: Error result: 0
}No repetitive error checking. Easy composition. Errors propagate automatically. Business logic stays prominent.
package main
import (
"errors"
"fmt"
"github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/function"
)
// Pure function that returns Either instead of (value, error)
func divide(a, b int) either.Either[error, int] {
if b == 0 {
return either.Left[int](errors.New("division by zero"))
}
return either.Right[error](a / b)
}
func main() {
// Compose operations with automatic error handling
finalResult := function.Pipe2(
divide(10, 2),
either.Map(func(x int) int { return x * 2 }),
either.GetOrElse(func() int { return 0 }),
)
fmt.Println("Result:", finalResult) // Output: Result: 10
// Error case is handled automatically
errorResult := function.Pipe2(
divide(10, 0), // Returns Left
either.Map(func(x int) int { return x * 2 }), // Skipped!
either.GetOrElse(func() int { return 0 }), // Returns default
)
fmt.Println("Error result:", errorResult) // Output: Error result: 0
}v1 uses Either[error, A] instead of v2's Result[A].
Run it
go run main.go
Result: 10 Error result: 0
Understanding the pattern.
Four building blocks: Result/Either return types, Pipe to compose, Map to transform, GetOrElse to extract.
1. Pure functions return results
- v2
- v1
// Instead of this: func divide(a, b int) (int, error) // We write this: func divide(a, b int) result.Result[int]
// Instead of this: func divide(a, b int) (int, error) // We write this: func divide(a, b int) either.Either[error, int]
2. Compose with Pipe
result := function.Pipe2( operation1(), // Returns Result[A] operation2, // A -> Result[B] operation3, // B -> C )
Pipe feeds the output of one function into the next, automatically handling errors.
3. Transform with Map
- v2
- v1
result.Map(func(x int) int {
return x * 2
})
either.Map(func(x int) int {
return x * 2
})Map transforms the value inside a Result/Either — but only if it's successful. Errors pass through unchanged.
4. Extract with GetOrElse
- v2
- v1
result.GetOrElse(func() int {
return 0
})
either.GetOrElse(func() int {
return 0
})GetOrElse extracts the value or provides a default if there was an error.
A more complex example.
Chain multiple operations into one pipeline. Failure in any step short-circuits the rest.
- v2
- v1
package main
import (
"errors"
"fmt"
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/result"
)
func divide(a, b int) result.Result[int] {
if b == 0 {
return result.Err[int](errors.New("division by zero"))
}
return result.Ok(a / b)
}
func sqrt(n int) result.Result[int] {
if n < 0 {
return result.Err[int](errors.New("cannot sqrt negative number"))
}
// Simplified integer sqrt
result := 0
for result*result <= n {
result++
}
return result.Ok(result - 1)
}
func calculate(a, b, c int) result.Result[string] {
return function.Pipe3(
divide(a, b), // 100 / 4 = 25
result.Chain(func(x int) result.Result[int] { // Chain another Result operation
return divide(x, c) // 25 / 5 = 5
}),
result.Chain(sqrt), // sqrt(5) ≈ 2
result.Map(func(x int) string { // Convert to string
return fmt.Sprintf("Final result: %d", x)
}),
)
}
func main() {
// Success case
success := calculate(100, 4, 5)
fmt.Println(result.GetOrElse(func() string {
return "Error occurred"
})(success))
// Output: Final result: 2
// Error case (division by zero)
failure := calculate(100, 0, 5)
fmt.Println(result.GetOrElse(func() string {
return "Error occurred"
})(failure))
// Output: Error occurred
}
package main
import (
"errors"
"fmt"
"github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/function"
)
func divide(a, b int) either.Either[error, int] {
if b == 0 {
return either.Left[int](errors.New("division by zero"))
}
return either.Right[error](a / b)
}
func sqrt(n int) either.Either[error, int] {
if n < 0 {
return either.Left[int](errors.New("cannot sqrt negative number"))
}
// Simplified integer sqrt
result := 0
for result*result <= n {
result++
}
return either.Right[error](result - 1)
}
func calculate(a, b, c int) either.Either[error, string] {
return function.Pipe3(
divide(a, b), // 100 / 4 = 25
either.Chain(func(x int) either.Either[error, int] { // Chain another Either operation
return divide(x, c) // 25 / 5 = 5
}),
either.Chain(sqrt), // sqrt(5) ≈ 2
either.Map(func(x int) string { // Convert to string
return fmt.Sprintf("Final result: %d", x)
}),
)
}
func main() {
// Success case
success := calculate(100, 4, 5)
fmt.Println(either.GetOrElse(func() string {
return "Error occurred"
})(success))
// Output: Final result: 2
// Error case (division by zero)
failure := calculate(100, 0, 5)
fmt.Println(either.GetOrElse(func() string {
return "Error occurred"
})(failure))
// Output: Error occurred
}| Concept | When to use | Notes |
|---|---|---|
Chain | a.k.a. FlatMapUse when your transformation returns another Result/Either. | |
Map | Functor mapUse when your transformation returns a plain value. | |
Pipe | function.PipeNCompose multiple operations into a pipeline. | |
Auto error | Short-circuitIf any step fails, subsequent steps are skipped. |
What's next?
Pick a thread to keep learning.
| Topic | Page | Why |
|---|---|---|
Why | Why fp-go?Understand the benefits and when to reach for it. | |
Pure functions | Pure functionsThe foundation of functional programming. | |
Monads | MonadsThe pattern behind Result, Either, IO, etc. | |
Result | ResultRecommended type for v2 error handling. | |
Either | EitherGeneric sum type. | |
Option | OptionHandle optional values safely. | |
IO | IOManage side effects. | |
Recipes · errors | Error handlingProduction-style patterns. | |
Recipes · HTTP | HTTP requestsEffectful pipelines. |
Common questions
Good fit: complex error handling, data transformation pipelines, composable business logic, testing-heavy codebases.
Not ideal: simple CRUD, performance-critical hot paths (use idiomatic packages), teams unfamiliar with FP.
Yes. fp-go is used in production at IBM and elsewhere. v2 is actively maintained and recommended for new projects.
Standard packages: minimal overhead. Idiomatic packages (v2): 2–32× faster, near-native performance. See the performance guide for details.
See the migration guide for a complete walkthrough of the 5 breaking changes and how to handle them.
Summary
- How to install fp-go (v1 or v2)
- How to write pure functions with Result/Either
- How to compose operations with Pipe
- How to transform values with Map and Chain
- How to handle errors automatically
- How to build complex pipelines
Read Why fp-go? to understand when and why to use functional programming in Go.