Just added: Algorithms you can run and practice
Updated Jun 22, 2026 Edit on GitHub

Syntax, Values & Types

Phase 1 got a program to print and run - the warm-up. Real programs hold things: a name, a count, a price - and the rules C# uses to store those things shape almost everything you'll write next. Two ideas do the heavy lifting: the compiler knows the type of every value before your code runs, and C# splits all values into two camps - ones that copy when passed around, and ones that share. Get that split into your head now and a whole category of "why did this change?" bugs never happens to you.

Static typing - the compiler checks before you run

What it actually is. C# is statically typed: every variable has a fixed type locked in when declared, and the compiler verifies every use of it before the program runs. A variable holding a whole number can never later hold text - try it, and the build fails with a red squiggle, no runtime crash.

📝 Static means "checked at compile time," as opposed to dynamic ("checked while running"). C# checks up front, so by the time your program runs, "what type is this?" is already answered and can't go wrong.

int count = 0;
count = count + 1;     // fine: an int plus an int is an int
count = "hello";       // compile error: cannot convert string to int

What just happened: int count = 0; told the compiler "count is an integer, forever." Adding to it is fine; assigning the text "hello" is a contradiction the compiler catches instantly with error CS0029: Cannot implicitly convert type 'string' to 'int'. A whole class of "I thought this was a number but it was text" mistakes simply cannot ship.

Value types vs reference types - copy or share?

This is the idea in this phase.

📝 C# splits every type into two families:

  • Value types hold their data directly. When you assign or pass one, you get a copy - a separate, independent value. These include int, double, bool, char, every struct, and every enum.
  • Reference types hold a reference (a pointer) to data that lives elsewhere in memory. When you assign or pass one, you copy the reference, not the data - so both names now point at the same object. These include every class, plus string and arrays.

Why the split? Small, simple values (a number, a flag) are cheapest to copy. Big or shared things (an object with many fields, a list everyone needs the same version of) are cheapest to pass by reference. The catch: copy-vs-share changes behavior, not just performance - what trips people up.

A struct is a value type; a class is a reference type:

struct PointVal { public int X; }   // value type
class PointRef  { public int X; }   // reference type

var a = new PointVal { X = 1 };
var b = a;                          // COPY - b is independent
b.X = 99;
// a.X is still 1, b.X is 99

var c = new PointRef { X = 1 };
var d = c;                          // SHARE - d points at the same object as c
d.X = 99;
// c.X is now 99 too - same object
PointVal: a.X = 1,  b.X = 99
PointRef: c.X = 99, d.X = 99

What just happened: b = a copied the whole struct, so b is independent - changing b.X left a untouched. But d = c only copied the reference; c and d name one object, so writing through d changed what c sees too. Same line of code (x = y), opposite result - the only difference is whether the type is a struct (value) or a class (reference).

⚠️ The classic surprise: passing to a method. The same rule applies when you hand a value to a method. Pass a struct and the method works on a copy - your original is safe. Pass a class and it works on your actual object - changes leak back out.

struct Counter { public int N; }
class  Box     { public int N; }

void BumpStruct(Counter c) { c.N++; }   // bumps a copy
void BumpClass(Box b)      { b.N++; }   // bumps the caller's object

var counter = new Counter { N = 0 };
BumpStruct(counter);
// counter.N is still 0 - the method changed its own copy

var box = new Box { N = 0 };
BumpClass(box);
// box.N is now 1 - the method reached the real object
counter.N = 0
box.N = 1

What just happened: BumpStruct received a copy of counter, incremented it, and threw it away - counter never changed. BumpClass received a copy of the reference, still pointing at the caller's box, so the increment stuck. A method that "didn't change my data" is almost always a value type; one that "changed my data behind my back" is a reference type.

💡 Key point. Ask two questions about any type: Does assigning it copy or share? Does passing it to a method protect my original or expose it? Value types copy and protect; reference types share and expose. null only enters the picture for reference types and nullable value types (next) - only a reference can point at "nothing."

var - let the compiler infer the type

Spelling out the type twice gets old fast: Dictionary<string, List<int>> map = new Dictionary<string, List<int>>(); is a mouthful. var lets the compiler infer the type from the right side:

var name = "Ada";          // compiler sees "Ada" is text → name is a string
var age = 36;              // compiler sees 36 is a whole number → age is an int
var price = 9.99;         // → double
var ready = true;         // → bool

What just happened: Each var told the compiler to figure out the type from the right-hand side. name is still a string - fully static, fully checked - you just didn't write the word. var isn't "any type" or dynamic; it's shorthand for a type the compiler can already see. name = 42; afterward is still the same compile error, because name's type was fixed the moment it was inferred.

💡 When to use it. Reach for var when the type is obvious from the right side (var user = new User();) or genuinely long to spell out. Prefer the explicit type when the value alone doesn't make it clear (var result = Process(); - what is result?). The goal is readability, not saving keystrokes.

Nullability - the war on NullReferenceException

📝 null means "this reference points at nothing." Read a value off a null reference and you get a NullReferenceException - historically C#'s single most common crash. Modern C# fights back on two fronts.

Nullable value types. Value types like int can't normally be null - they always hold a real number. But sometimes you need "a number, or nothing yet" (an unanswered survey field, say). Add a ? to make a nullable value type:

int score = 0;            // always a number; cannot be null
int? maybeScore = null;   // a number OR null - note the ?

if (maybeScore.HasValue)
    Console.WriteLine(maybeScore.Value);
else
    Console.WriteLine("no score yet");
no score yet

What just happened: int refuses null outright. int? (shorthand for Nullable<int>) adds one extra state - "nothing" - modeling "not answered yet" plainly instead of faking it with -1 or 0.

Nullable reference types. Reference types have always been able to be null, exactly why crashes were so common. Modern C# (<Nullable>enable</Nullable> in your project file, default for new projects) flips this: you must opt in to nullability. A plain string means "never null"; string? means "might be null" - and the compiler warns you whenever you risk dereferencing a possible null.

#nullable enable
string name = "Ada";       // promises: never null
string? note = null;       // allowed to be null

Console.WriteLine(name.Length);   // fine - name can't be null
Console.WriteLine(note.Length);   // warning CS8602: possible null dereference

What just happened: With nullability enabled, name is non-nullable - the compiler trusts it's never null and lets you use it freely. note is a string?, so reading note.Length earns a warning: "this might be null, you didn't check." The compiler moves the NullReferenceException from a 3-a.m. production crash to a squiggle in your editor. ⚠️ These are warnings, not errors, so it's tempting to ignore them. Don't: each is a real crash the compiler spotted for you. (Deep dive on nullability and the ?. / ?? operators in Phase 13.)

Strings - text done the C# way

Strings get their own section for a couple of quietly important behaviors.

📝 A string is a reference type, but an immutable one: once created, its characters never change. Every operation that looks like it modifies a string (uppercasing, replacing, concatenating) builds a brand-new string and leaves the original alone.

The most useful everyday feature is string interpolation - prefix with $ and drop expressions right inside { }:

var name = "Ada";
var age = 36;
var greeting = $"Hello {name}, you are {age} years old.";
Console.WriteLine(greeting);
Console.WriteLine($"Next year: {age + 1}");   // expressions work too
Hello Ada, you are 36 years old.
Next year: 37

What just happened: The $ turns the string into a template; each { ... } is evaluated and its result dropped in. {age + 1} shows real expressions work inside, not just variable names - far cleaner than gluing strings with +, and what you'll use almost every time.

⚠️ The equality gotcha - == on strings compares value. Coming from Java, this surprises people: in Java, == compares references (same object?), so you need .equals(). In C#, == on strings is overloaded to compare the actual text:

var a = "hello";
var part = "hel";
var b = part + "lo";        // built at runtime - a genuinely different object, same text
Console.WriteLine(a == b);  // True - compares the characters
True

What just happened: a and b are different objects in memory (b is built at runtime, so it isn't the same interned literal as a), but == on string compares their contents, so you get True. This is special to string - your own classes compare references by default, a different story told in Phase 9. For now: strings compare by value with ==, and that's the one you want.

Two more string forms: verbatim strings (@"...") turn off escape sequences, so backslashes and line breaks are literal - perfect for Windows paths and regex. Raw string literals ("""...""") let you paste multi-line text (JSON, SQL, HTML) without escaping anything:

var path = @"C:\Users\Ada\file.txt";          // no doubled backslashes needed
var json = """
    { "name": "Ada", "age": 36 }
    """;                                       // quotes inside need no escaping
Console.WriteLine(path);
Console.WriteLine(json);
C:\Users\Ada\file.txt
{ "name": "Ada", "age": 36 }

What just happened: In a normal string, "C:\Users" would treat \U as an escape and break. @ says "take this literally," so backslashes stand as-is. The """ raw string holds a block with embedded " quotes and newlines, no escaping required - the compiler even strips leading indentation up to the closing """. Reach for @"..." for paths and """...""" for chunks of structured text.

Recap

  1. C# is statically typed - every variable's type is fixed at declaration and checked at compile time, so type mismatches fail the build instead of crashing at runtime.
  2. Value vs reference is the big split. Value types (int, double, bool, struct, enum) hold data directly and copy on assignment and method calls. Reference types (class, string, arrays) hold a reference and share - both names point at the same object.
  3. ⚠️ Passing a struct to a method protects your original (it gets a copy); passing a class exposes it (the method reaches your real object).
  4. var infers the type from the right-hand side - still fully static, just less typing. Use it when the type is obvious.
  5. Nullability: int? adds "nothing" to a value type; modern nullable reference types make string non-null and string? maybe-null, with compiler warnings to kill NullReferenceException early.
  6. Strings are immutable reference types. Use $"..." interpolation; remember == compares value (unlike Java); use @"..." for paths and """...""" for multi-line text.

Next, we move from single values to collections - arrays, the List<T> you'll actually use, and dictionaries for looking things up by key.

Quick check

Test yourself on the idea that matters most - copy versus share:

[
  {
    "q": "You have a `struct Point` and write `var b = a;` then `b.X = 99;`. What is `a.X`?",
    "choices": [
      "Unchanged - a struct is a value type, so `b = a` made an independent copy",
      "99 - `b` and `a` point at the same object",
      "0 - assigning a struct resets its fields",
      "A compile error - you can't copy a struct"
    ],
    "answer": 0,
    "explain": "A struct is a value type: assignment copies the whole value, so `b` is independent of `a`. Changing `b.X` leaves `a.X` exactly as it was. If `Point` were a `class` (reference type), `b = a` would share the object and `a.X` would become 99 too."
  },
  {
    "q": "With nullable reference types enabled, what's the difference between `string` and `string?`?",
    "choices": [
      "`string` is non-nullable (the compiler warns if it might be null); `string?` is allowed to be null",
      "`string` is a value type and `string?` is a reference type",
      "There is no difference - the `?` is just a style preference",
      "`string?` is faster because it skips null checks"
    ],
    "answer": 0,
    "explain": "Under `<Nullable>enable</Nullable>`, a plain `string` promises it's never null and the compiler warns when you risk dereferencing a possible null. `string?` explicitly permits null. The point is to surface NullReferenceException risks as compile-time warnings instead of runtime crashes."
  },
  {
    "q": "In C#, what does `==` do when both operands are strings?",
    "choices": [
      "Compares the actual text (their characters) for equality",
      "Compares whether they're the same object in memory, like Java",
      "Always returns false unless they're literally the same literal",
      "Throws an exception - you must use `.Equals()` for strings"
    ],
    "answer": 0,
    "explain": "`==` is overloaded for `string` to compare value (the characters), so two different string objects with the same text are equal. This differs from Java, where `==` compares references. Note that for your own classes, `==` compares references by default - string is the special case."
  }
]

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. You have a `struct Point` and write `var b = a;` then `b.X = 99;`. What is `a.X`?

2. With nullable reference types enabled, what's the difference between `string` and `string?`?

3. In C#, what does `==` do when both operands are strings?