C# Coding Exercises

Programming is learned by writing programs.

Concept

Programming is learned by writing programs. The exercises below are graded by level; each level has worked, verified solutions for a selection of the exercises. Try each one yourself before reading the solution.

Hello World · Calculator · Number checker (even/odd/positive) · Temperature converter · Grade calculator · Fibonacci sequence · Prime number checker · Palindrome checker · Array statistics · String manipulation

Bank account system · Employee management · Inventory manager · File analyzer (lines/words/largest files) · Contact manager · Library management system · Log parser · CSV processor

Multithreaded producer-consumer · Async HTTP client (Module 36) · Plugin system using reflection (Module 37) · Generic repository · Caching service · Event-driven application · REST API (Module 51) · Database-backed application (Modules 49–50) · Background processing service

High-performance telemetry pipeline · Distributed service (Module 56) · Diagnostic data processor (Module 61) · Custom source generator (Module 39) · Roslyn analyzer (Module 39) · High-throughput messaging system · Production-grade monitoring service (Modules 48, 59)

Why It Matters

Solving varied problems builds the fluency interviews and real projects demand: choosing the right collection, structuring classes, handling errors and concurrency without looking things up.

Syntax

Each solution is a complete file-based program: save it as Program.cs and run dotnet run Program.cs.

Example

63.1 Beginner solutions 63.2 Intermediate solutions: bank account, log parser and CSV processor

63.3 Advanced solutions: generic repository, caching service, producer-consumer, event-driven application

63.4 Expert solution: high-performance telemetry pipeline

// Beginner exercise solutions: temperature converter, grade, Fibonacci, primes, palindrome, array statistics, string manipulation
double[] celsius = [-40, 0, 37, 100];
Console.WriteLine("temperatures: " + string.Join(", ", celsius.Select(c => $"{c} C = {c * 9 / 5 + 32} F")));

foreach (int score in new[] { 95, 83, 71, 64, 42 }) Console.Write($"{score}->{Grade(score)} ");
Console.WriteLine();

Console.WriteLine($"Fibonacci(15): {string.Join(" ", Fibonacci().Take(15))}");
Console.WriteLine($"primes < 50: {string.Join(" ", Enumerable.Range(2, 48).Where(IsPrime))}");
foreach (string w in new[] { "level", "Never odd or even", "engine" }) Console.Write($"'{w}' palindrome: {IsPalindrome(w)}; ");
Console.WriteLine();

int[] data = [12, 7, 3, 21, 7, 15];
Console.WriteLine($"array stats: min {data.Min()}, max {data.Max()}, mean {data.Average():F2}, median {Median(data)}, mode {data.GroupBy(x => x).MaxBy(g => g.Count())!.Key}");

string sentence = "  the quick  brown fox  ";
Console.WriteLine($"words: {sentence.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length}, title case: {string.Join(" ", sentence.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(w => char.ToUpper(w[0]) + w[
…
using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

// ---- Bank account system: encapsulation, validation, exceptions, history ----
var account = new BankAccount("ACC-001", "Asha");
account.Deposit(500m);
account.Withdraw(120.50m);
try { account.Withdraw(1000m); } catch (InvalidOperationException ex) { Console.WriteLine($"rejected: {ex.Message}"); }
try { account.Deposit(-5m); } catch (ArgumentOutOfRangeException ex) { Console.WriteLine($"rejected: {ex.ParamName} must be positive"); }
Console.WriteLine($"{account.Owner}: balance {account.Balance:0.00}, {account.History.Count} transactions: {string.Join(", ", account.History)}");

// ---- Log parser + CSV processor: parse, group, aggregate, export ----
string[] log =
[
    "2026-09-30T08:00:01Z INFO  Bench01 Flash started ecu=VCU",
    "2026-09-30T08:00:09Z WARN  Bench01 Voltage low value=11.2",
    "2026-09-30T08:01:15Z ERROR Bench02 Timeout ecu=BMS",
    "2026-09-30T08:02:00Z INFO  Bench01 Flash finished ecu=VCU",
    "garbage line",
    "2026-09-30T08:03:30Z ERROR Bench02 Timeout ecu=BMS",
];
var entries = log.Select(LogEntry.TryParse).OfType<LogEntry>().ToList();
Console.WriteLine($"parsed {e
…
using System.Collections.Concurrent;
using System.Threading.Channels;

// ---- Generic repository with an in-memory store ----
IRepository<Ecu, string> repo = new InMemoryRepository<Ecu, string>(e => e.Code);
repo.Add(new Ecu("VCU", "2.4.1")); repo.Add(new Ecu("BMS", "1.9.0"));
repo.Update(new Ecu("BMS", "2.0.0"));
Console.WriteLine($"repository: {string.Join(", ", repo.Find(e => e.Firmware.StartsWith('2')))}; Get(\"GW\") = {repo.Get("GW")?.ToString() ?? "null"}");

// ---- Caching service with expiry (thread-safe, lazy loading per key) ----
var cache = new ExpiringCache<string, string>(TimeSpan.FromMilliseconds(100));
int loads = 0;
Func<string, string> load = k => { Interlocked.Increment(ref loads); return $"calibration of {k}"; };
await Task.WhenAll(Enumerable.Range(0, 20).Select(_ => Task.Run(() => cache.Get("VCU", load))));
Console.WriteLine($"cache: 20 concurrent gets -> {loads} load");
await Task.Delay(150);
cache.Get("VCU", load);
Console.WriteLine($"cache: after expiry -> {loads} loads");

// ---- Multithreaded producer-consumer + background processing service ----
var channel = Channel.CreateBounded<int>(new BoundedChannelOptions(10) { FullMode = BoundedChannelFullMode.Wa
…
using System.Buffers.Binary;
using System.Diagnostics;
using System.Threading.Channels;

System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;

// High-performance telemetry pipeline: parse binary frames -> decode -> aggregate, with bounded channels (backpressure),
// batching, allocation-conscious parsing and throughput metrics.
const int FrameCount = 500_000;
byte[] raw = new byte[FrameCount * 12];                   // 4-byte id (big-endian) + 8 data bytes
var rng = new Random(42);
for (int i = 0; i < FrameCount; i++)
{
    BinaryPrimitives.WriteUInt32BigEndian(raw.AsSpan(i * 12), (uint)(0x100 + rng.Next(4)));
    rng.NextBytes(raw.AsSpan(i * 12 + 4, 8));
}

var batches = Channel.CreateBounded<Sample[]>(new BoundedChannelOptions(8) { SingleWriter = true, SingleReader = true });
var sw = Stopwatch.StartNew();
long allocatedBefore = GC.GetTotalAllocatedBytes(precise: true);

Task producer = Task.Run(async () =>
{
    const int BatchSize = 4096;
    for (int start = 0; start < FrameCount; start += BatchSize)
    {
        int n = Math.Min(BatchSize, FrameCount - start);
        var batch = new Sample[n];
        for (int i = 0; i < n; i
…

Expected Result

Twenty concurrent Get calls triggered exactly one load: Lazy<T> inside ConcurrentDictionary prevents duplicate loads (a cache stampede).

The per-ID statistics come from a seeded random generator, so they are reproducible; the throughput check depends on the machine.

temperatures: -40 C = -40 F, 0 C = 32 F, 37 C = 98.6 F, 100 C = 212 F
95->A 83->B 71->C 64->D 42->F 
Fibonacci(15): 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
primes < 50: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
'level' palindrome: True; 'Never odd or even' palindrome: True; 'engine' palindrome: False; 
array stats: min 3, max 21, mean 10.83, median 9.5, mode 7
words: 4, title case: The Quick Brown Fox, reversed: xof nworb  kciuq eht
rejected: insufficient funds: balance 379.50, requested 1000.00
rejected: amount must be positive
Asha: balance 379.50, 2 transactions: +500.00, -120.50
parsed 5/6 lines
  ERROR 2
  INFO  2
  WARN  1
errors per bench: Bench02=2
CSV export:
bench,level,message
Bench01,INFO,"Flash started ecu=VCU"
Bench01,WARN,"Voltage low value=11.2"
Bench02,ERROR,"Timeout ecu=BMS"
Bench01,INFO,"Flash finished ecu=VCU"
Bench02,ERROR,"Timeout ecu=BMS"
repository: Ecu { Code = BMS, Firmware = 2.0.0 }, Ecu { Code = VCU, Firmware = 2.4.1 }; Get("GW") = null
cache: 20 concurrent gets -> 1 load
cache: after expiry -> 2 loads
producer-consumer: 1000 frames processed by 3 consumers, total 1000
event: dashboard shows U0100 on BMS
event: network team notified
id 0x100: n=125277  mean 328.18  min  0.00  max 655.35  stddev 189.03
id 0x101: n=124892  mean 327.82  min  0.01  max 655.35  stddev 189.24
id 0x102: n=124903  mean 326.99  min  0.00  max 655.35  stddev 189.56
id 0x103: n=124928  mean 326.77  min  0.01  max 655.35  stddev 189.55
processed 500,000 frames; throughput above 1 million frames/s: True
allocated about 16 bytes per frame (the Sample batches); no per-frame strings or byte arrays

Common Mistakes

Reading the solution before trying — the learning happens while struggling.

Solving only the happy path: test empty input, invalid input, boundaries.

Over-engineering beginner exercises; under-engineering expert ones (no tests, no error handling).

Best Practices

Write a few test cases first (expected input → output).

After it works: refactor for clarity, then measure if performance matters.

Compare your solution with the one here and note differences in approach.

Interview Relevance

Very high: live-coding rounds use exactly these problems (palindromes, primes, Fibonacci, statistics, bank accounts, parsers, producer-consumer, caches, repositories).

Practice Questions

Solve the remaining beginner exercises (calculator with error handling for division by zero, number checker).

Build the library management system with borrowing rules and due dates.

Build the plugin system: load IPlugin implementations from DLLs in a folder with AssemblyLoadContext.

Extend the telemetry pipeline with multiple consumers and ordered output per ID.

Quick Quiz

Q1. (Predict the output) The 15th Fibonacci number, starting from 0?

Answer: 377 (verified: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377).

Q2. (Multiple choice) Which structure prevents duplicate concurrent loads per key in the caching service? A) Dictionary + lock around the whole load B) ConcurrentDictionary<TKey, Lazy<T>> C) List<T> D) static fields

Answer: B (verified: 1 load for 20 concurrent gets). A also works but serializes all keys.

Q3. (True/False) Welford's algorithm needs to store all samples to compute the standard deviation.

Answer: False. It updates mean and variance incrementally in O(1) memory.

Q4. (Find the bug) static bool IsPrime(int n) { for (int d = 2; d < n; d++) if (n % d == 0) return false; return true; }

Answer: It returns true for 0 and 1, and it is O(n); check n < 2 and loop only while d * d <= n.

Coding Challenge

Write a word-frequency counter: given a text, print the three most frequent words (case-insensitive, ignoring punctuation) with their counts.

string text = "The ECU reads the sensor. The sensor reports voltage; the ECU logs voltage!";
var top = text.Split(' ', StringSplitOptions.RemoveEmptyEntries)
    .Select(w => new string(w.Where(char.IsLetterOrDigit).ToArray()).ToLowerInvariant())
    .Where(w => w.Length > 0)
    .CountBy(w => w)
    .OrderByDescending(kv => kv.Value).ThenBy(kv => kv.Key)
    .Take(3);
foreach (var (word, count) in top) Console.WriteLine($"{word}: {count}");