---
name: swift-api-design-guidelines
description: "Swift API Design Guidelines: call-site clarity over brevity, argument labels and prepositions, method versus property, side-effect naming, mutating and nonmutating pairs, make factories, protocol and generic parameter names, casing, doc comments, complexity notes. Use when naming types, methods, properties or parameters, writing doc comments or reviewing an API for standard-library consistency. Not for language features, concurrency or lint config."
metadata:
  source: multi-agent-pipeline
---

# Swift API Design Guidelines

Use this when choosing names for types, methods, properties, parameters and
argument labels, and when writing the doc comments that go with them. Baseline:
Swift 6.3.

Out of scope, with where it goes instead:

- Language features and syntax, such as `some` versus `any`: `swift-language`.
- Concurrency: `swift-concurrency`.
- Lint rules and configuration: `swiftlint`.

When a request mixes these, answer the naming part briefly, then point to the
sibling skill for the rest. Do not start implementing the sibling's domain.

## Argument labels

Decide the first argument's label by testing these in order; the first rule
that applies wins.

1. **Grammatical phrase.** If the first argument reads as part of a phrase that
   starts in the base name, drop its label and move any leading words into the
   base name: `addSubview(y)`, not `add(subview: y)`.
2. **Conversion initializer.** A value-preserving (widening) conversion takes
   no first label: `Int64(someUInt32)`, `String(someCharacter)`. A narrowing
   or lossy one keeps a label that says so: `Int64(truncating:)`,
   `String(describing:)`.
3. **Indistinguishable peers.** When the arguments cannot usefully be told
   apart, label none of them: `min(x, y)`, `zip(names, scores)`.
4. **Prepositional phrase.** When the base name ends in a preposition that the
   first argument finishes, the preposition becomes the label: `removeBoxes(havingLength: 12)`,
   `fade(from: red)`, `relativePath(from: root)`.
5. **One abstraction, several arguments.** If the first two arguments are parts
   of a single idea, keep the preposition in the base name so each part gets
   its own label: `moveTo(x: b, y: c)`, not `move(toX: b, y: c)`.
6. **Otherwise, label it:** `split(maxSplits: 2)`, `setTitle("OK", for: .normal)`,
   `dismiss(animated: true)`, `sorted(by: >)`.

| Situation | Rule | Example |
|---|---|---|
| Reads as a grammatical phrase | no label; merge words into the base name | `addSubview(y)` |
| Value-preserving init | no first label | `Int64(someUInt32)` |
| Peers | no labels | `min(x, y)` |
| Prepositional phrase | preposition is the label | `fade(from: red)` |
| Two args, one abstraction | preposition in the base name | `cellAt(row:column:)` |
| Anything else | label | `split(maxSplits: 2)` |

Edge cases, conversion kinds, parameter names and default arguments:
[the argument labels reference](references/argument-labels-and-parameters.md).

## Method or property

A property answers a question about the receiver. Make it a property when it
takes no arguments, has no side effects and cannot fail; callers will assume
it is cheap, so if it is not O(1), document the cost. Make it a method when it
needs input, changes state, can throw or suspend, or does work heavy enough
that a property would mislead.

```swift
protocol TrackList {
    var trackCount: Int { get }                   // a fact about the receiver
    var isEmpty: Bool { get }
    func tracks(by artist: Artist) -> [Track]     // needs input
    mutating func shuffle()                       // changes state
    func loadArtwork() async throws -> Image      // real work that can fail
}
```

More in [the naming reference](references/naming-and-clarity.md).

## Naming by side effect

- **Mutating** operations are imperative verb phrases: `sort()`,
  `append(newElement)`, `remove(at:)`, `invalidate()`.
- **Non-mutating** operations read as noun phrases, adjective phrases or a
  description of what comes back: `distance(to:)`, `intersection(_:)`,
  `trimmingCharacters(in:)`.
- **Booleans** should state something true or false about the receiver: `isEmpty`,
  `contains(_:)`, `isFileURL`. Avoid `empty` (verb or adjective?) and
  `includes` (an unfinished phrase).

### Mutating and non-mutating pairs

When both forms exist, name them as a pair.

**Verb operations.** The mutating form is the imperative verb. The
non-mutating form adds `-ed` by default, when "a [verb]-ed [noun]" describes
the result. Fall back to `-ing` only when `-ed` is ungrammatical or would
describe the direct object instead of the returned value. A direct object is a
hint to run that grammar check, not a rule by itself.

| Mutating | Non-mutating | Why |
|---|---|---|
| `sort()` | `sorted()` | `-ed` default |
| `reverse()` | `reversed()` | `-ed` default |
| `sortLines()` | `sortedLines()` | `-ed` describes the result |
| `append(y)` | `appending(y)` | `appended` would not describe the returned receiver |
| `stripNewlines()` | `strippingNewlines()` | `-ed` would name the removed newlines |

**Noun operations.** The non-mutating form is the noun; the mutating form
takes a `form` prefix:

```swift
let both = weekdays.union(weekend)     // new value
weekdays.formUnion(weekend)            // in place
```

| Kind | Mutating | Non-mutating |
|---|---|---|
| Verb (default) | verb | verb + `-ed` |
| Verb where `-ed` fails | verb | verb + `-ing` |
| Noun | `form` + Noun | noun |

**Factories** get a `make` prefix whenever they produce a fresh value: `makeIterator()`,
`makeBuffer()`. If a mixed request already uses `make...` names, confirm them
in a line before routing the rest.

Decision tree, `form` rules, Boolean and factory patterns:
[the side-effects reference](references/side-effects-and-mutating-pairs.md).

## Documentation comments

Every public declaration gets one. The summary is a single sentence fragment
ending in a period, starting with a verb for actions and a noun phrase for
entities. What it says depends on the declaration:

| Declaration | Summary says |
|---|---|
| function or method | the action and its result |
| subscript | what it accesses |
| initializer | what it creates |
| type, property, variable | the thing itself |

```swift
protocol ReadingLog {
    /// Returns the reading closest to `time`, or `nil` if there are none.
    func reading(nearest time: Date) -> Reading?

    /// The number of readings recorded today.
    var todayCount: Int { get }

    /// Creates a log that keeps at most `limit` readings.
    init(limit: Int)

    /// Accesses the reading at `position`.
    subscript(position: Int) -> Reading { get }
}
```

After the summary, use symbol markup: `- Parameter name:`, a `- Parameters:`
block, `- Returns:`, `- Throws:`, `- Complexity:`.

```swift
protocol EditableReadingLog: ReadingLog {
    /// Removes the reading at `position` and hands it back.
    ///
    /// - Parameter position: The index of the reading to remove. Must be a
    ///   valid index of the log.
    /// - Returns: The reading that was removed.
    /// - Complexity: O(*n*) in the log's length.
    @discardableResult
    mutating func remove(at position: Int) -> Reading
}
```

A property looks free to call, so readers expect constant time. When a computed
property costs more, the doc comment has to say so:

```swift
/// The combined mass of every item in the crate.
///
/// - Complexity: O(*n*) in the item count.
var totalMass: Measurement<UnitMass> {
    items.map(\.mass).reduce(Measurement(value: 0, unit: .kilograms), +)
}
```

## Clarity

The top priority is how the call reads where it is used; every other choice
serves that reader.

- **Clarity beats brevity.** A longer name that removes doubt is better. Do not
  abbreviate. `remove(at: position)` says what `remove(position)` leaves open.
- **Include the words needed to avoid ambiguity.**
  `friends.remove(at: index)`, not `friends.remove(index)`.
- **Omit words that only repeat type information.**
  `allViews.remove(cancelButton)`, not `allViews.removeElement(cancelButton)`.
- **Name by role, not type.** `var greeting: String`, not `var string: String`;
  `track(_ subscriber: Subscriber, for topic: String)`, not
  `track(_ object: Subscriber, for string: String)`.
- **Compensate for weak types.** When a parameter is `Any`, `AnyObject`, `Int`
  or `String`, the type says little; add a role word, as in
  `addObserver(_:forKeyPath:)`, where `forKeyPath` explains the `String`.

Examples and terminology: [the naming reference](references/naming-and-clarity.md).

## Fluent call sites and protocols

Call sites should read as grammatical English: `insert(y, at: z)`,
`subviews.remove(at: i)`, `makeIterator()`. Not `insert(y, position: z)` or
`subviews.remove(i)`.

An initializer's first argument should not continue the type name as a
phrase: `Color(red:green:blue:)`, not `Color(havingRGBValuesRed:green:blue:)`.

Protocols:

- that say what something **is** are nouns: `Collection`, `IteratorProtocol`;
- that describe a **capability** end in `-able`, `-ible` or `-ing`:
  `Equatable`, `Hashable`, `Sendable`.

Generic parameters follow the role rule too: `Element`, `Key`, `Value`,
`Base` when the parameter has a meaning in the API; a single letter such as
`T` only when it has none. Details in
[the naming reference](references/naming-and-clarity.md).

## Conventions

- Types and protocols are `UpperCamelCase`; everything else is
  `lowerCamelCase`.
- Acronyms that are usually all capitals in American English are all upper or
  all lower case depending on position: `utf8Bytes`, `isRepresentableAsASCII`,
  `userSMTPServer`.
- Reach for a method or property before a free function. A free function fits
  in three cases only: nothing is naturally `self` (`max(a, b)`), the function is
  an unconstrained generic (`print(value)`), or the notation is established
  in the domain (`sin(x)`).
- Give one method defaulted parameters rather than writing several methods
  that vary only in the parameter list. Put defaulted parameters at the
  end, and give them labels: they are usually left out, so when present they
  must explain themselves.

  ```swift
  // One entry point
  func render(_ page: Page, scale: Double = 1, includesMargins: Bool = true) -> Image {
      PageRasterizer(scale: scale, margins: includesMargins).draw(page)
  }

  // Not a family such as render(_:), render(_:scale:) and
  // render(_:scale:includesMargins:)
  ```

- Two overloads can use one base name if they act on different kinds of types
  or the shared meaning is obvious. Never overload on return type alone.

Casing tables, complexity, free functions, overloads, tuples and closures:
[the conventions reference](references/conventions-and-special-rules.md).

## Common mistakes

1. Dropping a needed label: `friends.remove(i)` where `friends.remove(at: i)`
   is meant.
2. `-ed` where `-ing` is right: `stripped()` rather than `stripping()`. Test
   whether "a [verb]-ed [noun]" reads naturally.
3. A verb for a side-effect-free operation: a non-mutating `sort()` should be
   `sorted()`.
4. Naming by type: `string` for `greeting`, `array` for `elements`.
5. No doc comment, or a summary that describes the implementation instead of
   the purpose.
6. An expensive computed property with no complexity note, so callers use it in
   a loop.
7. `form` on a verb operation (`formSort()`); `form` is for noun operations
   only.
8. Factories without `make`: `createIterator()`, `buildBuffer()`.
9. Repeating type information: `removeElement(cancelButton)`,
   `stringValue: String`.
10. Overloads that differ only by return type, which confuse inference.
11. Unlabelled tuple members or closure parameters in public API, forcing
    `.0` and `.1`.

## Review checklist

Argument labels

- [ ] The first argument follows the right rule: grammatical phrase,
      prepositional, conversion, or labelled
- [ ] Prepositional labels do not tie together arguments that are independent
- [ ] Lossless conversion initializers take an unlabelled first argument
- [ ] Every argument not covered by a special rule has a label

Naming

- [ ] Mutating operations are imperative verbs
- [ ] Non-mutating operations use `-ed`, `-ing` or a noun
- [ ] Pairs follow the verb pattern or the noun / `form`-noun pattern
- [ ] Boolean names state a fact: `isEmpty`, `isValid`, `contains`
- [ ] Names describe roles, not types

Documentation

- [ ] Every public declaration is documented
- [ ] Each summary is one sentence fragment with a closing period
- [ ] The summary fits the declaration: action, access, creation or entity
- [ ] Non-O(1) computed properties state their complexity
- [ ] Symbol markup covers parameters, the return value and thrown errors

Conventions

- [ ] `UpperCamelCase` for types and protocols, `lowerCamelCase` for the rest
- [ ] Acronyms are cased uniformly by position
- [ ] Defaulted parameters instead of method families
- [ ] No overloads that differ only by return type
- [ ] Protocol names are nouns (what it is) or carry a capability suffix

## References

- [Argument labels and parameters](references/argument-labels-and-parameters.md):
  prepositional edge cases, grammatical-phrase examples, conversion kinds,
  parameter names for documentation, default arguments and ordering.
- [Naming and clarity](references/naming-and-clarity.md): needed and needless
  words, role naming, weak types, method versus property, generic parameter
  names, terminology.
- [Side effects and mutating pairs](references/side-effects-and-mutating-pairs.md):
  in-place and copying examples, how to pick `-ed` or `-ing`, the `form`
  prefix, Booleans, `make` factories.
- [Conventions and special rules](references/conventions-and-special-rules.md):
  casing and acronyms, complexity notes, free-function exceptions, overload
  safety, tuple and closure naming, unconstrained polymorphism.
