Software development, in Go

Your back end,
built to stay current

We build back-end services and tools in Go, keep them on a supported release, and check every change with its tests and the race detector, so each upgrade stays a small, regular step.

Thanks, we’ll be in touch soon.

{{ heroStatus }}

You’re in good company

A few of the companies we’ve worked with.

When the upgrade keeps waiting

Go supports each release until there are two newer ones, so a service that isn’t moved up about twice a year stops getting security fixes.

The move is usually small: the Go team keeps the Go 1 compatibility promise and expects almost all programs to compile and run as before. But a release can remove an old setting, change the text of an error message or a formatting detail, or need a newer operating system, so each move needs checking, and without an owner it keeps waiting.

Under load, other gaps show: a request with no time limit, or a goroutine waiting on something that never comes, holds memory and connections until the service slows. How much of this applies varies from codebase to codebase.

What we build

Every change runs through the tests and the race detector before anyone reviews it, and every upgrade is tried on a branch first, so what you run stays as it is until each change has passed.

  • New back-end services

    APIs and services written in Go from the first commit, with their tests, their logs and a time limit on every call built in from the start.

  • Command-line tools

    The tools your team runs every day, each built as one file for the systems it runs on, as Go’s toolchain builds by default, with tests of its own.

  • Upgrades to a supported release

    The go line raised one release at a time, with the settings each release removes checked first. Each step is tried on a branch, apart from the code you run, and merges once the tests and the race detector pass.

  • Services moved to Go, where it helps

    One service at a time, run beside the one it replaces and checked against the same tests, until it can take over. Only where it helps, and never as a rule.

  • Work done at once, within limits

    Goroutines, channels and the context package, with a time limit on every incoming request and every outgoing call, and a cap on the work in flight.

  • Faster services, measured first

    Each slow path profiled before anything changes, with profile-guided optimization where it helps, and every change measured before and after.

  • APIs and integrations

    REST and gRPC services, with the data that arrives checked where it enters and the contract between each pair of systems written down.

  • Checks on every change

    The tests, the race detector, fuzzing, vet and govulncheck run in CI on every change, and a change merges once they pass.

  • AI where it helps

    AI coding assistants can draft the routine parts. Each change they make is tested and reviewed like any other, and a person decides what merges.

Two releases a year, two supported

Go ships a major release about every six months and supports each one until there are two newer ones, fixing security problems in it through minor revisions. Today that’s Go 1.27, released on August 19, 2026, and Go 1.26.

Each release keeps the Go 1 compatibility promise, and the Go team expects almost all programs to compile and run as before. What can still change: support for an old setting ends (Go 1.27 removed asynctimerchan and several others, and a module that still sets one to its old value stops the go command), encoding/json now runs on its new implementation, with the same behavior but error text that may differ, gofmt may move some whitespace, and macOS 13 is now the oldest macOS supported.

So we raise the go line one release at a time, on a branch, with the tests and the race detector, after checking the settings each release removes. Choose a line of the go.mod to see what it does.

A go.mod of our own

go 1.25

What it does: The oldest Go release that can build the module, and the release its default GODEBUG settings follow.

On Go 1.27: Go 1.25 no longer gets security fixes: it left support when Go 1.27 came out, on August 19, 2026. The route is to 1.26, then to 1.27, one release at a time.

toolchain go1.25.14

What it does: Suggests the toolchain to build with: the go command may switch to it when the one installed is older. Without the line, the go line’s release is the suggestion.

On Go 1.27: When we raise the go line, we set this line to a supported toolchain or take it out, so the module never asks for a 1.25 toolchain, which no longer gets security fixes.

godebug asynctimerchan=1

What it does: Asks for the timer channels Go used before 1.23, through a GODEBUG setting in the module’s go.mod.

On Go 1.27: Go 1.27 removed the setting. Set to 1, the old behavior, the go command stops with an error; set to 0, its final default, it’s accepted. So the line comes out once the code no longer needs the old timers.

The require block

What it does: The other modules this one is built with, and the version of each that it requires.

On Go 1.27: For modules on go 1.27 or later, go mod tidy merges duplicate require blocks into at most two, one for direct dependencies and one for indirect ones. And govulncheck checks these modules against the Go vulnerability database and reports the vulnerabilities that actually affect your code.

We raise the go line one release at a time.

{{ rlStatus }}

Many things at once, each with a time limit

Go runs work at once in goroutines, passes values between them on channels, and carries a deadline and a cancel signal with each request through its context package.

A request with no time limit, or a goroutine blocked on something that can never happen, holds memory and connections. Go 1.27’s goroutine leak profile reports a large class of such leaks, though not every one, and the race detector finds unsafe sharing when it happens during a run.

So we give every incoming request and every outgoing call a time limit, cap the work in flight, and run the race detector and the leak profile before each release.

Start with the go.mod and the busiest service

The review covers each module: its go line, its toolchain line and its GODEBUG settings, in the godebug block and in //go:debug lines, against the settings Go 1.27 removed; the Go release each service is built and run with, in CI and in its images, against the supported releases; its dependencies, how current they are, what govulncheck reports and any replace lines or vendored code; the tests on the busiest paths, whether the race detector runs in CI, and any fuzz targets; the work done at once, the time limits on incoming requests and outgoing calls, the work in flight, and the leak profile on Go 1.27; performance, its profiles, profile-guided optimization, and memory and container limits, measured as a baseline for the review; the builds, cgo, how each binary is linked, the systems it’s built for and the images it runs in; the tests that compare encoding/json’s error text, which Go 1.27 may change; and the logs, metrics and traces.

The first step is one of these four, each tried on a branch: one service’s go line raised to a supported release, with its tests and the race detector passing; time limits added to one service’s incoming requests and outgoing calls; govulncheck and the race detector added to CI; or one busy path profiled, with one change measured before and after.

What you receive is a written review and a proposal for that first step.

  1. Which Go releases are supported?

    Each major Go release is supported until there are two newer ones, and its security fixes come in minor revisions. Today that’s Go 1.27, released on August 19, 2026, and Go 1.26. Go 1.25 left support the day Go 1.27 came out.

  2. Is moving to a newer Go release risky?

    Usually it’s a small step: the Go team expects almost all programs to compile and run as before. We still check what each release changes for your code, the settings it removes, error text and formatting among them, and try the move on a branch with your tests and the race detector before it merges.

  3. Will the JSON change in Go 1.27 affect us?

    In behavior, it shouldn’t: encoding/json now runs on its new v2 implementation, and the Go team says marshaling and unmarshaling behave as before, though the exact text of error messages may differ. Tests that compare those messages are the ones to check. An opt-out restores the old implementation for now, and the Go team expects to remove it in a future release.

  4. Should we rewrite a service in Go?

    It depends on the service and what holds it back. Where a move would help, we move one service at a time, run it beside the old one against the same tests, and switch over only once it matches. Often the better first step is to fix the service where it is.

  5. Will Go make our services faster?

    It depends on where the time goes. If a service mostly waits on a database or another service, a change of language may not help. We profile first, change what the profile points to, and measure before and after, so you see what each change does for yours.

  6. How do you find goroutine leaks and data races?

    The race detector runs with your tests and reports unsafe sharing when it happens during a run, so it finds races only on the paths the tests reach. On Go 1.27 the goroutine leak profile reports a large class of goroutines blocked for good, though not every one. And a time limit on every request and call keeps the rest from piling up.

  7. Can our Go services run in containers and on our cloud?

    Yes. Each service builds into one file for the system it runs on, and that file is what goes into its image. We set it up to run on the platforms you already use.

  8. How do you keep our Go code secure?

    We keep every service on a supported release, run govulncheck in CI to report the known vulnerabilities your code actually reaches, keep dependencies current, and check data where it enters.

  9. Can AI assistants help with our Go code?

    Yes, as long as their changes meet the same bar as anyone’s. An assistant can draft routine code, tests among it, and each change still goes through the tests, the race detector and a person’s review before it merges.

  10. Can you take over our existing codebase?

    Yes. We start by reading it: each module’s go.mod, its build and how each service runs. Then we name an owner for each service and agree one first piece of work with you, alongside any developers you have.

  11. What does it cost?

    It depends on your services, how many there are and which first step you choose. Before any work starts, you get a written proposal with the scope, the price or rate, and how you pay.

  12. Can we hire developers for our own team?

    Yes. Within 48 hours of your request we introduce developers who know Go codebases and work in your time zone. You meet them first, and decide who joins.

Go is a trademark of Google. Webair is independent of Google, and isn’t sponsored by, endorsed by or affiliated with it.

Let’s build it better

Tell us which Go release each of your services runs on, and how it’s built and run. We’ll agree on a first step in a short call.

Thanks, we’ll be in touch soon.

{{ ctaStatus }}