Static vs dynamic dispatch in Swift?

1.11.1 · L2 · Interview

Predict first

Without whole-module optimisation, which call is guaranteed to be a direct call?

final class Engine { func start() {} }
class Car { func drive() {} }

Engine().start()   // A
Car().drive()      // B
  • A only
  • B only
  • Both
  • Neither

Mental model. Static dispatch is a printed address on an envelope. Dynamic dispatch is calling directory assistance every time.

Static — the compiler knows the exact implementation: structs, enums, generics after specialization, final classes, and protocol extension methods that are not requirements. It can inline and optimize.

Dynamic — the implementation is chosen at runtime:

  • Class methods that can be overridden → vtable.
  • @objc / dynamic → message send.
  • Protocol methods through any P → witness table.

Dynamic dispatch is more flexible; static is faster and inlinable.

final rules out overrides, so A is a direct call. B goes through the class vtable unless whole-module optimisation proves no subclass exists.

Common traps

  • “Protocols are always dynamic.” A generic constrained to a protocol is specialized statically. Only the existential path uses the witness table.
  • “final is only a style preference.” It changes dispatch, and therefore optimization, at every call site.

They might follow up with

  • What is a vtable? A per-class table of method pointers; subclassing replaces entries, so lookup is dynamic.
  • How do you see what the compiler chose? Emit SIL and look for class_method, witness_method, or function_ref.

Related questions

  • 1.11.2 Why does final (and whole-module optimization) help performance?
  • 1.11.3 You added a method in a protocol extension and overrode it on a concrete type, but calling through any P does…
  • 1.4.6 What is an existential container? What is a witness table?

Why does final (and whole-module optimization) help performance? →