DO NOT search for go.mod files or try to detect the version yourself. Use ONLY the version shown above.
If version detected (not "unknown"):
Say: "This project is using Go X.XX, so I’ll stick to modern Go best practices and freely use language features up to and including this version. If you’d prefer a different target version, just let me know."
Do NOT list features, do NOT ask for confirmation
If version is "unknown":
Say: "Could not detect Go version in this repository"
Use AskUserQuestion: "Which Go version should I target?" → [1.23] / [1.24] / [1.25] / [1.26]
When writing Go code
, use ALL features from this document up to the target version:
Prefer modern built-ins and packages (
slices
,
maps
,
cmp
) over legacy patterns
Never use features from newer Go versions than the target
Never use outdated patterns when a modern alternative is available
Features by Go Version
Go 1.0+
time.Since
:
time.Since(start)
instead of
time.Now().Sub(start)
Go 1.8+
time.Until
:
time.Until(deadline)
instead of
deadline.Sub(time.Now())
Go 1.13+
errors.Is
:
errors.Is(err, target)
instead of
err == target
(works with wrapped errors)
Go 1.18+
any
Use
any
instead of
interface{}
bytes.Cut
:
before, after, found := bytes.Cut(b, sep)
instead of Index+slice
strings.Cut
:
before, after, found := strings.Cut(s, sep)
Go 1.19+
fmt.Appendf
:
buf = fmt.Appendf(buf, "x=%d", x)
instead of
[]byte(fmt.Sprintf(...))
atomic.Bool
/
atomic.Int64
/
atomic.Pointer[T]
Type-safe atomics instead of
atomic.StoreInt32
var
flag atomic
.
Bool
flag
.
Store
(
true
)
if
flag
.
Load
(
)
{
...
}
var
ptr atomic
.
Pointer
[
Config
]
ptr
.
Store
(
cfg
)
Go 1.20+
strings.Clone
:
strings.Clone(s)
to copy string without sharing memory
bytes.Clone
:
bytes.Clone(b)
to copy byte slice
strings.CutPrefix/CutSuffix
:
if rest, ok := strings.CutPrefix(s, "pre:"); ok
errors.Join
:
errors.Join(err1, err2)
to combine multiple errors
context.WithCancelCause
:
ctx, cancel := context.WithCancelCause(parent)
then
cancel(err)
context.Cause
:
context.Cause(ctx)
to get the error that caused cancellation
Go 1.21+
Built-ins:
min
/
max
:
max(a, b)
instead of if/else comparisons
clear
:
clear(m)
to delete all map entries,
clear(s)
to zero slice elements
slices package:
slices.Contains
:
slices.Contains(items, x)
instead of manual loops
slices.Index
:
slices.Index(items, x)
returns index (-1 if not found)
slices.IndexFunc
:
slices.IndexFunc(items, func(item T) bool { return item.ID == id })
slices.SortFunc
:
slices.SortFunc(items, func(a, b T) int { return cmp.Compare(a.X, b.X) })
slices.Sort
:
slices.Sort(items)
for ordered types
slices.Max
/
slices.Min
:
slices.Max(items)
instead of manual loop
slices.Reverse
:
slices.Reverse(items)
instead of manual swap loop
slices.Compact
:
slices.Compact(items)
removes consecutive duplicates in-place
slices.Clip
:
slices.Clip(s)
removes unused capacity
slices.Clone
:
slices.Clone(s)
creates a copy
maps package:
maps.Clone
:
maps.Clone(m)
instead of manual map iteration
maps.Copy
:
maps.Copy(dst, src)
copies entries from src to dst
maps.DeleteFunc
:
maps.DeleteFunc(m, func(k K, v V) bool { return condition })
sync package:
sync.OnceFunc
:
f := sync.OnceFunc(func() { ... })
instead of
sync.Once
+ wrapper
sync.OnceValue
:
getter := sync.OnceValue(func() T { return computeValue() })
Use
time.Tick
freely — as of Go 1.23, the garbage collector can recover unreferenced tickers, even if they haven't been stopped. The Stop method is no longer necessary to help the garbage collector. There is no longer any reason to prefer NewTicker when Tick will do.
Go 1.24+
t.Context()
not
context.WithCancel(context.Background())
in tests.
ALWAYS use t.Context() when a test function needs a context.
Before:
func
TestFoo
(
t
*
testing
.
T
)
{
ctx
,
cancel
:=
context
.
WithCancel
(
context
.
Background
(
)
)
defer
cancel
(
)
result
:=
doSomething
(
ctx
)
}
After:
func
TestFoo
(
t
*
testing
.
T
)
{
ctx
:=
t
.
Context
(
)
result
:=
doSomething
(
ctx
)
}
omitzero
not
omitempty
in JSON struct tags.
ALWAYS use omitzero for time.Duration, time.Time, structs, slices, maps.
Before:
type
Config
struct
{
Timeout time
.
Duration
json:"timeout,omitempty"
// doesn't work for Duration!
}
After:
type
Config
struct
{
Timeout time
.
Duration
json:"timeout,omitzero"
}
b.Loop()
not
for i := 0; i < b.N; i++
in benchmarks.
ALWAYS use b.Loop() for the main loop in benchmark functions.
Before:
func
BenchmarkFoo
(
b
*
testing
.
B
)
{
for
i
:=
0
;
i
<
b
.
N
;
i
++
{
doWork
(
)
}
}
After:
func
BenchmarkFoo
(
b
*
testing
.
B
)
{
for
b
.
Loop
(
)
{
doWork
(
)
}
}
strings.SplitSeq
not
strings.Split
when iterating.
ALWAYS use SplitSeq/FieldsSeq when iterating over split results in a for-range loop.
Before:
for
_
,
part
:=
range
strings
.
Split
(
s
,
","
)
{
process
(
part
)
}
After:
for
part
:=
range
strings
.
SplitSeq
(
s
,
","
)
{
process
(
part
)
}
Also:
strings.FieldsSeq
,
bytes.SplitSeq
,
bytes.FieldsSeq
.
Go 1.25+
wg.Go(fn)
not
wg.Add(1)
+
go func() { defer wg.Done(); ... }()
.
ALWAYS use wg.Go() when spawning goroutines with sync.WaitGroup.
Before:
var
wg sync
.
WaitGroup
for
_
,
item
:=
range
items
{
wg
.
Add
(
1
)
go
func
(
)
{
defer
wg
.
Done
(
)
process
(
item
)
}
(
)
}
wg
.
Wait
(
)
After:
var
wg sync
.
WaitGroup
for
_
,
item
:=
range
items
{
wg
.
Go
(
func
(
)
{
process
(
item
)
}
)
}
wg
.
Wait
(
)
Go 1.26+
new(val)
not
x := val; &x
— returns pointer to any value.
Go 1.26 extends new() to accept expressions, not just types.
Type is inferred: new(0) → int, new("s") → string, new(T{}) → T.
DO NOT use
x := val; &x
pattern — always use new(val) directly.
DO NOT use redundant casts like new(int(0)) — just write new(0).
Common use case: struct fields with pointer types.
Before:
timeout
:=
30
debug
:=
true
cfg
:=
Config
{
Timeout
:
&
timeout
,
Debug
:
&
debug
,
}
After:
cfg
:=
Config
{
Timeout
:
new
(
30
)
,
// int
Debug
:
new
(
true
)
,
// *bool
}
errors.AsTypeT
not
errors.As(err, &target)
.
ALWAYS use errors.AsType when checking if error matches a specific type.
Before:
var
pathErr
*
os
.
PathError
if
errors
.
As
(
err
,
&
pathErr
)
{
handle
(
pathErr
)
}
After:
if
pathErr
,
ok
:=
errors
.
AsType
[
*
os
.
PathError
]
(
err
)
;
ok
{
handle
(
pathErr
)
}