Go Iterators Are Bad
Based on The PrimeTime's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.
Go 1.23 iterators are controversial mainly because range loops are rewritten into nested callback-style functions with yield-driven control flow.
Briefing
Go 1.23’s new iterator design has sparked backlash because it makes Go feel more “functional” and syntactically magical than many programmers expect from an unapologetically imperative language. The core friction centers on how iteration is expressed: range loops are transformed into nested function calls with “yield”-style control flow, and breaks/escape paths get rewritten into boolean returns. Supporters like the generator-like feel and the ability to run cleanup via defer inside the iterator’s control flow, but critics say the resulting syntax and semantics are harder to read, harder to reason about, and more complex than Go’s usual “simple and explicit” style.
A concrete example from the proposal shows curried functions and a “yield” callback used to drive iteration, including the ability to range backward. While the example is readable in isolation, the broader design is criticized for being “a lot of effing syntax” to express what many consider a basic imperative loop. One proposed equivalence illustrates the transformation: the range body becomes a function that returns a boolean (continue vs stop), and “break” effectively turns into returning false. That rewrite is part of why opponents describe the feature as turning imperative control flow into a functional callback pipeline.
The discussion also splits over iterator implementation strategy—“push” vs “pull.” Russ Cox’s rationale (as referenced in the conversation) favors storing state inside the iterator’s control flow so that cleanup and minimal stack-frame sharing work naturally, and it argues that push iterators are often more convenient to implement. Critics counter that pull-style iterators can be easier to understand and that storing state outside the control flow—such as in a struct with explicit fields—matches how many programmers already model iterators in languages like Rust. That struct-based approach is repeatedly framed as more “one-dimensional,” easier to implement mentally, and less dependent on nested function layers.
Beyond readability, performance and ergonomics concerns surface. Some worry that closures and callbacks—central to the generator-like design—could be a poor fit for Go’s typical performance expectations, even if the feature is mostly used rather than implemented by most developers. Others argue the standard library and third-party packages will shoulder most iterator complexity, so the average Go programmer will mostly consume iterators rather than build them.
The conversation broadens into language-design philosophy: Go is designed for broad adoption, not for researchers or highly specialized language tinkerers. Critics say the iterator design feels out of character for Go’s “grug” audience—people who want straightforward, patchable code rather than clever syntax. Supporters emphasize that iterators unlock useful patterns and align with Go’s evolving ecosystem (generics, reusable slice/map utilities). Still, the loudest takeaway is cultural: even when the design choices are defensible, the resulting syntax and control-flow rewriting make Go 1.23 iterators feel like a departure from what many people think Go should be.
Cornell Notes
Go 1.23’s iterator proposal is drawing anger because it makes Go’s range loops behave like generator-style callbacks: the loop body gets wrapped into nested functions, and control-flow exits like break are rewritten into boolean returns. Supporters like the generator feel and the way defer-based cleanup can fit naturally into the iterator’s control flow. Critics say the syntax is too “functional” for an imperative language and that the semantics are harder to read—especially when the design relies on closures and yield-style plumbing. The debate also contrasts “push” vs “pull” iterators, with opponents preferring struct-based, state-in-a-field iterators (closer to Rust) over control-flow-driven state (closer to generator transforms).
What specific mechanism makes Go 1.23 iterators feel “functional” to critics?
Why do some people prefer “pull” iterators over “push” iterators in this discussion?
How does the debate about state placement (control flow vs struct fields) shape the criticism?
What role does defer-based cleanup play in the pro-iterator argument?
Why do some participants think most Go programmers won’t be harmed even if iterators are complex?
What broader language-philosophy argument appears in the backlash?
Review Questions
- Which transformation described in the discussion turns break-like control flow into a boolean return, and why does that matter for readability?
- Compare the struct-based iterator approach and the control-flow/yield-based approach: what does each make easier or harder to reason about?
- How do the push vs pull comparisons relate to laziness, buffering, and copying in the examples given?
Key Points
- 1
Go 1.23 iterators are controversial mainly because range loops are rewritten into nested callback-style functions with yield-driven control flow.
- 2
Break and other escape paths are effectively converted into boolean returns (continue vs stop), which critics see as functional-style plumbing in an imperative language.
- 3
Supporters value generator-like behavior and the ability to use defer for cleanup naturally within iterator control flow.
- 4
The push vs pull debate centers on laziness and incremental consumption: pull is framed as more efficient and easier to understand than push pipelines that may buffer or copy.
- 5
A major readability dispute is whether iterator state should live in control flow (generator transform) or in explicit struct fields (state-machine style).
- 6
Some participants argue complexity is acceptable because most developers will consume iterators from the standard library or third-party packages rather than implement them.
- 7
The backlash also reflects Go’s cultural design goal: keep features approachable and explicit for typical programmers rather than “magical” cleverness.