Greetings, traveler!
The Flyweight pattern is a structural design pattern that helps reduce memory usage by sharing common data between multiple objects.
It is useful in cases where you need to create a large number of similar objects, and storing the same internal data repeatedly would be wasteful.
The key idea
Flyweight splits state into two parts:
- Intrinsic state — the data that is shared and can be reused (stored inside the flyweight)
- Extrinsic state — the data that changes per usage and should be provided from the outside (not stored inside the flyweight)
In practice, Flyweight is usually implemented with a factory (or manager) that caches flyweight objects and reuses them when possible.
Implementation Example
Let’s say we’re building a game store. A store can handle thousands of purchases, but the number of unique games in the catalog is limited.
For example, Doom might be purchased hundreds of times. Storing the same game metadata for every purchase would repeat identical data again and again.
Instead, we’ll store shared game data as a Flyweight and keep purchase-specific details separately.
Flyweight
enum GameID: String {
case doom = "Doom"
case elderScrolls = "The Elder Scrolls"
case baldursGate = "Baldur's Gate"
}This enum represents our shared configuration (intrinsic state).
Now let’s define the flyweight itself:
final class GameCatalogEntryFlyweight {
let id: GameID
init(id: GameID) {
self.id = id
}
}Factory
The factory returns cached flyweights.
protocol GameCatalogEntryFactory {
func entry(for id: GameID) -> GameCatalogEntryFlyweight
}
final class DefaultGameCatalogEntryFactory: GameCatalogEntryFactory {
private var cache: [GameID: GameCatalogEntryFlyweight] = [:]
func entry(for id: GameID) -> GameCatalogEntryFlyweight {
if let cached = cache[id] {
return cached
}
let flyweight = GameCatalogEntryFlyweight(id: id)
cache[id] = flyweight
return flyweight
}
}Note: this cache will only grow. That’s fine for a small, controlled set of keys (like GameID), but in real apps you may want an eviction strategy. NSCache can be a good fit.
Extrinsic State
A deal contains a lot of data that should not be stored inside the flyweight.
struct PurchaseContext {
let id: Int
let price: Double
let customerName: String
}Storefront (Purchases storage)
Now let’s connect purchases with flyweights:
final class Storefront {
private let factory: GameCatalogEntryFactory
private var purchases: [Int: (game: GameCatalogEntryFlyweight, context: PurchaseContext)] = [:]
init(factory: GameCatalogEntryFactory) {
self.factory = factory
}
func purchase(game id: GameID, context: PurchaseContext) {
let game = factory.entry(for: id)
purchases[context.id] = (game: game, context: context)
}
func report() {
for (_, purchase) in purchases.sorted(by: { $0.key < $1.key }) {
let title = purchase.game.id.rawValue
let price = purchase.context.price
let customer = purchase.context.customerName
print("Purchase \(purchase.context.id): \(title), customer: \(customer), price: \(price)")
}
}
}Usage
let factory = DefaultGameCatalogEntryFactory()
let store = Storefront(factory: factory)
store.purchase(
game: .doom,
context: .init(id: 1, price: 19.99, customerName: "John")
)
store.purchase(
game: .doom,
context: .init(id: 2, price: 14.99, customerName: "Alice")
)
store.purchase(
game: .baldursGate,
context: .init(id: 3, price: 59.99, customerName: "Kate")
)
store.report()
Even though we created three purchases, we only keep two flyweight instances (.doom and .baldursGate). The rest of the data stays outside and is stored per purchase.
In this example, a game “flyweight” represents only the shared part — the catalog entry (Doom, The Elder Scrolls, Baldur’s Gate, etc.). Many different purchases can reference the same flyweight instance, because the game itself doesn’t change between transactions. All purchase-specific data, such as the order id, customer name, and price, is stored separately as external context. This way the app can process thousands of purchases without allocating thousands of identical game metadata objects.
Conclusion
Flyweight is a practical pattern when you deal with a large number of objects and you can clearly separate reusable internal data from per-instance context.
Most of the time, the implementation comes down to:
- defining the intrinsic part (the flyweight)
- caching it in a factory
- passing extrinsic state from the outside
This pattern isn’t needed in everyday code, but when memory usage starts to matter and the number of objects explodes, Flyweight can be a clean and reliable option.
This concludes the discussion of this pattern. The following article will discuss another structural design pattern, the Proxy.
Check out other posts in the Design Patterns series:
- Visitor Design Pattern in Swift
- Template Method Design Pattern in Swift
- Strategy Design Pattern in Swift
- State Design Pattern in Swift
- Observer Design Pattern in Swift
- Memento Design Pattern in Swift
- Mediator Design Pattern in Swift
- Iterator Design Pattern in Swift
- Command Design Pattern in Swift
- Chain of Responsibility Design Pattern in Swift
- Proxy Design Pattern in Swift
- Facade Design Pattern in Swift
- Decorator Design Pattern in Swift
- Composite Design Pattern in Swift
- Bridge Design Pattern in Swift
- Adapter Design Pattern in Swift
- Singleton Design Pattern in Swift
- Prototype Design Pattern in Swift
- Builder Design Pattern in Swift
- Abstract Factory Design Pattern in Swift
- Factory Method Design Pattern in Swift
- Design Patterns: Basics
