.NET
yield
await
flow of control
C#

How do yield and await implement flow of control in .NET?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

yield and await both pause and resume execution in C#, but they solve different control-flow problems. yield builds lazy iterators by turning a method into a state machine that produces sequence elements over time. await builds asynchronous continuations by turning a method into a state machine that resumes after awaited operations complete.

Understanding this distinction helps avoid misuse, such as trying to await in synchronous iterator methods or expecting yield to make I/O non-blocking.

Core Sections

1. yield for lazy iteration

csharp
1IEnumerable<int> CountTo(int n)
2{
3    for (int i = 1; i <= n; i++)
4    {
5        yield return i;
6    }
7}
8
9foreach (var x in CountTo(3))
10{
11    Console.WriteLine(x);
12}

Execution resumes at each yield return boundary when consumer requests next element.

2. await for asynchronous operations

csharp
1async Task<string> DownloadAsync(HttpClient client, string url)
2{
3    var content = await client.GetStringAsync(url);
4    return content;
5}

Execution returns to caller at await, then continues when awaited task completes.

3. State machine transformation

The compiler rewrites both constructs:

  • yield -> iterator object with MoveNext() state.
  • await -> async state machine with continuation callbacks.

This is why both features look high-level but compile into deterministic control flow.

4. Async streams combine both ideas

C# async streams use IAsyncEnumerable<T> and await foreach.

csharp
1async IAsyncEnumerable<int> GetNumbersAsync()
2{
3    for (int i = 0; i < 3; i++)
4    {
5        await Task.Delay(100);
6        yield return i;
7    }
8}

Here await handles asynchronous delay, and yield return produces streamed items.

5. Cancellation and error propagation

await propagates exceptions through tasks. Iterator exceptions appear when enumeration advances. For async streams, cancellation tokens are important to avoid runaway producers.

Common Pitfalls

  • Expecting yield to perform asynchronous I/O by itself.
  • Blocking on async methods (.Result/.Wait) and causing deadlocks.
  • Assuming iterator code runs immediately rather than on enumeration.
  • Mixing synchronous iterators with async-only operations incorrectly.
  • Ignoring cancellation in long-running async stream producers.

Summary

In .NET, yield controls deferred sequence generation, while await controls asynchronous continuation after non-blocking operations. Both are compiler-generated state machines with different contracts. Use yield for lazy data production, await for async workflows, and async streams when you need both together. Clear separation of these concepts leads to cleaner, safer control flow in C# applications.

A practical way to make this guidance durable is to convert it into a small runbook that includes prerequisites, expected environment versions, and a short verification sequence. Even strong teams lose time when troubleshooting steps live only in memory or chat history. A runbook should explicitly answer three questions: what to check first, what output confirms healthy behavior, and what output indicates a known failure mode. This level of clarity helps both experienced maintainers and newer contributors, and it reduces repeated investigation during incidents.

It is also valuable to create a tiny reproducible fixture for this topic. The fixture can be a minimal script, test case, sample request, or small dataset that demonstrates the correct behavior in isolation. When regressions appear after dependency upgrades, infrastructure changes, or framework migrations, that fixture becomes the fastest way to isolate whether the issue is environmental or logic-related. Keeping a focused fixture in source control gives you a stable benchmark across branches and release cycles.

For long-term reliability, pair documentation with one automated guardrail in CI. The guardrail should be narrow and fast: an import check, schema validation, endpoint contract test, deterministic unit test, or lightweight performance threshold. Avoid broad flaky checks that hide real signals. The goal is early, actionable feedback before code reaches production. If the same category of issue appears repeatedly, promote the manual troubleshooting step into automation so the system catches it first. Over time, this shifts effort from reactive debugging to preventive quality control and keeps the knowledge article relevant in real engineering workflows.


Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track 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.

Browse interview questions

All Rights Reserved.