"Concurrency" and "parallelism" get used as synonyms, even in code reviews and interviews. They're related, but they solve different problems.

  • Concurrency is about structuring a program to manage multiple tasks at once

how your program handles multiple tasks that overlap in time.

  • Parallelism is about actually running multiple tasks at the exact same physical instance

multiple computations literally running at the same instant on multiple CPU cores.

An Analogy

Imagine a single cook in a kitchen preparing three dishes.

While the pasta water heats up, the cook chops vegetables.

While the sauce simmers, the cook prepares the salad.

=> Only one person is working, but all three dishes are in progress at the same time, because the cook switches whenever one dish is waiting. That's concurrency.

Now hire two more cooks and give each one dish.

All three are actively chopping, stirring, and plating at the same instant.

That's parallelism.

The first kitchen is better at not wasting time on waiting. The second is better at getting more actual work done per second.

Concurrency in C#: async/await

Concurrency in .NET usually means I/O-bound work, where the program spends most of its time waiting on something external: a database, an HTTP call, a file, a message queue.

public async Task<DashboardData> LoadDashboardAsync(int userId)
{
    var profileTask  = userService.GetProfileAsync(userId);
    var ordersTask   = orderService.GetOrdersAsync(userId);
    var invoicesTask = billingService.GetInvoicesAsync(userId);

    await Task.WhenAll(profileTask, ordersTask, invoicesTask);

    return new DashboardData(await profileTask, await ordersTask, await invoicesTask);
}

All three requests are in flight together, so the total time is roughly the slowest call rather than the sum. But no extra CPU cores are doing computation here.

While the requests wait on the network, the calling thread is released back to the thread pool and can serve other work.

The key point: concurrency can happen on a single thread. A UI thread that stays responsive while awaiting a download is concurrent, even though only one thread exists.

Parallelism in C#: Parallel, and Task.Run

Parallelism applies to CPU-bound work, where the program is limited by computation. Waiting is not the problem.

var results = new double[data.Length];

Parallel.For(0, data.Length, i =>
{
    results[i] = HeavyComputation(data[i]);
});

Or with PLINQ:

var processed = data
    .AsParallel()
    .Select(HeavyComputation)
    .ToArray();

Side by Side

Concurrency

Parallelism

Core idea

Manage multiple tasks in progress

Execute multiple tasks simultaneously

Workload

I/O-bound (waiting)

task is limited by the time it spends waiting for I/O operations

CPU-bound (computing)

speed is limited by the power of the processor

Needs multiple cores?

No

Yes

Typical C# tools

async/await, Task.WhenAll, Channel<T>

Parallel.For/ForEach, PLINQ, Task.Run

They Can Combine

Real applications often use both. A service might fetch 50 files concurrently (I/O-bound, async/await), then process each file's contents in parallel (CPU-bound, Parallel.ForEachAsync or PLINQ):

var files = await DownloadAllAsync(urls);               // concurrency: overlapping waits

await Parallel.ForEachAsync(files,
    new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
    async (file, ct) =>
    {
        var parsed = ParseAndTransform(file);           // parallelism: CPU work across cores
        await SaveAsync(parsed, ct);
    });

How to Choose

Ask one question first: what is my code waiting on?

  • Waiting on a network call, database, disk, or timer? You need concurrency. Use async/await.

  • Waiting on the CPU to finish heavy computation? You need parallelism. Use Parallel, PLINQ, or Task.Run for offloading.

  • Both? Start with concurrency for the I/O, then parallelize the CPU stage.