Skip to content

Introduction to Synchronous Programming

Before we learn async and await, we must understand how normal C# code runs. Normal code is synchronous.

Synchronous programming means the program does one task at a time, in order. A task must finish completely before the next task can start. If one task takes a long time, everything after it has to wait.

Think of a single ticket counter at a railway station. Only one person is served at a time. The second person waits until the first person gets the ticket, the third waits for the second, and so on.

A real-life example: making breakfast

Suppose you make breakfast in a synchronous way:

  1. Put water on the stove and stand there for 3 minutes until it boils.
  2. Only then put bread in the toaster and stand there for 2 minutes.
  3. Only then pour a glass of juice (1 minute).

Total time = 3 + 2 + 1 = 6 minutes. While the water was boiling you did nothing else, even though you could have started the toast.

Synchronous code in C#

Here is the same breakfast written as a C# console program. Thread.Sleep pretends that each step takes some time (we use seconds instead of minutes).

using System;
using System.Diagnostics;
using System.Threading;

class Program
{
    static void Main()
    {
        Stopwatch watch = Stopwatch.StartNew();

        BoilWater();
        MakeToast();
        PourJuice();

        Console.WriteLine($"Breakfast ready in {watch.Elapsed.TotalSeconds:F0} seconds");
    }

    static void BoilWater()
    {
        Console.WriteLine("Boiling water started...");
        Thread.Sleep(3000);   // wait 3 seconds
        Console.WriteLine("Water is boiled");
    }

    static void MakeToast()
    {
        Console.WriteLine("Toasting bread started...");
        Thread.Sleep(2000);   // wait 2 seconds
        Console.WriteLine("Toast is ready");
    }

    static void PourJuice()
    {
        Console.WriteLine("Pouring juice...");
        Thread.Sleep(1000);   // wait 1 second
        Console.WriteLine("Juice is ready");
    }
}
Output
Boiling water started...
Water is boiled
Toasting bread started...
Toast is ready
Pouring juice...
Juice is ready
Breakfast ready in 6 seconds

How the code runs step by step

TimeWhat the program is doing
0 to 3 secondsInside BoilWater(), waiting. Nothing else can run.
3 to 5 secondsInside MakeToast(), waiting.
5 to 6 secondsInside PourJuice(), waiting.
6 secondsPrints the total time.

Notice three things:

  • The output order is always the same. This makes synchronous code easy to read and easy to debug.
  • Each method is called, runs to the end, and only then returns to Main.
  • While a method is waiting, the thread that runs it is stuck. It cannot do any other work.

Where does the waiting come from in real applications?

In real projects we do not call Thread.Sleep. The waiting comes from work that happens outside your program:

  • Reading or writing a file on the disk
  • Running a query on SQL Server
  • Calling a Web API or downloading a file from the internet
  • Sending an email

For example, this synchronous code waits until the whole file is read:

string text = File.ReadAllText(@"C:\Data\orders.txt");   // the program waits here
Console.WriteLine($"File has {text.Length} characters");

During this wait the CPU is doing almost nothing for your program. It is only waiting for the disk (or the database, or the network) to reply.

The problem with synchronous code

  • Wasted time: independent tasks (like toast and water) cannot overlap, so the total time is the sum of all tasks.
  • Frozen screens: in a desktop app (WinForms, WPF), if the UI thread waits for 5 seconds, the window stops responding. You cannot click, move or even repaint it.
  • Poor scalability on servers: in an ASP.NET Core Web API, each request needs a thread. If threads sit waiting for the database, the server runs out of free threads when many users arrive at the same time.

When synchronous code is perfectly fine

Synchronous is not "bad". It is the right choice when:

  • The work is quick, for example adding numbers, sorting a small list or formatting a string.
  • The work is pure calculation (CPU work) with no waiting for disk, network or database.
  • You are writing a small script or console tool where nobody else is waiting.

For everything that waits on something outside the program, C# gives us a better way: asynchronous programming, which is the next lesson.

Summary

  • Synchronous programming runs tasks one by one, in order.
  • The next line runs only after the current line has fully finished.
  • It is simple to write, read and debug.
  • While waiting for files, databases or APIs, the thread is stuck and does nothing useful.
  • This causes frozen user interfaces and servers that cannot handle many users.
  • Use synchronous code for quick or pure-calculation work; use asynchronous code for waiting work.