swift-async-algorithms: debounce, throttle and merge for Swift AsyncSequence
Async Algorithms for Swift
At a glance
- What is it?
- Apple's swift-async-algorithms package adds time-based and multi-input operators to Swift's AsyncSequence, and its own documentation is the only real specification of the edge cases. Here is what it covers, how to add it to a Package.swift, and where it stops being the right tool.
- Who is it for?
- Adopt swift-async-algorithms if you are already on Swift concurrency and your problem is ordering or timing across more than one asynchronous source; the debounce, throttle, merge and combineLatest implementations save you from writing the state machine yourself. Do not adopt it if your streams are plain AsyncStream with a single producer and no timing requirement, or if you are still on a Combine-based architecture, because the package does not bridge to Combine publishers.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap swift-async-algorithms fills in Swift concurrency
Swift 5.5 shipped AsyncSequence and the ability to write `for await` loops over it, plus Sequence-style methods such as map and filter. What it did not ship was the set of operators that only make sense when values arrive over time or from several sources at once. The README states the package's motivation directly: it is for algorithms that work with values over time, including those primarily about time, like debounce and throttle, and those about order, like combineLatest and merge.
The audience is narrow and specific. This is for Swift developers who already write async/await code and now need to combine two streams, drop duplicate adjacent values, or rate-limit events from a UI or a socket. The README notes that operations with multiple inputs, the way zip works on Sequence, are surprisingly complex to implement, with subtle behaviors and many edge cases. That sentence is the whole pitch: you can write your own merge, and it will be wrong in a way you discover three months later. The package is the place where those edge cases are handled once.
How the operators are layered: combining, creating, timing
The README groups the API into five categories, and the grouping itself tells you how the package is meant to be used. Combining operators take existing asynchronous sequences and produce one: chain concatenates sequences with the same element type, merge interleaves elements from all underlying sequences, zip produces pairs, combineLatest produces a tuple that updates when any base sequence produces a value, and joined(separator:) flattens a sequence of sequences with a separator inserted.
Creating operators go the other direction. The `async` property composes an asynchronous sequence from a synchronous one. AsyncChannel and AsyncThrowingChannel are the interesting pair: the README describes them as asynchronous sequences with back pressure sending semantics, and the throwing variant can emit failures. That back pressure is the mechanism worth understanding, because it is what distinguishes a channel from a bare AsyncStream.
A third group covers iteration and collection: adjacentPairs, chunks and chunked, compacted, removeDuplicates, and interspersed(with:). The time-based group holds debounce(for:tolerance:clock:), throttle(for:clock:reducing:) and AsyncTimerSequence. Finally, RangeReplaceableCollection.init(_:) and three Dictionary initializers pull every value out of a sequence into a concrete collection. Note that those collection initializers only terminate when the sequence finishes; on an infinite sequence they never return.
Adding the package and a first debounce
Installation is through Swift Package Manager. The repository root contains Package.swift plus [email protected] and [email protected], so the manifest version is selected by your toolchain rather than by a flag you pass. The README does not print a dependency snippet, so the entry you write follows the standard SwiftPM form: a package dependency on the repository URL, with the version taken from the releases the project publishes. The most recent release listed is 1.1.5, published on 2026-06-29.
Once resolved, the module is imported as AsyncAlgorithms. The README's contents list gives the signature `debounce(for:tolerance:clock:)`, which emits values after a quiescence period has been reached, and `throttle(for:clock:reducing:)`, which ensures a minimum interval has elapsed between events. A debounce therefore collapses a burst of input into one emission after the source goes quiet, while a throttle keeps a steady cadence under continuous input. AsyncTimerSequence, in the same group, emits the value of now at a given interval repeatedly.
The `clock` parameter is not optional in the debounce signature, which means you choose the time source explicitly rather than inheriting one. The README's guide for the operator is the place to check which clock suits your platform. If you need to move bytes rather than values, AsyncBufferedByteIterator is documented as an iterator for byte sequences derived from asynchronous read functions. The README calls it highly efficient, which is the package's own wording, not a measured claim.
Where the package is the wrong tool
The most common mistake is reaching for these operators when a single AsyncStream would do. If you have one producer, no timing requirement and no second source to combine, adding the dependency buys you nothing except a transitive module in your build graph. The README's own framing, algorithms that work with values over time, is the test: if time and multiplicity are not part of your problem, you are outside the intended scope.
The second boundary is Combine. The package does not appear in the README as a bridge to Combine publishers, and the related searches show people asking how the two compare. They are different systems with different types; AsyncSequence and Publisher are not interchangeable, and nothing in the repository layout suggests an adapter ships here. If your architecture is built on publishers, converting is an application-level decision, not a package import.
The third is availability. The repository ships three manifests, Package.swift, [email protected] and [email protected], which is a signal about how far back the package tries to reach, but it also means your toolchain version determines which manifest SPM reads. If you are pinned to an older Swift, check which manifest applies before assuming the current API surface is available to you. The README does not document a compatibility matrix beyond those files.
swift-async-algorithms compared with writing it yourself, and with Combine
The realistic alternative to this package is not another library; it is the merge you write in an afternoon. A hand-rolled merge typically spawns a task per source, writes into a shared continuation, and finishes when all sources finish. It works in the happy path and fails on cancellation: when the consuming task is cancelled, the child tasks need to be cancelled too, and the continuation must be finished exactly once. That is the class of subtle behavior the README points at when it says a shared package can get these details correct, with extensive testing and documentation.
The second alternative is Combine, and the difference in approach is structural rather than cosmetic. Combine is a push-based framework with publishers, subscribers and demand signalled through Subscriber.Demand; it predates async/await and integrates with it only awkwardly. AsyncSequence is pull-based and integrates with structured concurrency, so intermediate state is a local variable and `try` applies directly to throwing calls. The README makes exactly this argument when it describes the foundation added in Swift 5.5. If you are starting a new codebase on Swift concurrency, Combine is the legacy choice, not the competing one. If you maintain a large Combine codebase, this package will not replace it incrementally.
Maintenance, release cadence and the Apache-2.0 licence
The repository is not archived, and the last push was on 2026-09-03, which is recent relative to the release history. Releases are incremental rather than sweeping: 1.1.3 on 2026-03-04, 1.1.4 on 2026-05-18 and 1.1.5 on 2026-06-29 are all patch-level version bumps. For an adopter this is the good pattern, because patch releases mean the API surface is stable and an upgrade is unlikely to require source changes. It also means you should not expect new operators to appear quickly.
The upgrade cost that actually bites is toolchain, not package version. With Package.swift, [email protected] and [email protected] in the root, a Swift toolchain upgrade can change which manifest is resolved, and the resolved manifest determines the minimum platform requirements your app inherits. Budget for that check whenever you move Xcode versions, not just when the package cuts a release.
The licence is Apache-2.0, per the LICENSE.txt file at the repository root, and the repository also carries a .license_header_template and a .licenseignore. Apache-2.0 is a permissive licence with an explicit patent grant, which matters for a dependency that ships inside a distributed app. It also carries notice and attribution obligations. This is a description of the licence text, not legal advice; run the specifics past whoever handles compliance for your product.
Editorial conclusion
Adopt swift-async-algorithms if you are already on Swift concurrency and your problem is ordering or timing across more than one asynchronous source; the debounce, throttle, merge and combineLatest implementations save you from writing the state machine yourself. Do not adopt it if your streams are plain AsyncStream with a single producer and no timing requirement, or if you are still on a Combine-based architecture, because the package does not bridge to Combine publishers. Before committing, read the docc guide for each operator you plan to use, confirm the Package.swift tools version against your toolchain, and check whether your deployment targets include platforms where Foundation's clock types behave differently.
Frequently asked questions
What is asynchronous in Swift?
In Swift, asynchronous code is built on async/await and AsyncSequence, which the README describes as the foundation added in Swift 5.5. That foundation lets a `for/in` loop use `await` to process values from an asynchronous sequence while structured concurrency keeps intermediate state in local variables.
What is the difference between async and sync in Swift?
Synchronous code runs to completion before the next statement, while asynchronous code suspends and resumes, which is why Swift's AsyncSequence requires `for await` rather than a plain loop. The README frames swift-async-algorithms as the home for algorithms that work with values arriving over time, a problem synchronous sequences do not have.
What is async vs await?
The README describes structured concurrency as allowing code where `try` can be used directly on functions that throw and asynchronous logic is written much like synchronous logic. In that model `async` marks a function or sequence as suspending, and `await` is the point where the caller suspends to receive its result.
Is async faster than sync?
The README makes no performance claim about async versus sync, so speed is not the reason to adopt this package. Its stated goals are first-class integration with async/await, a home for time-based algorithms, and being cross-platform and open source.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/apple-swift-async-algorithms)