Green checkmarks are incomplete: grading C# beyond correctness
A green check can prove output without proving the solution scales. Learn how performance, allocation, query, and structure gates turn a result into actionable feedback.
Submit a working solution on a correctness-only practice platform and you get the same reward: a green checkmark. It does not matter if your solution allocates a gigabyte, hammers the database with a thousand queries, or slows to a crawl as the input grows. The examples pass, so the answer is marked correct.
That checkmark is not false. It is incomplete.
In production, the O(n²) loop that sailed through the test cases melts when the input is a million rows. The innocent-looking LINQ query fans out into an N+1 stampede. The "working" service is a dependency tangle nobody dares to touch. None of these fail a narrow output contract, but all of them can fail the system that contract belongs to.
Correctness is not one bit. It is evidence across every constraint the software has to satisfy.
What a performance gate actually proves
A useful performance test is not a drag race between clever one-liners. It asks whether a solution has enough headroom for the scale promised by the problem. If an input can contain 200,000 values, the test should include that order of magnitude and give an efficient approach room to pass despite normal timing noise. An accidental quadratic approach should miss by a wide margin.
That distinction matters. Tiny timing differences rarely teach anything: garbage collection, machine scheduling, JIT compilation, and background work all add variation. A gate becomes useful when it separates complexity classes or catches a meaningful allocation pattern, not when it crowns a 4.1 ms run over a 4.3 ms run.
The order of proof matters too:
- Establish behavior first. Visible and hidden cases check the output, edge conditions, and failure behavior.
- Measure representative scale. Large cases exercise the constraints the prompt promised, rather than inventing a workload the solution was never meant to handle.
- Compare with a budget, not another person's laptop. The result is pass or fail against deliberate headroom on the grading server.
- Return the evidence. A timeout should name the case and budget. An allocation failure should show bytes used and allowed. Without those numbers, a red result is merely another opaque icon.
Practice what actually gets graded
Our thesis is simple: you get good at what your feedback loop measures. If the loop only measures correctness, that's all you'll train. So we measure what production measures:
- Wall-clock time against a budget, per test. Every puzzle has hidden tests with large inputs sized to expose the intended complexity. A naive solution does not receive a vague code review comment; it fails the test suite with a timeout and a bar showing how much of the budget it consumed.
- Allocated bytes. Speed is not the whole story in .NET. Every run reports managed-heap allocations, and puzzles where memory behavior is part of the lesson can enforce a hard budget.
- The actual SQL. Database puzzles run your LINQ against a real database and show you every query your code generates. The N+1 isn't an abstract warning. It's forty queries staring at you where one should be.
- Structure and architecture rules. Refactoring katas are graded on Roslyn-measured structural metrics, and architecture katas on design rules: dependency direction, layering, abstraction boundaries, checked automatically on every submission.
Those dimensions should be selective, not decorative. An algorithm puzzle does not need an architecture score, and a refactoring kata should not reward a microsecond difference in a tiny fixture. Each exercise should expose only the evidence its learning objective can justify. The scorecard is adaptive for that reason: an allocation value may be reported as information without affecting the verdict, while another puzzle can make the same dimension a hard constraint because avoiding a copy is the lesson. More metrics are not automatically better feedback. A learner needs to know which constraint matters, why it matters here, and what measured result would count as enough.
What your suite says
returns_expected_result
✓
handles_empty_input
✓
handles_duplicates
✓
sample_cases_3_of_3
✓
✓ All green 4 / 4
What production measures
wall-clock 840 ms / budget 200 ms
✗
allocations 1.2 GB
✗
40 queries where 1 belongs
✗
query plan: seq scan
✗
✗ Over budget same code
What that feels like
Take the classic warm-up, Two Sum. The obvious solution is two nested loops:
public int[] TwoSum(int[] nums, int target)
{
for (var i = 0; i < nums.Length; i++)
for (var j = i + 1; j < nums.Length; j++)
if (nums[i] + nums[j] == target)
return [i, j];
return [];
}
Correct? Completely. Submit it here and the sample cases pass, then a hidden test named "Large input (n = 200,000), must be O(n)" times out at its budget. The grade isn't a vague "try to do better." It's a hard fail with a number attached.
One dictionary later:
public int[] TwoSum(int[] nums, int target)
{
var seen = new Dictionary<int, int>();
for (var i = 0; i < nums.Length; i++)
{
if (seen.TryGetValue(target - nums[i], out var j))
return [j, i];
seen[nums[i]] = i;
}
return [];
}
The same behavioral cases still pass, while the large case now finishes with comfortable headroom. That contrast, felt rather than merely read about, is how Big-O stops being an interview fact and becomes an instinct. The exact milliseconds can vary; the change from quadratic growth to linear growth does not.
How to read the result instead of gaming it
Performance feedback is useful only if it changes the next decision. A timeout on a large input is usually a prompt to inspect complexity before micro-optimizing syntax. An allocation failure asks which copies, intermediate collections, or strings can be removed. A database query count asks whether work that belongs in one set-based query leaked into a loop. A structure rule asks whether responsibilities are tangled, not whether a method can be compressed onto fewer physical lines.
The practical loop looks like this:
- Keep the failing case and its budget visible.
- State the suspected cause in plain language: "membership lookup is linear and sits inside a linear loop."
- Change the design, not the stopwatch: introduce the dictionary, project in SQL, or remove the unnecessary copy.
- Resubmit and compare the failed dimension. Confirm that behavior stayed green.
- Stop when the solution has clear headroom and remains readable.
That last step protects against the opposite failure. The fastest solution is not automatically the best one. If two approaches both pass comfortably, prefer the one another engineer can understand and change. Performance budgets define a constraint; they are not permission to turn every method into a benchmark stunt. Our performance guide goes deeper on choosing complexity first and reducing allocations only when the evidence points there.
Measured where you can't fake it
One more thing matters: all of this is measured server-side, right next to the executing code, inside an isolated sandbox. Browser and network latency are outside the measured interval, and every eligible result uses the grading environment rather than a developer's laptop. That makes budgets and leaderboard results comparable, but not magically noiseless. Small fluctuations are normal, so clear headroom matters more than a single personal-best run.
(If "you run my code on your servers" raises an eyebrow, good, it should. We wrote up exactly how the pipeline and the sandbox work, because we'd want to know too.)
The result is a scorecard, not one ceremonial checkmark: behavior, runtime, allocation, and any track-specific rule are reported separately, with the evidence behind each verdict. The grading guide documents exactly what Submit measures, what remains hidden, and how timing fairness works.
The Free plan includes the full algorithm catalog with time and allocation evidence. If the green checkmark you distrust belongs to the suite itself, the free C# unit testing exercises run your tests against planted bugs to show what they would really catch. Otherwise, start with a solution you know is correct, then use the measurements to discover what "correct at scale" asks you to change. Choose a puzzle and run the experiment yourself.