I Rewrote 500 Lines of Swift Code With Just 3 Functions
The map, filter, reduce patterns that Apple engineers use daily

So there I was, staring at a method that had grown into a 500-line monster. You know the type — started innocent enough with “just a quick data processing function” but somehow evolved into this nested loop nightmare that made my eyes water just looking at it.
The method was supposed to process product data for an e-commerce app. Simple enough, right? Take a list of products, apply some filters, calculate prices with discounts, group by category, and return a nice clean data structure. But what started as a “should take 20 minutes” task had become this sprawling beast that nobody wanted to touch.
Then I discovered something that completely changed how I write Swift code. Three simple functions that turned my 500-line headache into 12 elegant lines. And the kicker? It actually runs faster than the original.
📺 Coming soon to Swift Pal: A complete video walkthrough of these functional programming patterns at _https://youtube.com/@swift-pal_
🎯 The 500-Line Monster
Let me show you what I’m talking about. Here’s a simplified version of the disaster I was dealing with:
func processProductData(_ rawProducts: [Product]) -> ProcessedProductData {
var result = ProcessedProductData()
var validProducts: [Product] = []
var discountedProducts: [DiscountedProduct] = []
var categorizedProducts: [String: [DiscountedProduct]] = [:]
var totalRevenue: Double = 0
var categoryTotals: [String: Double] = [:]
// First pass: Filter valid products
for product in rawProducts {
if product.isActive &&
product.price > 0 &&
!product.name.isEmpty &&
product.stock > 0 {
// Check if user has permission to view this category
if hasPermissionForCategory(product.category) {
validProducts.append(product)
}
}
}
// Second pass: Apply discounts and calculate prices
for product in validProducts {
var finalPrice = product.price
var discountApplied = 0.0
// Check for category discounts
if let categoryDiscount = getCategoryDiscount(product.category) {
if categoryDiscount.isActive &&
Date() >= categoryDiscount.startDate &&
Date() <= categoryDiscount.endDate {
discountApplied += categoryDiscount.percentage
}
}
// Check for quantity discounts
if product.stock > 100 {
discountApplied += 0.05 // 5% bulk discount
} else if product.stock > 50 {
discountApplied += 0.03 // 3% moderate bulk discount
}
// Check for seasonal discounts
let calendar = Calendar.current
let month = calendar.component(.month, from: Date())
if month == 12 { // December
discountApplied += 0.10 // 10% holiday discount
} else if month == 7 { // July
discountApplied += 0.07 // 7% summer sale
}
// Apply maximum discount cap
if discountApplied > 0.30 {
discountApplied = 0.30 // Max 30% discount
}
finalPrice = finalPrice * (1.0 - discountApplied)
let discountedProduct = DiscountedProduct(
id: product.id,
name: product.name,
originalPrice: product.price,
finalPrice: finalPrice,
discountPercentage: discountApplied,
category: product.category,
stock: product.stock,
isOnSale: discountApplied > 0
)
discountedProducts.append(discountedProduct)
totalRevenue += finalPrice * Double(product.stock)
}
// Third pass: Group by category
for product in discountedProducts {
if categorizedProducts[product.category] == nil {
categorizedProducts[product.category] = []
}
categorizedProducts[product.category]?.append(product)
}
// Fourth pass: Calculate category totals
for (category, products) in categorizedProducts {
var categoryTotal: Double = 0
for product in products {
categoryTotal += product.finalPrice * Double(product.stock)
}
categoryTotals[category] = categoryTotal
}
// Fifth pass: Sort categories by revenue
let sortedCategories = categoryTotals.sorted { $0.value > $1.value }
// Sixth pass: Create final sorted category structure
var finalCategorizedProducts: [String: [DiscountedProduct]] = [:]
for (category, _) in sortedCategories {
if let products = categorizedProducts[category] {
// Sort products within category by final price (descending)
let sortedProducts = products.sorted { $0.finalPrice > $1.finalPrice }
finalCategorizedProducts[category] = sortedProducts
}
}
// Calculate statistics
var averageDiscount: Double = 0
var productsOnSale = 0
for product in discountedProducts {
if product.isOnSale {
productsOnSale += 1
averageDiscount += product.discountPercentage
}
}
if productsOnSale > 0 {
averageDiscount = averageDiscount / Double(productsOnSale)
}
result.products = finalCategorizedProducts
result.totalRevenue = totalRevenue
result.categoryTotals = categoryTotals.sorted { $0.value > $1.value }
result.averageDiscount = averageDiscount
result.totalProducts = discountedProducts.count
result.productsOnSale = productsOnSale
return result
}Look at that mess! Six different passes through the data, nested conditions everywhere, and good luck trying to test individual pieces of this logic. I literally had to scroll to see the entire method.
And this isn’t even the full 500 lines — I’m showing you the “simplified” version. The real one had error handling, logging, caching logic, and about three more discount types.
🔍 The 3-Function Transformation
Now here’s where it gets interesting. What if I told you that exact same functionality — all of it — can be written like this:
func processProductData(_ rawProducts: [Product]) -> ProcessedProductData {
let processedProducts = rawProducts
.filter { isValidProduct($0) }
.map { applyDiscounts($0) }
.reduce(into: [String: [DiscountedProduct]]()) { result, product in
result[product.category, default: []].append(product)
}
.mapValues { $0.sorted { $0.finalPrice > $1.finalPrice } }
.sorted { calculateCategoryRevenue($0.value) > calculateCategoryRevenue($1.value) }
return ProcessedProductData(
products: Dictionary(processedProducts, uniquingKeysWith: { first, _ in first }),
totalRevenue: calculateTotalRevenue(from: processedProducts),
categoryTotals: processedProducts.map { ($0.key, calculateCategoryRevenue($0.value)) },
averageDiscount: calculateAverageDiscount(from: processedProducts),
totalProducts: processedProducts.reduce(0) { $0 + $1.value.count },
productsOnSale: processedProducts.flatMap(\.value).filter(\.isOnSale).count
)
}
// Supporting functions (clean and testable!)
private func isValidProduct(_ product: Product) -> Bool {
return product.isActive &&
product.price > 0 &&
!product.name.isEmpty &&
product.stock > 0 &&
hasPermissionForCategory(product.category)
}
private func applyDiscounts(_ product: Product) -> DiscountedProduct {
let discountPercentage = [
getCategoryDiscountPercentage(product.category),
getQuantityDiscountPercentage(product.stock),
getSeasonalDiscountPercentage()
].reduce(0, +).clamped(to: 0...0.30)
let finalPrice = product.price * (1.0 - discountPercentage)
return DiscountedProduct(
id: product.id,
name: product.name,
originalPrice: product.price,
finalPrice: finalPrice,
discountPercentage: discountPercentage,
category: product.category,
stock: product.stock,
isOnSale: discountPercentage > 0
)
}
private func calculateCategoryRevenue(_ products: [DiscountedProduct]) -> Double {
return products.reduce(0) { total, product in
total + (product.finalPrice * Double(product.stock))
}
}Same functionality. About 40 lines instead of 150+. And here’s the crazy part — it’s actually faster than the original because we’re not making multiple passes through the data.
But more importantly? Look how readable it is. You can see exactly what’s happening at each step without drowning in nested loops and temporary variables.
🗺️ Map: Transform Your Data Like a Pro
Let’s break down what’s actually happening here, starting with map. Think of map as your universal translator — it takes every element in a collection and transforms it into something else.
Here’s the thing most developers get wrong about map — they think it's just for simple transformations. But map is incredibly powerful when you use it right:
// Basic transformation (what most people do)
let prices = products.map { $0.price }
// Advanced transformation (what pros do)
struct ProductDisplayModel {
let title: String
let formattedPrice: String
let badgeText: String?
let imageURL: URL?
}
let displayModels = products.map { product in
ProductDisplayModel(
title: product.name.uppercased(),
formattedPrice: formatCurrency(product.price),
badgeText: product.isOnSale ? "SALE" : nil,
imageURL: URL(string: product.imageURLString)
)
}See the difference? Instead of just extracting a property, we’re creating entirely new objects optimized for display. This is where map really shines — data transformation that keeps your view controllers clean.
Real-World Map Patterns
Here are some patterns I use constantly in iOS apps:
// API response transformation
let users = apiResponse.map { json in
User(
id: json["id"] as? Int ?? 0,
name: json["full_name"] as? String ?? "Unknown",
email: json["email_address"] as? String,
profileImage: URL(string: json["avatar_url"] as? String ?? "")
)
}
// Enum case transformation
enum UserRole: String, CaseIterable {
case admin, moderator, user, guest
var displayName: String {
switch self {
case .admin: return "Administrator"
case .moderator: return "Moderator"
case .user: return "User"
case .guest: return "Guest"
}
}
}
let roleNames = UserRole.allCases.map(\.displayName)
// Result: ["Administrator", "Moderator", "User", "Guest"]
// Complex UI state transformation
let cellViewModels = notifications.map { notification in
NotificationCellViewModel(
title: notification.title,
subtitle: timeAgoString(from: notification.createdAt),
iconName: notification.type.iconName,
isUnread: !notification.isRead,
backgroundColor: notification.isRead ? .systemBackground : .systemBlue.withAlphaComponent(0.1)
)
}When NOT to Use Map
Here’s something important — map isn't always the right choice. Don't use map when:
// ❌ Don't do this (you're not using the result)
products.map { print($0.name) }
// ✅ Do this instead
products.forEach { print($0.name) }
// ❌ Don't do this (you want to filter, not transform)
let expensiveProducts = products.map { $0.price > 100 ? $0 : nil }.compactMap { $0 }
// ✅ Do this instead
let expensiveProducts = products.filter { $0.price > 100 }The rule of thumb: use map when you want to transform every element into something else. If you're not transforming, or if you want to exclude some elements, you probably want a different function.
🔽 Filter: Find What You Need
Now let’s talk about filter — the function that saves you from writing countless if statements inside loops. If map is your transformer, filter is your bouncer — it decides who gets in and who doesn't.
Most developers use filter for simple conditions, but there's so much more you can do:
// Basic filtering (beginner level)
let activeProducts = products.filter { $0.isActive }
// Advanced filtering (pro level)
let premiumAvailableProducts = products.filter { product in
return product.isActive &&
product.price >= 50.0 &&
product.stock > 0 &&
product.category != .restricted &&
hasUserPermission(for: product.category) &&
!product.isDiscontinued
}
// Dynamic filtering with closures
func createProductFilter(
minPrice: Double? = nil,
categories: [ProductCategory]? = nil,
inStock: Bool = true
) -> (Product) -> Bool {
return { product in
if let minPrice = minPrice, product.price < minPrice { return false }
if let categories = categories, !categories.contains(product.category) { return false }
if inStock && product.stock <= 0 { return false }
return product.isActive
}
}
// Usage: Now you can create reusable filters
let premiumElectronicsFilter = createProductFilter(
minPrice: 100.0,
categories: [.electronics, .computers]
)
let premiumElectronics = products.filter(premiumElectronicsFilter)Search Functionality Made Simple
Here’s where filter really proves its worth — implementing search functionality:
struct SearchManager {
static func searchProducts(
_ products: [Product],
query: String,
filters: SearchFilters = SearchFilters()
) -> [Product] {
let normalizedQuery = query.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
return products.filter { product in
// Text search across multiple fields
let matchesText = normalizedQuery.isEmpty ||
product.name.lowercased().contains(normalizedQuery) ||
product.description.lowercased().contains(normalizedQuery) ||
product.tags.contains { $0.lowercased().contains(normalizedQuery) }
// Apply additional filters
let matchesCategory = filters.selectedCategories.isEmpty ||
filters.selectedCategories.contains(product.category)
let matchesPriceRange = product.price >= filters.minPrice &&
product.price <= filters.maxPrice
let matchesAvailability = !filters.inStockOnly || product.stock > 0
return matchesText && matchesCategory && matchesPriceRange && matchesAvailability
}
}
}
// Usage is clean and readable
let searchResults = SearchManager.searchProducts(
allProducts,
query: "wireless headphones",
filters: SearchFilters(
selectedCategories: [.electronics],
minPrice: 50,
maxPrice: 300,
inStockOnly: true
)
)Performance Tips for Filter
Here’s something most developers don’t know — the order of conditions in filter matters for performance:
// ❌ Slow: expensive operations first
let results = products.filter { product in
return isUserEligibleForProduct(product) && // Expensive API call
product.isActive && // Cheap property check
product.stock > 0 // Cheap property check
}
// ✅ Fast: cheap operations first
let results = products.filter { product in
return product.isActive && // Cheap checks first
product.stock > 0 &&
isUserEligibleForProduct(product) // Expensive check last
}Swift’s filter uses short-circuiting, so if the first condition is false, it won't even evaluate the rest. Put your cheapest checks first!
⚡ Reduce: The Swiss Army Knife
Alright, here’s where things get really interesting. reduce is the function that confuses most developers, but once you get it, you'll see it's actually the most powerful of the three.
Most people think reduce is just for adding numbers:
// This is what everyone learns first
let total = prices.reduce(0, +)
let sum = numbers.reduce(0) { $0 + $1 }But reduce is so much more. It's basically a way to take a collection and "reduce" it down to a single value — but that value can be anything. A number, a string, an array, a dictionary, a custom object... anything.
Here’s where my mind was blown when I first understood this:
// Building a dictionary from an array
let productsByCategory = products.reduce(into: [String: [Product]]()) { result, product in
result[product.category, default: []].append(product)
}
// Finding min and max in one pass
let priceRange = products.reduce(into: (min: Double.infinity, max: -Double.infinity)) { result, product in
result.min = min(result.min, product.price)
result.max = max(result.max, product.price)
}
// Building a complex summary object
struct ProductSummary {
var totalProducts: Int = 0
var totalValue: Double = 0
var categoryCounts: [String: Int] = [:]
var averageRating: Double = 0
var topRatedProduct: Product?
}
let summary = products.reduce(into: ProductSummary()) { summary, product in
summary.totalProducts += 1
summary.totalValue += product.price * Double(product.stock)
summary.categoryCounts[product.category, default: 0] += 1
// Update average rating (running average)
summary.averageRating = ((summary.averageRating * Double(summary.totalProducts - 1)) + product.rating) / Double(summary.totalProducts)
// Track top-rated product
if summary.topRatedProduct == nil || product.rating > summary.topRatedProduct!.rating {
summary.topRatedProduct = product
}
}See what happened there? We processed the entire product list once and got a complete summary with totals, averages, groupings, and even found the top-rated product. That’s the power of reduce.
Advanced Reduce Patterns
Here are some patterns I use constantly in real apps:
// Creating a lookup dictionary for O(1) access
let userLookup = users.reduce(into: [Int: User]()) { lookup, user in
lookup[user.id] = user
}
// Now you can do userLookup[userId] instead of users.first { $0.id == userId }
// Flattening nested arrays
let allTags = products.reduce(into: Set<String>()) { allTags, product in
allTags.formUnion(product.tags)
}
// Building SQL-like GROUP BY functionality
struct SalesData {
let date: Date
let amount: Double
let region: String
}
let salesByRegion = salesData.reduce(into: [String: (count: Int, total: Double)]()) { result, sale in
let current = result[sale.region, default: (count: 0, total: 0.0)]
result[sale.region] = (count: current.count + 1, total: current.total + sale.amount)
}
// Creating a frequency counter
let wordFrequency = text.components(separatedBy: .whitespacesAndNewlines)
.reduce(into: [String: Int]()) { frequency, word in
let cleanWord = word.lowercased().trimmingCharacters(in: .punctuationCharacters)
frequency[cleanWord, default: 0] += 1
}Reduce vs Multiple Loops
This is the key insight that changed how I write code. Instead of multiple passes through your data:
// ❌ Multiple passes (inefficient)
let validProducts = products.filter { $0.isActive }
let totalRevenue = validProducts.reduce(0) { $0 + $1.price }
let categories = Set(validProducts.map { $0.category })
let averagePrice = totalRevenue / Double(validProducts.count)
// ✅ Single pass (efficient)
struct ProductAnalysis {
var validProducts: [Product] = []
var totalRevenue: Double = 0
var categories: Set<String> = []
var productCount: Int = 0
var averagePrice: Double {
return productCount > 0 ? totalRevenue / Double(productCount) : 0
}
}
let analysis = products.reduce(into: ProductAnalysis()) { analysis, product in
guard product.isActive else { return }
analysis.validProducts.append(product)
analysis.totalRevenue += product.price
analysis.categories.insert(product.category)
analysis.productCount += 1
}One loop. All the data you need. And it’s fast.
🔗 Chaining: Where the Magic Happens
Now here’s where everything comes together. The real power isn’t in using these functions individually — it’s in chaining them together to create elegant data processing pipelines.
Remember that nightmare 500-line method from the beginning? Here’s how the chaining actually works:
let processedData = rawProducts
.filter { product in
// Step 1: Keep only valid products
product.isActive &&
product.price > 0 &&
!product.name.isEmpty &&
product.stock > 0 &&
hasPermissionForCategory(product.category)
}
.map { product in
// Step 2: Transform each product with discount calculations
let discount = calculateTotalDiscount(for: product)
let finalPrice = product.price * (1.0 - discount)
return DiscountedProduct(
id: product.id,
name: product.name,
originalPrice: product.price,
finalPrice: finalPrice,
discountPercentage: discount,
category: product.category,
stock: product.stock,
isOnSale: discount > 0
)
}
.reduce(into: ProcessedProductData()) { result, product in
// Step 3: Aggregate everything into final structure
result.products[product.category, default: []].append(product)
result.totalRevenue += product.finalPrice * Double(product.stock)
if product.isOnSale {
result.saleProductCount += 1
result.totalDiscount += product.discountPercentage
}
result.categoryRevenue[product.category, default: 0] += product.finalPrice * Double(product.stock)
}Each step in the chain has a single responsibility. It’s like an assembly line where each station does one specific job really well.
🚀 Your Next Steps
Alright, so how do you actually start implementing this in your own code? Here’s my step-by-step process for refactoring existing code and writing new functional-style code.
The 3-Step Refactoring Process
Step 1: Identify the Data Flow
Look for methods that follow this pattern:
- Start with a collection
- Do some filtering
- Transform the data
- Aggregate or group results
// Before: Imperative mess
func analyzeUserActivity(_ users: [User]) -> ActivitySummary {
var activeUsers: [User] = []
var totalSessions = 0
var usersByCountry: [String: Int] = [:]
// Multiple loops doing different things
for user in users {
if user.lastLoginDate > Date().addingTimeInterval(-30 * 24 * 60 * 60) {
activeUsers.append(user)
}
}
for user in activeUsers {
totalSessions += user.sessionCount
}
for user in activeUsers {
usersByCountry[user.country, default: 0] += 1
}
return ActivitySummary(
activeUserCount: activeUsers.count,
totalSessions: totalSessions,
usersByCountry: usersByCountry
)
}Step 2: Extract the Logic into Small Functions
// Create focused, testable functions
private func isRecentlyActive(_ user: User) -> Bool {
let thirtyDaysAgo = Date().addingTimeInterval(-30 * 24 * 60 * 60)
return user.lastLoginDate > thirtyDaysAgo
}
private func createActivitySummary(from users: [User]) -> ActivitySummary {
let activeUsers = users.filter(isRecentlyActive)
let summary = activeUsers.reduce(into: ActivitySummary()) { result, user in
result.activeUserCount += 1
result.totalSessions += user.sessionCount
result.usersByCountry[user.country, default: 0] += 1
}
return summary
}Step 3: Chain It Together
// Final functional version
func analyzeUserActivity(_ users: [User]) -> ActivitySummary {
return users
.filter(isRecentlyActive)
.reduce(into: ActivitySummary()) { summary, user in
summary.activeUserCount += 1
summary.totalSessions += user.sessionCount
summary.usersByCountry[user.country, default: 0] += 1
}
}What to Learn Next
Once you’re comfortable with map, filter, and reduce, explore these advanced functional concepts:
- Lazy evaluation for performance with large datasets
- Custom operators for domain-specific operations
- Result types for better error handling in chains
- Combine framework for reactive programming (if you’re on iOS 13+)
But honestly? Just mastering these three functions will transform how you write Swift code. Start with simple transformations, get comfortable with chaining, and gradually work up to complex data processing pipelines.
The goal isn’t to use functional programming everywhere — it’s to have it in your toolkit for when it makes code cleaner and more maintainable
📺 Want to see these patterns in action with live coding examples? Check out the complete video tutorial on Swift Pal: _https://youtube.com/@swift-pal_
🎉 Enjoyed this article? Your support means the world to me!
💬 Drop a comment below! I love hearing about your experiences and answering questions
🎬 Subscribe on Youtube and become early subscribers of my channel: https://www.youtube.com/@swift-pal
💼 Let’s connect on LinkedIn for more professional insights: https://www.linkedin.com/in/karan-pal
Happy coding! 🚀
New articles, straight to your inbox.
No spam, no filler — just new writing on iOS, the web, and AI when it ships. Unsubscribe anytime.
Keep reading
Why I'm Rebuilding My Blog From Scratch (and Leaving Medium)
After years of publishing on someone else's platform, I'm moving my writing to a home I actually own. Here's the reasoning, and what I'm building instead.
ReadData Is the Model: The Most Ignored Part of AI
A beginner-friendly guide to why data quality beats model hype.
Read