Skip to content

Balking ​

Balking skips an operation when an object's current state makes the request inappropriate. It differs from queueing: the extra request is deliberately ignored or rejected.

iOS use case ​

Users may tap Import twice. Starting two imports for the same selection wastes work and can create duplicate photos.

swift
import Foundation

actor PhotoImporter {
    private var isImporting = false

    func importPhotos(_ identifiers: [String]) async throws -> Bool {
        guard !isImporting else { return false }
        isImporting = true
        defer { isImporting = false }

        // Replace with PhotosUI item loading and persistence.
        for identifier in identifiers {
            try Task.checkCancellation()
            print("Importing \(identifier)")
        }
        return true
    }
}

Actor isolation makes the check-and-set atomic before the first suspension. false means the caller's request was balked; the UI can keep its current progress state rather than showing a second spinner.

Production decisions ​

Decide whether to ignore, report, or join an existing task. If callers should await the same result, use in-flight request coalescing instead. Use defer to reset state on success, error, or cancellation. Keep the button disabled while importing as a UI affordance, but still enforce the state rule in the importer.

Follow the state transition ​

The actor serializes access to isImporting. The guard checks whether an import is active; setting the flag happens before the loop can suspend, so a second caller cannot also pass the guard. The defer resets the flag when the method exits through completion, a thrown error, or cancellation. Returning false is a deliberate API result: the second request did no work.

From example to an iOS screen ​

The import button can be disabled during progress, but that only protects this particular UI. Deep links, accessibility actions, and repeated programmatic calls still reach the importer, so the actor owns the invariant. Have the view model interpret false as “already importing” and leave the existing progress UI intact. In a real PhotosUI import, each identifier represents a selected item; load it and persist it in the loop, reporting progress on the main actor.

Failure modes and verification ​

Balking discards the second request. If the second tap contains different photos, discarding may lose user intent; queue the request or coalesce identical requests instead. Test two overlapping calls with a controllable suspension point, then test that cancellation or error allows a later call to start. A Boolean is sufficient for one global import; use IDs or a set of in-flight keys for per-album imports.

All patterns

Released under the MIT License.