Starting a new thread in a foreach loop
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
You can start a new thread inside a foreach loop, but that does not make it a good default pattern. A raw thread per item creates overhead, complicates error handling, and scales badly when the collection grows. In most real C# code, Task, the thread pool, or Parallel.ForEach is the safer abstraction.
The Raw Thread Version
A direct implementation is possible:
This works, but notice what you now own yourself: creating each thread, tracking it, waiting for completion, and deciding what to do when something fails. That is a lot of management code for a very common problem.
Why the Loop Variable Still Deserves Attention
The localItem copy makes intent explicit. In modern C#, foreach capture semantics are much less error-prone than they used to be, but closures are still easy to misunderstand when code changes later. If somebody refactors the loop body, introduces mutable shared state, or swaps foreach for another loop shape, the explicit local keeps the behavior obvious.
The bigger issue is not closure capture. It is that a thread-per-item approach has poor cost characteristics. Threads consume memory for their stacks, require OS scheduling, and increase context switching. That cost can dominate the useful work if each iteration is small.
Prefer Tasks for General Concurrent Work
If each iteration is mostly asynchronous or short-lived, Task is a better default than Thread.
This gives you better composition and better exception flow. If one task fails, Task.WhenAll can surface that failure in a structured way. You also let the runtime reuse pooled threads instead of forcing a brand-new thread for each item.
Parallel.ForEach for CPU-Bound Work
When each item is independent and the work is CPU-bound, Parallel.ForEach is often a better fit than manually creating threads.
This still runs work concurrently, but it uses the thread pool and internal partitioning logic. That usually gives better throughput and less boilerplate. You can also cap parallelism when needed:
That control matters when the machine is already busy or when the loop body competes for a limited external resource.
When a New Thread Per Item Is Actually Reasonable
Manual Thread creation is mostly justified when you need thread-specific behavior rather than generic concurrency. Examples include a dedicated long-lived worker, custom apartment state for COM interop, or very specialized thread affinity requirements. Those are uncommon compared with ordinary item processing.
If the loop is handling network I/O, database calls, or file operations, raw threads are especially hard to justify. In those cases, asynchronous APIs usually scale better because they do not tie up a thread while the program is waiting on external work.
Common Pitfalls
The most common mistake is starting hundreds or thousands of threads because the collection is large. That often makes the program slower, not faster.
Another mistake is forgetting that multiple loop iterations may update the same data. Even if thread creation is correct, shared mutable state still needs synchronization with locks, ConcurrentDictionary, channels, or some other safe design.
A third problem is not waiting for completion. If the enclosing method exits before all threads finish, the overall operation may end in a half-complete state.
Summary
- Starting a raw thread in a
foreachloop is possible, but it is usually the wrong abstraction - A thread per item adds memory, scheduling, and lifecycle overhead
- '
Task.WhenAllis a better default for general concurrent or asynchronous work' - '
Parallel.ForEachis often the right tool for independent CPU-bound iterations' - Use manual
Threadcreation only when you need thread-level behavior that higher-level APIs cannot express
Related reading
- Starting multiple async/await functions at once and handling them separately
- STAThread and multithreading
- STAThread and multithreading
- Static method behavior in multi-threaded environment in java
- Startup.cs class is missing in .NET 6
- Static and Sealed class differences
- stdasync function running serially
- stdasync won't spawn a new thread when return value is not stored

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.