Checkpoint: C# Basics
Reviewed & published by Brayan K
You've learned data types, casting, Console input, strings, List, Dictionary, properties and enums — let's combine them. This checkpoint isn't new theory; it's where the pieces click together into real, working programs. You'll warm up, recap what you know, build a complete program from scratch, and test yourself with a short quiz.
Part of the free C# course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You've Learned
Everything in this checkpoint draws on the eight lessons you just completed. Here's the toolkit you now carry into every C# program:
| Topic | You can now… | Key tool |
|---|---|---|
| Data Types | Pick the right type for any value | int, double, decimal, bool |
| Type Casting | Convert and validate safely | TryParse, (int) |
| User Input | Read and re-prompt for input | Console.ReadLine |
| Strings | Format, split, join, build text | $"...", StringBuilder |
| List<T> | Store ordered, growable data | Add, RemoveAll, Sort |
| Dictionary | Look up and tally by key | TryGetValue, GetValueOrDefault |
| Properties | Wrap data with validation | { get; set; } |
| Enums | Model fixed sets of options | enum, switch |
1. Warm-Up
Before the main challenge, run this short program that already weaves together five skills: splitting strings, parsing with TryParse, collecting into a List<int>, sorting, and interpolated output. Trace each line and predict the result before you run it.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
// A quick warm-up touching several skills at once.
string raw = "85, ninety, 73, 100, 60";
string[] parts = raw.Split(','); // strings
List<int> scores = new List<int>(); // List<T>
foreach (string part in parts)
if (int.TryParse(part.Trim(), out int n)) // input-style parsing
scores.Add(n);
scores.Sort();
Console.WriteLine($"Valid scores: {string.Join(", ", scores)}");
Console.WriteLine($"Average: {scores.Average():F1}"); // interpolation
// Valid scores: 60, 73, 85, 100
// Average: 79.5
}
}
// ✅ Expected output:
// Valid scores: 60, 73, 85, 100
// Average: 79.52. Build Challenge: Word Frequency Report
Now the main event. Build a complete word frequency report: split a sentence into words, tally each word in a Dictionary, print every word with its count, and announce the most frequent word. This is a genuine real-world task — search engines, analytics, and spam filters all start here. Attempt it yourself in the editor before revealing the solution.
🎯 Multi-Step Build Challenge
The starter below has the brief, the expected output, and a scaffold in comments. Combine strings + Dictionary + loops + interpolation to make it work.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// 🎯 BUILD CHALLENGE: Word Frequency Report
//
// You're given a sentence. Build a program that:
// 1. Splits the sentence into words (lower-cased, on spaces).
// 2. Counts how many times each word appears, using a
// Dictionary<string, int> (the GetValueOrDefault tally pattern).
// 3. Prints each word and its count with interpolation.
// 4. Reports the single most frequent word and how often it appeared.
//
// Expected output for the sentence below:
// the: 3
// quick: 1
// fox: 2
// jumps: 1
// Most frequent: 'the' (3 times)
string sentence = "the quick fox jumps the fox the";
// --- your code here ---
// string[] words = ...
// var counts = new Dictionary<string, int>();
// foreach (...) counts[word] = counts.GetValueOrDefault(word) + 1;
// print each pair
// track the best word + best count, then print it
}
}Here's one clean way to do it. Compare it with your attempt — notice how the count and the "best so far" are tracked in the same loop, so we never scan the dictionary twice.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
string sentence = "the quick fox jumps the fox the";
// 1) Split into lower-cased words.
string[] words = sentence.ToLower().Split(' ');
// 2) Tally each word with the GetValueOrDefault pattern.
var counts = new Dictionary<string, int>();
foreach (string word in words)
counts[word] = counts.GetValueOrDefault(word) + 1;
// 3) Print each word and its count.
string bestWord = "";
int bestCount = 0;
foreach (KeyValuePair<string, int> pair in counts)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
// 4) Track the most frequent word as we go.
if (pair.Value > bestCount)
{
bestCount = pair.Value;
bestWord = pair.Key;
}
}
Console.WriteLine($"Most frequent: '{bestWord}' ({bestCount} times)");
// the: 3
// quick: 1
// fox: 2
// jumps: 1
// Most frequent: 'the' (3 times)
}
}
// ✅ Expected output:
// the: 3
// quick: 1
// fox: 2
// jumps: 1
// Most frequent: 'the' (3 times)🔎 Stretch: model real data
Want to push further? This example brings together a class with properties, an enum, a computed property, and a List<Task> — exactly how a real task list app stores its data. Read it, run it, then try adding a new task or a fourth Priority level.
using System;
using System.Collections.Generic;
// A small model that combines properties + an enum + a collection.
enum Priority { Low, Medium, High }
class Task
{
public string Title { get; set; }
public Priority Level { get; set; }
public bool Done { get; set; }
public string Badge => Done ? "✓" : "·"; // computed property
}
class Program
{
static void Main()
{
var tasks = new List<Task>
{
new Task { Title = "Email client", Level = Priority.High, Done = true },
new Task { Title = "Write report", Level = Priority.Medium, Done = false },
new Task { Title = "Tidy desk", Level = Priority.Low, Done = false }
};
foreach (Task t in tasks)
Console.WriteLine($"[{t.Badge}] {t.Level,-6} {t.Title}");
// [✓] High Email client
// [·] Medium Write report
// [·] Low Tidy desk
}
}
// ✅ Expected output:
// [✓] High Email client
// [·] Medium Write report
// [·] Low Tidy desk📝 Checkpoint Quiz
Answer each in your head (or out loud) before revealing. Six questions covering the whole basics track.
Q1. Which type should hold a price like 19.99 that must be exact?
decimal (written 19.99m). double has binary rounding drift and is wrong for money.
Q2. The user types "abc". What does int.TryParse("abc", out int n) return, and what is n?
It returns false, and n is set to its default of 0. No exception is thrown — that's why TryParse is safe for input.
Q3. You read a key that isn't in a Dictionary with the indexer. What happens, and what should you use instead?
The indexer throws KeyNotFoundException. Use TryGetValue (or GetValueOrDefault / ContainsKey) to read safely.
Q4. Why can't you safely remove items from a List inside a foreach over that same list?
Changing the collection mid-iteration throws InvalidOperationException. Use RemoveAll(predicate), or loop backwards with a for loop using indexes.
Q5. What's the advantage of an auto-property over a public field?
A property keeps a stable public surface: you can later add validation in the setter, make the setter private, or compute the value — all without breaking calling code. A public field locks you out of that.
Q6. Given enum Level { Low = 1, Medium, High }, what is (int)Level.High?
3 — Low is explicitly 1, so Medium auto-increments to 2 and High to 3.
Pro Tips for the Road Ahead
- 💡 Reach for the right collection: List for ordered data, Dictionary for lookups by key.
- 💡 Validate at the boundary: parse and check input the moment it arrives, before it spreads through your code.
- 💡 Let types do the work: enums and properties catch whole classes of bugs at compile time.
- 💡 Read your output: if a program runs but prints the wrong thing, the bug is usually a type or a loop boundary — exactly what you practised here.
Watch-Outs You've Now Met
- Integer division: 7 / 2 is 3. Cast to double for a real average.
- Concatenating input: "5" + 3 is "53" — parse before doing maths.
- Missing keys / out-of-range indexes: guard with TryGetValue and Count checks.
- Forgetting usings: List/Dictionary need System.Collections.Generic; Sum/Average need System.Linq.
🎉 Checkpoint Complete
- ✅ You combined input, parsing, collections, strings and formatting into one working program
- ✅ You built a real word-frequency report with the Dictionary tally pattern
- ✅ You modelled data with classes, properties and enums in a single list
- ✅ You passed the quiz on the core gotchas: division, concatenation, missing keys, mutation
Practice quiz
Which C# type should hold a price like 19.99 that must stay exact?
- double
- float
- decimal
- int
Answer: decimal. decimal (written 19.99m) avoids the binary rounding drift that makes double wrong for money.
The user enters "abc". What does int.TryParse("abc", out int n) return, and what is n?
- Returns true, n is 0
- Returns false, n is 0
- Throws a FormatException
- Returns false, n is -1
Answer: Returns false, n is 0. TryParse returns false on bad input and sets n to its default of 0, without throwing.
Reading a key that is NOT in a Dictionary using the indexer (dict[key]) does what?
- Returns null
- Returns 0
- Throws KeyNotFoundException
- Adds the key automatically
Answer: Throws KeyNotFoundException. The indexer throws KeyNotFoundException for a missing key; use TryGetValue or GetValueOrDefault to read safely.
What is the one-line idiom to tally counts in a Dictionary<string,int> called 'counts'?
- counts.Add(word, 1)
- counts[word] = counts.GetValueOrDefault(word) + 1
- counts.Increment(word)
- counts[word]++ only
Answer: counts[word] = counts.GetValueOrDefault(word) + 1. GetValueOrDefault returns 0 for a missing word, so adding 1 starts a new tally or grows an existing one.
Why can't you safely remove items from a List inside a foreach over that same list?
- foreach is read-only by design
- Modifying the collection mid-iteration throws InvalidOperationException
- It silently skips elements
- Lists cannot be removed from at all
Answer: Modifying the collection mid-iteration throws InvalidOperationException. Mutating a collection during foreach throws InvalidOperationException; use RemoveAll or a reverse for loop.
Which method reads a line of text typed by the user at the console?
- Console.Write
- Console.ReadKey
- Console.ReadLine
- Console.Input
Answer: Console.ReadLine. Console.ReadLine returns the full line the user typed as a string (or null at end of input).
What does string interpolation with $"Hello {name}" do?
- Prints a literal {name}
- Inserts the value of the name variable into the string
- Throws if name is null
- Only works with numbers
Answer: Inserts the value of the name variable into the string. An interpolated string ($"...") substitutes the value of each {expression} into the text.
What is the advantage of an auto-property over a public field?
- It runs faster
- It lets you later add validation or change access without breaking callers
- It uses less memory
- Fields cannot be public
Answer: It lets you later add validation or change access without breaking callers. A property keeps a stable public surface, so you can add a setter check or make it computed later.
Given enum Level { Low = 1, Medium, High }, what is (int)Level.High?
- 1
- 2
- 3
- 0
Answer: 3. Low is explicitly 1, so Medium auto-increments to 2 and High to 3.
Integer division: what does the C# expression 7 / 2 evaluate to?
- 3.5
- 3
- 4
- 3.0
Answer: 3. Two ints divide as integers, discarding the remainder; cast to double for 3.5.
Continue this course
- Previous: Enums
- Next: Nullable Types & Null Operators — Nullable<T>, ?? , ?. and ??= for safe null handling
- Quick reference: C# cheat sheet
Frequently asked questions
What should I be able to do after this checkpoint?
You should be able to read user input with Console.ReadLine, convert and validate it with TryParse, store collections in a List or Dictionary, count or tally items, format output with string interpolation, and model data with properties and enums. If any of those feel shaky, revisit that lesson before moving on — the next topics build directly on these foundations.
Why does the checkpoint combine so many topics into one program?
Real programs never use one concept in isolation. A simple feature like a word-frequency report touches input, strings, a Dictionary, loops, formatting and sometimes an enum all at once. Practising them together is what turns separate facts into a skill you can actually use, which is exactly what a checkpoint is for.
I got stuck on the build challenge — is that normal?
Completely. The build challenge is meant to stretch you. Try writing it yourself first, run it, and read the errors; they are clues, not failures. Only then open the revealed solution and compare it to your attempt. The gap between your version and the model answer is where the real learning happens.
What comes after the C# basics checkpoint?
Next you move into nullable types and the null-handling operators, then deeper object-oriented programming, exceptions, LINQ and beyond. Everything ahead assumes you are comfortable with the basics you just consolidated here, so a solid checkpoint pays off across the whole rest of the course.