Swift
Programming
Arrays
Nil Elements
Code Optimization

How can I remove all nil elements in a Swift array?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Arrays containing optional values are common in Swift whenever data may be missing. The most idiomatic way to remove all nil elements is compactMap, which unwraps valid values and drops missing ones in one pass. Choosing the right pattern matters for readability, correctness, and performance in large transformations.

Use compactMap as the Default Choice

For an array like [Int?], compactMap is concise and clear.

swift
let input: [Int?] = [1, nil, 3, nil, 5]
let output: [Int] = input.compactMap { $0 }
print(output) // [1, 3, 5]

This code communicates intent directly. You want non-nil values only, and that is exactly what compactMap means.

compactMap also handles parse and transform steps while filtering invalid values:

swift
let raw = ["42", "x", "17", ""]
let numbers = raw.compactMap { Int($0) }
print(numbers) // [42, 17]

One operation, one clear meaning.

Why filter Plus Force Unwrap Is Usually Worse

You can remove nil values with filter followed by map, but it is less expressive and can become unsafe during refactors.

swift
let input: [Int?] = [1, nil, 2]
let output = input.filter { $0 != nil }.map { $0! }
print(output)

This version adds force unwrap and two passes in source code. Even if it works now, future edits can introduce accidental crashes. Prefer compactMap unless you need special branching behavior.

Mutating Existing Variables Cleanly

When you have a mutable variable and want to clean it, assign a non optional result to a new variable with explicit type.

swift
var maybeNames: [String?] = ["Ana", nil, "Ben", nil]
let names: [String] = maybeNames.compactMap { $0 }
print(names) // ["Ana", "Ben"]

Keeping the cleaned result in a separate constant avoids confusion between [String?] and [String]. If you truly need in-place replacement, update the variable type at declaration time to match the final representation.

Working with Nested Optionals

Data pipelines can produce nested optional layers such as [Int??]. A single compactMap removes one layer only.

swift
1let nested: [Int??] = [.some(.some(1)), .some(nil), nil, .some(.some(4))]
2let oneLayer: [Int?] = nested.compactMap { $0 }
3let flattened: [Int] = oneLayer.compactMap { $0 }
4print(flattened) // [1, 4]

Be explicit about the level of unwrapping you expect, especially when types come from generic utilities.

Sequence and Lazy Variants for Large Data

For very large collections, lazy processing can reduce intermediate allocations.

swift
1let big: [Int?] = Array(repeating: 1, count: 200_000) + [nil]
2let lazyClean = big.lazy.compactMap { $0 }
3
4let firstTen = Array(lazyClean.prefix(10))
5print(firstTen)

Use lazy chains only when profiling shows a real benefit. In normal app code, the plain array result is usually clearer.

Reusable Helper for Domain Specific Cleanup

If your code repeatedly removes nil and enforces additional rules, create a focused helper.

swift
1func nonEmptyStrings(from values: [String?]) -> [String] {
2    values.compactMap { value in
3        guard let v = value?.trimmingCharacters(in: .whitespacesAndNewlines) else {
4            return nil
5        }
6        return v.isEmpty ? nil : v
7    }
8}
9
10let cleaned = nonEmptyStrings(from: [" Alice ", nil, "", "Bob"])
11print(cleaned) // ["Alice", "Bob"]

This keeps call sites concise while preserving a clear data contract.

Testing Optional Cleanup Logic

Where cleanup affects product behavior, write focused tests:

  • all values missing returns empty array
  • order of surviving elements is unchanged
  • invalid parse values are dropped as expected
  • whitespace rules are applied consistently

Example using XCTest:

swift
1import XCTest
2
3final class CompactMapTests: XCTestCase {
4    func testRemovesNilAndPreservesOrder() {
5        let input: [Int?] = [3, nil, 1, nil, 2]
6        XCTAssertEqual(input.compactMap { $0 }, [3, 1, 2])
7    }
8}

Small tests prevent subtle data issues when transformation code evolves.

Common Pitfalls

Using map instead of compactMap keeps optional elements and can break downstream code that expects non optional values.

Force unwrap patterns are another common risk. They often survive initial reviews and fail later when assumptions change.

A third issue is type ambiguity in chained transformations. Add explicit type annotations when compiler inference becomes unclear.

Summary

  • 'compactMap is the idiomatic Swift approach for removing nil elements.'
  • It combines filtering and unwrapping in one clear operation.
  • Avoid filter plus force unwrap except for narrow, justified cases.
  • Handle nested optionals intentionally, one layer at a time.
  • Add focused tests when cleanup logic influences business behavior.

Course illustration
Course illustration

All Rights Reserved.