C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
156: Implementing Sorting and Searching Algorithms in C#
I've spent a lot of time reviewing code from developers who are just starting to tackle algorithmic thinking, and there is one mistake that pops up constantly: the belief that "if the code produces the right output, the algorithm is correct."
The "It Works on My Machine" Complexity Trap
Let's say you're building a system to sort a list of 10 user profiles by their join date. You write a simple Bubble Sort because it's intuitive—you just swap adjacent elements until the list is sorted. You run it with your 10 test users, it finishes in a fraction of a millisecond, and you check it into the codebase. You think you've solved the problem.
// The "naive" approach that feels fine during testing
public void BubbleSort(List<User> users)
{
int n = users.Count;
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (users[j].JoinDate > users[j + 1].JoinDate)
{
var temp = users[j];
users[j] = users[j + 1];
users[j + 1] = temp;
}
}
}
}
Here is where the trap snaps shut. When that code hits production and you suddenly have 10,000 users, your application doesn't just slow down—it crawls. Bubble Sort has a time complexity of $O(n^2)$. To put that in perspective: for 10 items, you do roughly 100 operations. For 10,000 items, you're looking at 100,000,000 operations. That is the difference between an instantaneous UI and a frozen application.
Thinking in Big O and Choosing the Right Tool
To write professional C#, you have to stop looking at whether the code works and start looking at how it scales. This is where we move from "naive" sorting to "efficient" sorting. In the real world, you'll almost always use Array.Sort() or LINQ's OrderBy() because they use highly optimized versions of QuickSort or Timsort. But as an engineer, you need to understand the mechanics of these algorithms to know when they'll fail you.
Take QuickSort, for example. Instead of blindly swapping every element, it uses a "divide and conquer" strategy. It picks a pivot and partitions the array into two halves: those smaller than the pivot and those larger. This brings the average complexity down to $O(n \log n)$. For our 10,000 users, that's roughly 133,000 operations instead of 100 million. I'll be honest: the math is the most important part of this lesson, not the syntax.
public void QuickSort(int[] array, int left, int right)
{
if (left < right)
{
int pivotIndex = Partition(array, left, right);
QuickSort(array, left, pivotIndex - 1);
QuickSort(array, pivotIndex + 1, right);
}
}
private int Partition(int[] array, int left, int right)
{
int pivot = array[right];
int i = left - 1;
for (int j = left; j < right; j++)
{
if (array[j] <= pivot)
{
i++;
(array[i], array[j]) = (array[j], array[i]); // C# Tuple swap
}
}
(array[i + 1], array[right]) = (array[right], array[i + 1]);
return i + 1;
}
Searching follows a similar logic. If your data is unsorted, you're stuck with a Linear Search—checking every single item until you find a match. But if you've already invested the effort to sort your data, you can use a Binary Search. Instead of scanning, you jump to the middle, see if your target is higher or lower, and discard half the remaining list with every single step. This turns a search through a million items from a million checks into roughly 20 checks.
public int BinarySearch(int[] sortedArray, int target)
{
int low = 0;
int high = sortedArray.Length - 1;
while (low <= high)
{
int mid = low + (high - low) / 2;
if (sortedArray[mid] == target) return mid;
if (sortedArray[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1; // Not found
}
My advice? Don't memorize these implementations for the sake of it. Instead, focus on the trade-off. Sorting takes time, but it makes searching nearly instantaneous. If you're searching a list once, don't bother sorting it. If you're searching it thousands of times, sort it first.
📋 Practical Task
Build a High-Score Leaderboard with Binary Search Insertion
Instead of sorting a list every time a new score is added (which is inefficient), your task is to build a Leaderboard class that maintains a sorted list of integers in descending order.
Requirements:
- Implement a method
AddScore(int score). This method must not useList.Sort()or LINQ. Instead, it should use a binary search logic to find the exact index where the score should be inserted to keep the list sorted. - Implement a method
GetRank(int score)that returns the 1-based rank of a given score using binary search. If the score isn't in the list, return -1. - Ensure the leaderboard is sorted from Highest to Lowest.
Test Case: If you add scores in the order [50, 100, 75, 20], the internal list should be [100, 75, 50, 20]. Calling GetRank(75) should return 2.
There are no comments for now.