skip to content

In Go, before writing your own generic Contains or SortFunc helper, how do you check the standard library already ships it?

level: middleimportance: nice to knowfreq 35%

answer

  1. the answer is a terminal command
  2. no browser, no search engine
  3. one command, package or symbol
  4. generics 1.18, the libraries later
  5. go doc slices, go doc maps

basics

~20 s

Run go doc slices and go doc maps, or go doc slices.Contains for one symbol. Since Go 1.21 those packages ship generic search, sort, clone and compare helpers, so most hand-rolled versions are duplicates that drift.

solid answer

~50 s

The reflex is `go doc slices` and `go doc maps` before writing anything: they print the package's exported API without leaving the terminal, and `go doc slices.ContainsFunc` prints one symbol's signature and doc comment. Those two packages arrived in Go 1.21 and cover the helpers teams most often hand-roll — membership, index, sort, clone, compare, min and max — so a utility package written before 1.21 tends to accumulate near-duplicates. The cost of the duplicate is not the ten lines: it is a second implementation to test, a second name reviewers must learn, and behaviour that quietly diverges from the standard one at the edges. When the standard library has something close but not identical, prefer a thin wrapper over the standard function to a parallel implementation. Go 1.26 also rebuilt `go fix` as the home of the modernizers, which rewrite older patterns into standard-library calls.

code

text · 3 lines
text
go doc slices
go doc maps
go doc slices.ContainsFunc

go deeper

for a junior

Know that go doc slices and go doc maps print a package's API in the terminal, and that Go gained generic slice and map helpers after generics themselves.

for a middle

Explain the timeline that causes the duplicates: generics in Go 1.18, the slices and maps packages in Go 1.21, so any older utility package predates them. Name what those packages cover.

for a senior

Argue the real cost of a duplicate — a second test suite, edge-case drift, and a name every reader must investigate — and show how you build on a standard primitive with a predicate instead of writing a parallel implementation.

for a principal

Own the cleanup as policy rather than as taste: how a team retires its utility package without a big-bang refactor, when a periodic modernizer sweep beats a per-pull-request gate, and how upgrade cadence decides which standard helpers you may use at all.

## The reflex The check costs seconds and does not need a browser: ```text go doc slices # every exported symbol in the package go doc maps go doc slices.Contains # one symbol, with its doc comment ``` `go doc` reads the packages in the module's own build list, so it prints the API for the toolchain and dependency versions you are actually compiling against — not whatever a search engine surfaced. (Go 1.26 removed the separate `cmd/doc` binary; `go doc` is the command.) `go doc pkg@version` was added in Go 1.27 for looking at a version you have not selected. ## Why this leaf exists Generics landed in Go 1.18, but the generic *libraries* landed in Go 1.21, three releases and eighteen months later. Every codebase older than that grew a `internal/util` or `pkg/collections` package full of hand-written helpers, because at the time there was nothing else. A coverage-report aggregator I have seen carried its own `contains`, `indexOf`, `keys`, `uniq`, `sortBy` and `clone` — six functions, six sets of tests, and by then five of them existed upstream. Go 1.21 added `slices` (membership, index, sorting, binary search, cloning, comparison, min and max over a slice), `maps` (clone, copy, equality, deletion by predicate) and `cmp`, plus the builtins `min`, `max` and `clear`. Go 1.23 added sequence-shaped functions to the same packages. If a helper you are about to write operates on a slice or a map and does something obvious, the odds now favour it already existing. ## What the duplicate actually costs The honest objection is that `contains` is a three-line loop and importing a package to avoid it is silly. Three things answer that: 1. **Correctness at the edges.** The standard versions have settled semantics for nil inputs, empty inputs, NaN, and what happens to the elements a delete vacates. A local reimplementation gets these right until someone edits it. 2. **Free improvement.** The standard implementations are optimised by the Go team across releases, and your code picks that up on a toolchain upgrade without a diff. 3. **Reader cost.** A reviewer who sees `slices.Contains` knows what it does. A reviewer who sees `util.Contains` must open it, and must keep wondering whether it differs. Multiply by every helper and every new joiner. The counterweight: do not wrap a standard function in a local alias just to keep an old name alive. That is the worst of both, an extra name for zero behaviour. ## When the standard library is close but not identical This is the common case and the interesting one. You need membership by a field, and `slices.Contains` compares whole elements — but `slices.ContainsFunc` takes a predicate. You need a sort by two keys — `slices.SortFunc` takes a comparison function returning a negative, zero or positive `int`. The pattern is: build the small thing you need *on top of* the standard primitive rather than beside it, so the loop, the bounds and the sort algorithm remain someone else's problem and only your predicate is yours. If nothing fits — Go still ships no generic `Map`, `Filter` or `Reduce`, and that is deliberate — write the helper, keep it small, and put it where its users are rather than in a grab-bag `util` package. ## Making the check systematic One engineer remembering to run `go doc` is not a process. Cheaper habits: put "does the standard library already do this?" in the review checklist for any new exported helper; run the modernizers — Go 1.26 rebuilt `go fix` as their home, and they rewrite recognisable older patterns into standard-library calls — as a periodic sweep rather than a per-PR gate; and when you delete a duplicate, delete its tests with it rather than leaving them asserting behaviour nothing implements any more. The interview signal here is small but real: a candidate who says "I would check `go doc slices` first" is telling you they treat the standard library as a living thing that has changed under them, rather than as the Go they learned once.

  • The standard library has something close but not identical to what you need. Wrap it or reimplement it?
    Wrap it. Membership by a field is `slices.ContainsFunc` with your predicate; a two-key ordering is `slices.SortFunc` with your comparison. Building on the standard primitive leaves the loop, the bounds and the algorithm as someone else's tested problem, and only your predicate as yours. A parallel implementation doubles the surface for no gain.
  • Which of these helpers does Go still not ship, and what should a team do about it?
    Generic `Map`, `Filter` and `Reduce` are deliberately absent — the idiomatic answer is a `for` loop. If a transform genuinely repeats across a package, write a small local helper next to its users rather than founding a general functional toolkit that every reader has to learn.
  • How do you stop duplicates accumulating without policing every pull request?
    Make it a review-checklist line for any new exported helper, and run the modernizers periodically — Go 1.26 rebuilt `go fix` as their home, and they rewrite recognisable older patterns into standard-library calls. When you delete a duplicate, delete its tests too, so nothing is left asserting behaviour that no longer has an implementation.

saying these in an interview costs you the question

  • Assumes Go has no generic slice or map helpers
  • Reimplements a standard helper because it is short
  • Cites the Go they learned rather than checking go doc
  • Thinks the slices package arrived with generics in 1.18
  • Expects the standard library to ship Map and Filter
  • Keeps a local alias wrapping a standard function