All articles
iOS7 min read

I Rewrote 500 Lines of Swift Code With Just 3 Functions

The map, filter, reduce patterns that Apple engineers use daily

K
Karan Pal
Author
Image Generated by AI
Image Generated by AI

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:

  1. Start with a collection
  2. Do some filtering
  3. Transform the data
  4. 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:

  1. Lazy evaluation for performance with large datasets
  2. Custom operators for domain-specific operations
  3. Result types for better error handling in chains
  4. 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! 🚀

● The newsletter

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