Posted in

How to filter a list in Go?

If you’ve ever spent time sifting through messy, unstructured data to pull out exactly what you need, you know how critical a good filtering system is. For someone like me—who’s spent a decade running a company that designs and manufactures industrial filtration systems for manufacturing and processing plants—filtering isn’t just a tech buzzword. It’s a habit I apply to everything, from sorting inventory in our warehouse to debugging Go code that powers our internal inventory and sales tracking tools. Filter

A few years back, when our engineering team was rebuilding our in-house system to track filter shipments, material stock, and client orders, we settled on Go (Golang) for its speed, simplicity, and ability to handle concurrent tasks—perfect for keeping up with hundreds of daily orders and parts requests. Early on, though, we ran into a mess: we had lists of filter types, order statuses, and part numbers, and our initial filtering code was clunky, slow, and hard to maintain. That experience taught me that filtering in Go isn’t just about writing a function—it’s about choosing the right approach for what you’re filtering, whether that’s a list of industrial filter elements or a slice of structs in your code.

Let’s walk through what we learned, using real examples from our codebase, and tie it all back to how we apply this same logic to the filters we ship to clients every day.

First, Know What You’re Filtering (And Why It Matters)

Before you write a line of code, stop and ask: what’s in the list I’m working with? For our code, most of the lists we filter are slices of structs. For example, we have a FilterPart struct that looks like this (simplified for clarity):

type FilterPart struct {
    ID          string  // Unique part number, e.g., "HEPA-0042"
    Type        string  // Filter type: "HEPA", "Activated Carbon", "Pleated"
    Application string  // Where it’s used: "HVAC", "Oil Processing", "Water Treatment"
    InStock     bool    // Whether we have it in our warehouse
    Quantity    int     // Number of units available
}

When we need to pull a list of HEPA filters for an HVAC client, or parts that are in stock for same-day shipping, we’re filtering slices of these structs. The same way, when a client comes to us with a request for a filter that works for their water treatment system, we’re filtering our product catalog to find exactly what fits.

The key here: if you try to force a one-size-fits-all filter approach, you’ll end up with code that’s hard to read or filters data incorrectly. For small lists, a simple function works great; for larger datasets, you’ll want something that’s efficient and parallelizable.

The Basic Filter: Simple Slice Iteration (For Small Lists)

When our system was new, our first filter function for FilterPart looked like this. We needed to get all in-stock HEPA filters for HVAC applications:

func FilterInStockHepaForHVAC(parts []FilterPart) []FilterPart {
    var filtered []FilterPart
    for _, part := range parts {
        if part.Type == "HEPA" && part.Application == "HVAC" && part.InStock {
            filtered = append(filtered, part)
        }
    }
    return filtered
}

This works for small lists—say, a slice of 50 or 100 parts. We used this for our internal team’s quick searches, since it’s straightforward, easy to write, and doesn’t require any extra libraries. Think of this like checking a box of loose filter elements to pull out the ones that fit a specific spec: you go one by one, pick out the right ones, and put them in a new pile.

But we quickly hit a problem: when we needed to filter for other criteria—like out-of-stock activated carbon filters for oil processing—we’d have to write almost identical functions, just changing the condition. That’s redundant, and when you’re managing 10,000+ parts, it’s easy to make a typo (like mixing up "Oil" and "oil" in the condition, which would leave out valid parts).

This is the lesson here for basic iteration: use it for small, infrequent filters, but don’t rely on it for everything. If you find yourself writing the same loop over and over, it’s time to abstract the logic.

The Flexible Filter: Reusable Generic Functions (For Go 1.18+)

Go 1.18 introduced generics, which changed the game for filtering. Generics let you write a single filter function that works with any data type, not just FilterPart. That meant we could stop writing custom functions for every filter criteria and build a reusable tool that works for our parts structs, our order slices, even our inventory logs.

Here’s a generic filter function we built, which accepts any slice and a condition function that defines what to keep:

func Filter[T any](list []T, condition func(T) bool) []T {
    var filtered []T
    for _, item := range list {
        if condition(item) {
            filtered = append(filtered, item)
        }
    }
    return filtered
}

That’s it. Now, to get our in-stock HEPA HVAC filters, we just pass the list of parts and a condition function:

parts := []FilterPart{/* our full list of parts */}
filteredParts := Filter(parts, func(p FilterPart) bool {
    return p.Type == "HEPA" && p.Application == "HVAC" && p.InStock
})

And if we need to pull out activated carbon filters for water treatment that are low in stock (less than 5 units), it’s just as easy:

lowStockCarbonWater := Filter(parts, func(p FilterPart) bool {
    return p.Type == "Activated Carbon" && p.Application == "Water Treatment" && p.Quantity < 5
})

This is exactly how we run our product catalog searches now. The same way we can adapt a single filtering process to sort through different filter materials for different clients, this generic function adapts to any data we need to filter. It’s clean, reduces code duplication, and is easy to update—if we need to add a new criteria (like a Material field or a LeadTime), we just adjust the condition function, not the filter itself.

One thing to note: when we first tested this, we worried about performance for large slices. But for our use case—filtering slices up to 10,000 items—this generic function is just as fast as writing a custom loop. It’s not the fastest approach for massive datasets (like 100k+ items), but for most business applications, it’s perfect.

For Large Datasets: Parallel Filtering (When Speed Matters)

Last year, when our inventory grew to over 50,000 parts, we noticed that filtering the entire list was starting to take a few seconds—too slow for our team when they’re pulling parts for time-sensitive orders, like emergency filter replacements for a hospital’s HVAC system. That’s when we added parallel filtering to our toolkit, leveraging Go’s built-in concurrency to split the work across multiple goroutines.

Parallel filtering works by splitting the original slice into chunks, filtering each chunk at the same time, then combining the results. Here’s a simplified version of the function we use:

func ParallelFilter[T any](list []T, condition func(T) bool, chunkSize int) []T {
    // If the list is small, just use the basic filter to avoid overhead
    if len(list) <= chunkSize {
        return Filter(list, condition)
    }

    // Split the list into chunks
    chunks := make([][]T, 0, (len(list)+chunkSize-1)/chunkSize)
    for i := 0; i < len(list); i += chunkSize {
        end := i + chunkSize
        if end > len(list) {
            end = len(list)
        }
        chunks = append(chunks, list[i:end])
    }

    // Filter each chunk in parallel
    var results [][]T
    sem := make(chan struct{}, runtime.NumCPU()) // Limit to number of CPU cores to avoid overloading
    var wg sync.WaitGroup

    for _, chunk := range chunks {
        sem <- struct{}{}
        wg.Add(1)
        go func(c []T) {
            defer wg.Done()
            chunkResult := Filter(c, condition)
            results = append(results, chunkResult)
            <-sem
        }(chunk)
    }

    wg.Wait()

    // Combine all results into a single slice
    var filtered []T
    for _, res := range results {
        filtered = append(filtered, res...)
    }

    return filtered
}

We set the chunk size to 1,000 and limit goroutines to the number of CPU cores on our servers, so we don’t use more memory than necessary. The result? Filtering 50,000 parts now takes less than 200 milliseconds—fast enough for our team to get parts to clients same-day, even for emergency requests.

This is similar to how we handle large orders for our industrial clients: we split the order picking list across multiple warehouse teams (the goroutines), each picking a portion, then combine the picks to get the order out fast. The logic applies to both software and our core business—working smarter, not harder, when dealing with large sets of data or inventory.

Common Pitfalls We Avoided (The Hard Way)

Over the years, we’ve messed up a few filtering approaches, so let’s share what we learned so you don’t make the same mistakes.

First, ignoring data type consistency. Early on, we had a filter condition that checked p.Type == "hepa" (lowercase) instead of "HEPA" (uppercase), and we missed 20% of our HEPA filters. That’s like a warehouse worker writing down "hepa" on a pick list instead of the part code, and ending up with the wrong filters for a client. Always make sure your conditions match the data exactly—add logging if you’re filtering strings to catch typos or formatting differences.

Second, using filtering for operations that don’t need it. Don’t filter a list if you just need a single item—use Find() instead, which stops iterating as soon as it finds a match. For example, if you need the ID for a specific filter part, there’s no reason to iterate through the entire list and keep all matching items. It’s the difference between picking one specific filter element from a shelf versus dumping the whole box on the table to sift through.

Third, over-optimizing too early. We spent months trying to make our parallel filter even faster, only to realize that 90% of our filters are on small lists, and the generic function works perfectly. Don’t add concurrency until you actually need it—premature optimization leads to messy, hard-to-debug code, just like buying a high-end industrial filter for a small office that only needs a basic air filter.

How This Ties Back to Who We Are

At the end of the day, filtering in Go is just like designing and manufacturing the filters we sell: it’s about precision, flexibility, and meeting the exact needs of the user. When we build a HEPA filter for a hospital, it’s not a one-size-fits-all part—we design it to fit their specific HVAC system, meet their air quality standards, and work reliably even during peak demand. When we build a filter function in Go, it’s not just code—it’s built to fit our data, adapt as our business grows, and work when we need it most.

If you’re someone who needs reliable, high-performance filters for your operations, whether that’s industrial filtration systems, water treatment components, or air quality solutions, we’ve got you covered. Our team works closely with clients to customize filters for their specific needs, just like how we customize our Go filtering functions for our internal tools.

Filtration Equipment If you’re looking to upgrade your filtration systems or need reliable parts for your projects, we’d love to chat. Reach out to our team to discuss your requirements, and we’ll help you find the right solutions for your business.

References

  1. Go 1.18 Release Notes: Generics. The Go Programming Language, 2022.
  2. Donovan, A. A. A., & Kernighan, B. W. The Go Programming Language. Addison-Wesley, 2015.
  3. Go sync Package Documentation: Goroutines and Channels. The Go Programming Language, n.d.
  4. Effective Go: Common Mistakes and Best Practices. The Go Programming Language, n.d.

Xinxiang Deshengwei Filtration and Purification Equipment Co., Ltd.
As one of the most experienced filter suppliers in China, we also support customized service. We warmly welcome you to buy bulk high quality filter made in China here from our factory. If you have any enquiry about cooperation, please feel free to email us.
Address: No.5 Daoqing Road, Hongqi District, Xinxiang City, Henan Province, China.
E-mail: rushufeng@dswgl.com
WebSite: https://www.dswfiltration.com/