DEV Community

Manohari Jayachandran
Manohari Jayachandran

Posted on

C# Coding Interview Prep Part 1: Variables, Strings, Arrays, Lists, Dictionaries, and Every Basic Building Block

This is a refresher built while preparing for C# coding interviews, and shared as a resource, because a genuinely comprehensive, practice-oriented guide is hard to find in one place. This series doesn't skip the basics to get to the "interesting" parts - the basics are exactly what trips people up under interview pressure, when a simple question about arrays suddenly requires recalling details nobody thinks about in day-to-day coding. Every topic in this series follows the same format: a plain-English explanation, an analogy, a code example, and a practice question at the end, so this can genuinely be used to test yourself, not just read passively.

This is Part 1 of three. Part 1 covers the _basics _- variables, data types, strings, arrays, List, Dictionary, HashSet, tuples, value vs reference types, operators, control flow, loops, methods, enums, structs vs classes, nullable types, type conversion, and basic exception handling. Part 2 covers mid-level concepts - LINQ, generics, delegates and events, interfaces vs abstract classes, and exception handling in depth. Part 3 covers _advanced _concepts - async and Task, reflection, memory management and garbage collection, design patterns in practice, and performance considerations.

Topic 1: Variables and Data Types

A variable is a named storage location holding a value of a specific type. C# is statically typed - once a variable is declared with a type, it can only ever hold values of that type, or something compatible with it.

Think of a labeled storage box. A box labeled "int" can only hold integers - trying to put a sentence in it doesn't work, the same way a shoebox labeled for shoes doesn't neatly fit a stack of books.

// Value types - the actual data lives directly
// in the variable
int age = 30;
double price = 19.99;
bool isPublished = true;
char grade = 'A';
decimal exactMoney = 19.99m;  // decimal for money -
                               // no floating point
                               // rounding errors

// var - the compiler infers the type at compile
// time, this is still statically typed, NOT dynamic
var name = "Alex";  // compiler infers string

// Reference types - the variable holds a reference
// (address) to where the actual data lives
string title = "C# Basics";
int[] numbers = { 1, 2, 3 };
Post post = new Post();

// Constants - value is fixed at compile time,
// cannot ever change
const double Pi = 3.14159;

// Nullable value types - value types normally
// cannot be null, this opts in
int? nullableAge = null;
Enter fullscreen mode Exit fullscreen mode

Practice question: What is the difference between const and readonly in C#? Write one example of each.

Topic 2: Strings and String Methods

A string is a sequence of characters, and in C# it's a reference type - but with a critical twist: strings are immutable. Once created, a string's actual content in memory never changes. Every operation that appears to modify a string actually creates a brand new string.

Think of a printed book page. You cannot erase and rewrite a word on a printed page - if you want different text, you print an entirely new page. Modifying a string works the same way: the old string still exists in memory until garbage collected, and a new one is created for the result.

string greeting = "Hello";
string name = "Manohari";

// Concatenation - creates a NEW string, does not
// modify 'greeting'
string message = greeting + ", " + name;

// String interpolation - cleaner syntax, same result
string message2 = $"{greeting}, {name}";

// Common string methods
string text = "  C# Interview Prep  ";
Console.WriteLine(text.Trim());           // "C# Interview Prep"
Console.WriteLine(text.ToUpper());        // "  C# INTERVIEW PREP  "
Console.WriteLine(text.Contains("Prep")); // true
Console.WriteLine(text.Replace("C#", "CSharp"));
Console.WriteLine(text.Split(' ').Length);
Console.WriteLine(text.Substring(2, 4));  // extracts a portion
Console.WriteLine(text.IndexOf("Prep"));  // position found at

// String comparison - == checks VALUE equality
// for strings specifically (special-cased), not
// reference equality
string a = "hello";
string b = "hello";
Console.WriteLine(a == b);         // true - value equality
Console.WriteLine(a.Equals(b));    // true - same result

// StringBuilder - for heavy concatenation, avoid
// creating thousands of throwaway strings in a loop
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i).Append(", ");
}
string result = sb.ToString();
Enter fullscreen mode Exit fullscreen mode

Practice question: Write a method that reverses a string without using any built-in reverse method.

Topic 3: Arrays

An array is a fixed-size, ordered collection of elements of the same type. The size is set at creation and cannot change afterward - to "resize," a new array must be created and the old data copied over.

Think of a row of numbered parking spaces painted onto pavement. The number of spaces is fixed the moment the lot is built - adding more spaces means literally repaving with a new, larger layout, not just adding to the existing one.

// Declaration and initialization
int[] numbers = { 1, 2, 3, 4, 5 };
string[] names = new string[3];  // size 3, all null initially
names[0] = "Alex";
names[1] = "Priya";
names[2] = "Sam";

// Accessing and modifying
Console.WriteLine(numbers[0]);   // 1
numbers[0] = 100;                // arrays ARE mutable in
                                  // content, just fixed size

// Iterating
foreach (var n in numbers)
{
    Console.WriteLine(n);
}

// Multi-dimensional array
int[,] grid = new int[3, 3];
grid[0, 0] = 1;
grid[1, 1] = 5;

// Jagged array - array of arrays, each inner
// array can be a different length
int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 1, 2, 3, 4 };

// Common Array methods
Array.Sort(numbers);
Array.Reverse(numbers);
int index = Array.IndexOf(numbers, 100);
bool found = Array.Exists(numbers, n => n > 50);
Enter fullscreen mode Exit fullscreen mode

Practice question: Given an integer array, write a method that finds the second largest number without sorting the entire array.

Topic 4: List

List is a resizable, ordered collection - the generic, flexible alternative to a fixed-size array. Internally it's backed by an array that gets replaced with a larger one automatically as items are added beyond current capacity.

Think of an expandable file folder versus a fixed shoebox. As more documents come in, the expandable folder just grows - you never have to buy a new one and manually move everything over, even though internally that's conceptually similar to what List does behind the scenes automatically.

var posts = new List<string>();

posts.Add("First Post");
posts.Add("Second Post");
posts.Insert(0, "Inserted at start");

posts.Remove("First Post");     // removes by value
posts.RemoveAt(0);               // removes by index

bool exists = posts.Contains("Second Post");
int index = posts.IndexOf("Second Post");

Console.WriteLine(posts.Count);  // current item count

// Iterating
foreach (var post in posts)
{
    Console.WriteLine(post);
}

// Converting between List and Array
int[] array = { 1, 2, 3 };
List<int> fromArray = array.ToList();
int[] backToArray = fromArray.ToArray();

// Sorting and searching
var numbers = new List<int> { 5, 2, 8, 1 };
numbers.Sort();
numbers.Sort((a, b) => b.CompareTo(a));  // descending,
                                            // custom comparer
Enter fullscreen mode Exit fullscreen mode

Practice question: Write a method that removes all duplicate values from a List while preserving the original order.

Topic 5: Dictionary

A Dictionary stores key-value pairs and provides fast, close-to-O(1) lookup by key, using a hash table internally. Every key must be unique.

Think of a labeled filing cabinet drawer system, where every drawer has exactly one unique name on it. Finding "the invoices drawer" doesn't require checking every drawer one by one - you go directly to the one labeled correctly.

var postsBySlug = new Dictionary<string, string>();

postsBySlug["csharp-basics"] = "C# Basics Post";
postsBySlug["csharp-linq"] = "LINQ Deep Dive";

// Safe lookup - does not throw if key is missing
if (postsBySlug.TryGetValue("csharp-basics", out var title))
{
    Console.WriteLine(title);
}

// Direct access - throws KeyNotFoundException if missing
string result = postsBySlug["csharp-basics"];

bool hasKey = postsBySlug.ContainsKey("csharp-linq");
postsBySlug.Remove("csharp-linq");

// Iterating - gives KeyValuePair<TKey,TValue>
foreach (var kvp in postsBySlug)
{
    Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}

// Just keys or just values
foreach (var key in postsBySlug.Keys) { }
foreach (var value in postsBySlug.Values) { }

// Update or add in one call
postsBySlug["csharp-basics"] = "Updated Title";  // overwrites
Enter fullscreen mode Exit fullscreen mode

Practice question: Given an array of words, write a method using a Dictionary that counts how many times each word appears.

Topic 6: HashSet

A HashSet stores unique values only, with no guaranteed order, and provides fast O(1) average lookup for "does this exist" checks - much faster than checking a List with Contains for large collections.

Think of a guest list at the door of an event, where a bouncer just needs to answer "is this name already checked in" as fast as possible - not caring about the order names were added, just whether a given name is present at all.

var uniqueTags = new HashSet<string>();

uniqueTags.Add("Azure");
uniqueTags.Add("C#");
uniqueTags.Add("Azure");  // silently ignored - duplicate

Console.WriteLine(uniqueTags.Count);  // 2

bool hasAzure = uniqueTags.Contains("Azure");  // O(1) check

// Set operations
var setA = new HashSet<int> { 1, 2, 3, 4 };
var setB = new HashSet<int> { 3, 4, 5, 6 };

setA.IntersectWith(setB);  // setA becomes {3, 4}
// UnionWith, ExceptWith also available
Enter fullscreen mode Exit fullscreen mode

Practice question: Given two integer arrays, write a method that returns only the numbers that appear in both arrays, using a HashSet.

Topic 7: Tuples

A tuple bundles multiple values together into one object without needing to define a custom class. Modern C# tuples support named elements, making them readable at the point of use.

Think of a small labeled tray carrying a few different items together - a tray with "coffee" and "muffin" compartments lets you carry both at once without needing a whole custom carrying case built just for that specific combination.

// Named tuple
(string Name, int Age) person = ("Alex", 30);
Console.WriteLine(person.Name);  // "Alex"
Console.WriteLine(person.Age);   // 30

// Returning multiple values from a method
(int Min, int Max) FindMinMax(int[] numbers)
{
    return (numbers.Min(), numbers.Max());
}

var result = FindMinMax(new[] { 5, 2, 8, 1 });
Console.WriteLine($"Min: {result.Min}, Max: {result.Max}");

// Deconstruction
var (min, max) = FindMinMax(new[] { 5, 2, 8, 1 });
Console.WriteLine(min);
Console.WriteLine(max);

// Tuple vs custom class - when to use which
// Tuple: quick, internal, 2-3 short-lived values
// Class: the shape has real meaning, gets passed
// around broadly, or needs its own behavior/methods
Enter fullscreen mode Exit fullscreen mode

Practice question: Write a method that takes a list of numbers and returns a tuple containing the sum, average, and count in one call.

Topic 8: Value Types vs Reference Types

This is one of the most frequently tested distinctions in C# interviews. Value types - int, bool, double, struct - store their actual data directly in the variable. Reference types - class, array, string, List, Dictionary - store a reference, a memory address, to where the actual data lives on the heap. The variable itself just holds that address.

Think of a value type as writing a number directly on a sticky note - the note is the data. A reference type is like writing a house address on a sticky note - the note points to where the actual house is, and copying the note just copies the address, not the house itself.

// Value type - assignment COPIES the actual value
int a = 5;
int b = a;   // b gets its OWN copy of 5
b = 10;
Console.WriteLine(a);  // still 5 - a was never touched

// Reference type - assignment copies the REFERENCE,
// both variables point to the SAME object
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;   // list2 points to the SAME list
list2.Add(4);
Console.WriteLine(list1.Count);  // 4 - list1 sees the
                                   // change too, because
                                   // it's the same object

// Passing to methods - same rule applies
void ModifyValue(int x) { x = 100; }
void ModifyReference(List<int> list) { list.Add(999); }

int number = 5;
ModifyValue(number);
Console.WriteLine(number);  // still 5

var myList = new List<int> { 1, 2 };
ModifyReference(myList);
Console.WriteLine(myList.Count);  // 3 - the SAME list
                                    // object was modified

// string is a special case - reference type, but
// immutable, so it BEHAVES like a value type in
// most practical situations
string s1 = "hello";
string s2 = s1;
s2 = s2 + " world";  // creates a NEW string for s2
Console.WriteLine(s1);  // still "hello" - unaffected
Enter fullscreen mode Exit fullscreen mode

Practice question: Without running the code, predict the output: create a struct Point with a public int X, create var p1 = new Point with X set to 1, assign var p2 = p1, then set p2.X to 99. What is p1.X afterward, and why?

Topic 9: Operators

Operators perform operations on values - arithmetic, comparison, logical, and a few special ones worth knowing well for interviews specifically.

// Arithmetic
int sum = 5 + 3;
int remainder = 10 % 3;   // modulo - remainder after division

// Comparison
bool isEqual = (5 == 5);
bool isGreater = (5 > 3);

// Logical
bool result = true && false;   // AND
bool result2 = true || false;  // OR
bool result3 = !true;           // NOT

// Null-coalescing - returns left if not null,
// otherwise returns right
string name = null;
string display = name ?? "Unknown";  // "Unknown"

// Null-conditional - safely accesses a member,
// returns null instead of throwing if the object
// itself is null
Post post = null;
string title = post?.Title;  // null, no exception thrown

// Null-coalescing assignment
string cachedValue = null;
cachedValue ??= "default";  // assigns only if currently null

// Ternary conditional
int age = 20;
string category = age >= 18 ? "Adult" : "Minor";

// Increment/decrement
int count = 0;
count++;      // post-increment
++count;      // pre-increment - subtle difference in
              // expressions, same end result standalone
Enter fullscreen mode Exit fullscreen mode

Practice question: What is the difference between x++ and ++x when used inside a larger expression, like int y = x++ + 1 versus int y = ++x + 1? Write out what each assigns to x and y given x starts at 5.

Topic 10: Control Flow - if, else, switch

Control flow statements decide which code actually runs based on conditions.

int score = 85;

// if / else if / else
if (score >= 90)
{
    Console.WriteLine("A");
}
else if (score >= 80)
{
    Console.WriteLine("B");
}
else
{
    Console.WriteLine("C or below");
}

// switch statement
string grade;
switch (true)
{
    case bool _ when score >= 90:
        grade = "A";
        break;
    case bool _ when score >= 80:
        grade = "B";
        break;
    default:
        grade = "C or below";
        break;
}

// switch expression (modern C#, more concise)
string grade2 = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    _ => "C or below"
};

// Pattern matching in switch
object value = 42;
string description = value switch
{
    int i when i > 0 => "positive integer",
    int i when i < 0 => "negative integer",
    string s => $"a string: {s}",
    null => "null value",
    _ => "something else"
};
Enter fullscreen mode Exit fullscreen mode

Practice question: Write a method using a switch expression that takes an integer representing a day of the week (1-7) and returns whether it's a "Weekday" or "Weekend".

Topic 11: Loops

Loops repeat a block of code, either a fixed number of times, while a condition holds, or once per element in a collection.

// for loop - when you know the iteration count
// or need the index
for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

// while loop - condition checked BEFORE each iteration
int count = 0;
while (count < 5)
{
    Console.WriteLine(count);
    count++;
}

// do-while - condition checked AFTER each iteration,
// so the body ALWAYS runs at least once
int x = 10;
do
{
    Console.WriteLine(x);
    x++;
} while (x < 5);  // runs once even though condition
                    // is false from the start

// foreach - iterates every element in a collection,
// no manual index management
var posts = new List<string> { "A", "B", "C" };
foreach (var post in posts)
{
    Console.WriteLine(post);
}

// break and continue
for (int i = 0; i < 10; i++)
{
    if (i == 3) continue;  // skip this iteration
    if (i == 7) break;     // exit the loop entirely
    Console.WriteLine(i);
}
Enter fullscreen mode Exit fullscreen mode

Practice question: Write a program using a loop that prints the Fibonacci sequence up to the 10th number.

Topic 12: Methods and Parameters

A method is a named, reusable block of code. Parameters pass data into a method; a return type defines what comes back out.

// Basic method
int Add(int a, int b)
{
    return a + b;
}

// Optional parameters - must come after required ones
void Greet(string name, string greeting = "Hello")
{
    Console.WriteLine($"{greeting}, {name}");
}
Greet("Alex");               // uses default "Hello"
Greet("Alex", "Hi");         // overrides default

// Named arguments - can be passed in any order
Greet(greeting: "Hey", name: "Alex");

// params - accepts a variable number of arguments
int Sum(params int[] numbers)
{
    int total = 0;
    foreach (var n in numbers) total += n;
    return total;
}
Sum(1, 2, 3);        // works
Sum(1, 2, 3, 4, 5);  // also works, any count

// ref - passes by REFERENCE, method can modify
// the caller's actual variable
void Double(ref int number)
{
    number = number * 2;
}
int value = 5;
Double(ref value);
Console.WriteLine(value);  // 10 - actually changed

// out - similar to ref, but the method MUST assign
// a value before returning, commonly used for
// "try" pattern methods
bool TryParse(string input, out int result)
{
    return int.TryParse(input, out result);
}

// Method overloading - same name, different
// parameter signatures
int Multiply(int a, int b) => a * b;
double Multiply(double a, double b) => a * b;
Enter fullscreen mode Exit fullscreen mode

Practice question: What is the actual difference between ref and out parameters? Write a method using each that demonstrates the difference.

Topic 13: Enums

An enum defines a named set of related constant values, making code more readable than using raw numbers or strings for a fixed set of options.

public enum OrderStatus
{
    Pending,     // 0 by default
    Processing,  // 1
    Shipped,     // 2
    Delivered    // 3
}

var status = OrderStatus.Processing;
Console.WriteLine(status);          // "Processing"
Console.WriteLine((int)status);      // 1

// Assigning explicit values
public enum HttpStatusCode
{
    Ok = 200,
    NotFound = 404,
    ServerError = 500
}

// Converting from an int
var code = (HttpStatusCode)404;
Console.WriteLine(code);  // NotFound

// Parsing from a string
var parsed = Enum.Parse<OrderStatus>("Shipped");

// Switching on an enum
string GetMessage(OrderStatus status) => status switch
{
    OrderStatus.Pending => "Order received",
    OrderStatus.Shipped => "On its way",
    OrderStatus.Delivered => "Completed",
    _ => "Unknown status"
};
Enter fullscreen mode Exit fullscreen mode

Practice question: Create an enum for days of the week, and write a method that returns true if a given day is a weekend.

Topic 14: Structs vs Classes

Both structs and classes group related data and behavior together, but a struct is a value type, copied on assignment, while a class is a reference type, shared on assignment - the same fundamental distinction as value types versus reference types, applied to your own custom types.

// struct - value type, typically small, simple data
public struct Point
{
    public int X;
    public int Y;
}

var p1 = new Point { X = 1, Y = 2 };
var p2 = p1;       // COPIES the values
p2.X = 99;
Console.WriteLine(p1.X);  // still 1 - p1 untouched

// class - reference type, typically larger objects
// with behavior, identity, and mutable state over time
public class Post
{
    public string Title;
    public int ViewCount;
}

var post1 = new Post { Title = "Hello", ViewCount = 0 };
var post2 = post1;  // SHARES the same object
post2.ViewCount = 5;
Console.WriteLine(post1.ViewCount);  // 5 - same object
Enter fullscreen mode Exit fullscreen mode

Use a struct for small, immutable-feeling data with no real identity - a Point, a Color, a Money amount. Use a class for anything with real identity, larger data, or behavior that changes over the object's lifetime - a Post, a Customer, a Service. Most day-to-day domain modeling uses classes; structs are the exception, reached for deliberately.

Practice question: Explain why using a struct for a large object with 15 fields would generally be a bad idea, based on what you know about value type copying.

Topic 15: Nullable Types

Value types - int, bool, DateTime - cannot normally hold null; they always have some default value. Nullable value types, written as int?, opt into allowing null, useful for representing "no value provided" distinctly from an actual zero or false.

int? age = null;
int regularAge = 0;  // 0 is a real value,
                       // different from "no value"

if (age.HasValue)
{
    Console.WriteLine(age.Value);
}

// GetValueOrDefault - safe access with a fallback
int actualAge = age.GetValueOrDefault(18);

// Null-coalescing works here too
int displayAge = age ?? 18;

// Nullable reference types (C# 8+) - a compiler
// feature (not a runtime type change) that WARNS
// when a reference type that should never be null
// might actually be null
string? nullableName = null;   // explicitly allowed
string nonNullableName = "Alex"; // compiler warns if
                                   // this is ever assigned
                                   // null somewhere
Enter fullscreen mode Exit fullscreen mode

Practice question: Why might a nullable int? be a better choice than a regular int for a "discount percentage" field on an order, where most orders have no discount at all?

Topic 16: Type Conversion and Casting

Converting a value from one type to another - sometimes safe and implicit, sometimes requiring an explicit cast, and sometimes needing a parsing method entirely.

// Implicit conversion - safe, no data loss possible,
// compiler does it automatically
int number = 100;
double asDouble = number;  // int fits safely into double

// Explicit conversion (casting) - REQUIRED when data
// loss is possible, forces you to acknowledge the risk
double price = 19.99;
int roundedDown = (int)price;  // 19 - truncates,
                                 // does not round

// Parsing strings to numbers
string input = "42";
int parsed = int.Parse(input);        // throws if invalid
bool success = int.TryParse(input, out int result);
                                        // safe, returns
                                        // false instead of
                                        // throwing

// Convert class - handles more conversions, including
// null handling
string text = Convert.ToString(42);
int fromString = Convert.ToInt32("42");

// as vs cast for reference types
object obj = "hello";
string s1 = (string)obj;      // throws if obj isn't
                                // actually a string
string s2 = obj as string;    // returns null instead
                                // of throwing if it fails

// is keyword with pattern matching
if (obj is string str)
{
    Console.WriteLine(str.Length);
}
Enter fullscreen mode Exit fullscreen mode

Practice question: What is the actual difference between int.Parse() and int.TryParse()? When would you genuinely prefer one over the other?

Topic 17: Basic Exception Handling

Exceptions represent errors that occur during execution. try/catch lets code handle an error gracefully instead of crashing the entire program.

try
{
    int result = 10 / int.Parse("0");
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Cannot divide by zero: {ex.Message}");
}
catch (FormatException ex)
{
    Console.WriteLine($"Invalid number format: {ex.Message}");
}
catch (Exception ex)
{
    // Generic catch-all - should be LAST, since more
    // specific catches above it would never be reached
    // if this came first
    Console.WriteLine($"Unexpected error: {ex.Message}");
}
finally
{
    // ALWAYS runs, whether an exception occurred or not -
    // used for cleanup (closing files, connections, etc.)
    Console.WriteLine("Cleanup runs here regardless");
}

// Throwing your own exceptions
void ValidateAge(int age)
{
    if (age < 0)
    {
        throw new ArgumentException("Age cannot be negative", nameof(age));
    }
}

// Custom exception types
public class InsufficientFundsException : Exception
{
    public InsufficientFundsException(string message) : base(message) { }
}
Enter fullscreen mode Exit fullscreen mode

Practice question: Write a method that safely divides two numbers, catching a divide-by-zero error and returning 0 instead of letting the exception propagate.

Key Lessons

Value types copy on assignment; reference types share the same underlying object - this single distinction explains a huge share of "unexpected" C# behavior.

Arrays are fixed size; List resizes automatically - default to List unless the size is genuinely fixed and known.

Dictionary gives fast key-based lookup; HashSet gives fast existence checks with no duplicates.

Strings are immutable - every apparent modification creates a new string, which is exactly why StringBuilder exists for heavy concatenation.

ref and out both pass by reference, but out requires the method to assign a value before returning, commonly used in the TryParse pattern.

Structs and classes mirror the value versus reference type distinction for your own custom types - most everyday modeling uses classes.

What's Next

Part 2 covers mid-level concepts - LINQ, generics, delegates and events, interfaces versus abstract classes, and exception handling in real depth.

Summary

Every C# codebase, no matter how advanced, is built from these same basic pieces - variables and types, strings, collections, control flow, methods, and basic error handling. Interview questions on these topics aren't testing whether you've memorized syntax - they're testing whether the underlying model, value versus reference, mutable versus immutable, fixed versus resizable, is genuinely internalized, since that's what actually predicts whether unfamiliar code will behave the way you expect it to.


Originally published at TechStack Blog: https://www.techstackblog.com/post.html?slug=csharp-interview-prep-basics-part1

More from TechStack Blog: C# / .NET: https://www.techstackblog.com/category.html?cat=csharp
CS Fundamentals: https://www.techstackblog.com/category.html?cat=cs-fundamentals

Top comments (0)