C# LINQ SkipWhile: Syntax and Behavior
C# LINQ SkipWhile skips leading elements until a predicate first returns false, then returns the rest. Learn its syntax, indexed overload, and common pitfalls.
The C# LINQ SkipWhile operator skips leading elements while a predicate returns true, then returns the rest, starting with the first element for which the predicate returns false. This article explains its syntax, its indexed overload, and the behavior that distinguishes it from Where and Skip.
What SkipWhile Does
The SkipWhile operator in C# LINQ removes elements from the start of a sequence while a predicate returns true. Once the predicate returns false for an element, that element and everything after it are returned unchanged. In LINQ to Objects, the predicate is not evaluated again after the first failure.
int[] numbers = { 1, 3, 5, 2, 4, 6 }; IEnumerable<int> result = numbers.SkipWhile(n => n % 2 != 0); Console.WriteLine(string.Join(", ", result)); // Output: 2, 4, 6
The first three elements are odd, so n % 2 != 0 keeps returning true and those elements are skipped. When the predicate reaches 2, it returns false, and SkipWhile returns 2 and everything after it.
The single-argument overload takes a Func<T, bool>. A second overload also provides the element's index, which is useful when the decision depends on position.
The Stopping Rule: Why SkipWhile Is Not Where
A common mistake is confusing SkipWhile with Where. Both take a predicate, but they behave differently:
Whereevaluates the predicate for every element and keeps the elements that match.SkipWhileevaluates the predicate only from the start of the sequence and stops at the first failure.
Consider:
int[] numbers = { 1, 3, 5, 2, 4, 6, 7 }; var skipped = numbers.SkipWhile(n => n % 2 != 0); var filtered = numbers.Where(n => n % 2 != 0);
skipped is 2, 4, 6, 7. The trailing 7 is odd, but SkipWhile already stopped evaluating at 2, so the 7 is included.
filtered is 1, 3, 5, 7. Where checked every element and kept only the odd ones.
The stopping rule is the defining behavior of SkipWhile. If you need to remove all elements that match a condition regardless of position, use Where. If you need to remove only a leading run of matching elements, use SkipWhile.
SkipWhile vs Skip
Skip and SkipWhile both remove elements from the start of a sequence, but they use different criteria:
| Operator | Criterion | Example |
|---|---|---|
Skip(n) | Fixed count | numbers.Skip(3) removes the first 3 elements |
SkipWhile(predicate) | Condition | numbers.SkipWhile(n => n < 0) removes leading negatives |
Use Skip when you know how many elements to drop. Use SkipWhile when the number depends on the data and can only be determined at runtime.
A practical example is skipping a variable-length header:
string[] lines = { "BEGIN", "BEGIN", "DATA", "value1", "value2" }; var data = lines.SkipWhile(line => line == "BEGIN"); // data: DATA, value1, value2
Skip(2) would also work here, but only because the header length happens to be fixed. If the header length varies, SkipWhile adapts without knowing the count in advance.
The Indexed Overload
The second overload of SkipWhile passes the zero-based index of each element to the predicate:
public static IEnumerable<TSource> SkipWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
The index is the position of the element in the source sequence, not in the skipped portion. This overload is useful when the decision depends on position rather than value alone.
int[] values = { 5, 4, 3, 2, 1 }; var result = values.SkipWhile((value, index) => value > index); // index 0: 5 > 0 → true, skip // index 1: 4 > 1 → true, skip // index 2: 3 > 2 → true, skip // index 3: 2 > 3 → false, stop // result: 2, 1
A realistic use is skipping elements whose value exceeds a position-dependent threshold, such as discarding the first few readings in a series until the values stabilize relative to their position.
Deferred Execution and Runtime Behavior
SkipWhile uses deferred execution. The query does not run when you call SkipWhile; it runs when you enumerate the result. Each enumeration re-evaluates the predicate from the beginning of the source.
int[] numbers = { 1, 2, 3 }; var query = numbers.SkipWhile(n => n < 3); numbers[0] = 10; Console.WriteLine(string.Join(", ", query)); // Output: 10, 2, 3
Because the source array was modified before enumeration, the predicate sees 10 first, fails immediately, and the query returns the whole sequence. If you need a snapshot of the result, materialize it with ToList() or ToArray().
During a single enumeration, the predicate is evaluated once for each element until the first failure; it is not evaluated again after that point. For a finite sequence of length n, this means at most n predicate evaluations per enumeration.
Common Pitfalls
One recurring bug is assuming SkipWhile filters the whole sequence. As shown earlier, trailing elements that match the predicate are included because evaluation stops at the first failure.
Another pitfall is relying on side effects inside the predicate. The predicate may be called multiple times across enumerations, and the number of calls depends on where the first failure occurs. A predicate with side effects produces different behavior on each enumeration.
A third pitfall is using SkipWhile on an infinite sequence without a guaranteed failure point. If the predicate never returns false, enumerating the result never terminates. On a large finite sequence, the operator will evaluate the whole sequence and produce an empty result. Ensure there is a point where the condition fails, or bound the sequence with Take before applying SkipWhile.
Performance and Memory Considerations
SkipWhile itself is an iterator method. It allocates a small state machine when the query is enumerated, but it does not buffer the entire sequence. Elements are consumed one at a time from the source, and skipped elements are discarded without being stored.
The cost of SkipWhile is dominated by two factors:
- The cost of the predicate itself.
- The number of elements evaluated before the first failure.
If the predicate is expensive and the failing element is deep in the sequence, SkipWhile pays the predicate cost for every element up to that point. There is no way to skip elements without evaluating the predicate, because the operator cannot know where the run ends otherwise.
When the source is a List<T> or an array, SkipWhile iterates through the leading elements directly. When the source is itself a lazily computed sequence, SkipWhile composes with it, and each enumeration re-runs the entire chain. Materializing an intermediate result with ToList() can avoid repeated work when the same query is enumerated multiple times.
Practical Use Cases
SkipWhile fits scenarios where a sequence has a leading region that should be discarded based on a condition:
- Skipping log lines before a marker such as
"ERROR"or a timestamp threshold. - Skipping leading whitespace or comment lines in a parsed file.
- Skipping preamble bytes in a binary stream until a header signature appears.
- Skipping leading invalid measurements until the first valid reading.
In each case, the condition is data-dependent, and the number of skipped elements is unknown until the predicate fails. That is exactly the situation SkipWhile was designed for.