Coding Interview Workshop · Lesson 3

Build a boundary matrix for slicing

Define a slice using a start index and a count, with an exclusive end implied by their relationship.

Clarify the observable contract

Define a slice using a start index and a count, with an exclusive end implied by their relationship. A zero-length slice at the array end is valid; negative values, starts beyond the end and ranges extending past the array are invalid. The result is an independent array copy. The supplied function assumes a non-null array and validates the numeric bounds before constructing a span.

Write the accepted range as zero less than or equal to start, zero less than or equal to count, and count no greater than length minus start after start is known valid. The validation order matters because subtraction is safe only after the start bound has been established.

Walk through the verified program

Start one and count two select twenty and thirty. The harness then checks an empty suffix, an empty source and four rejected numeric pairs, yielding six boundary checks. In particular, int.MaxValue is rejected before subtracting it from the length. No start-plus-count expression can wrap and make an invalid range appear valid.

Short-circuit evaluation rejects negative starts and starts beyond the length before evaluating the final difference. The empty end slice is intentional, not a loophole. The six counted checks exclude the initial displayed copy, so the final line is a check count rather than the length of the sliced data.

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

int[] data = { 10, 20, 30 };
Console.WriteLine(string.Join(",", Slice(data, 1, 2)));
int checks = 0;
void Check(bool value) { if (!value) throw new Exception("slice check"); checks++; }
Check(Slice(data, 3, 0).Length == 0); Check(Slice(Array.Empty<int>(), 0, 0).Length == 0);
foreach (var pair in new[] { (-1, 1), (0, -1), (2, 2), (int.MaxValue, 1) }) {
    bool rejected = false; try { Slice(data, pair.Item1, pair.Item2); } catch (ArgumentOutOfRangeException) { rejected = true; } Check(rejected);
}
Console.WriteLine("boundary checks:" + checks);
static int[] Slice(int[] data, int start, int count) {
    if (start < 0 || count < 0 || start > data.Length || count > data.Length - start) throw new ArgumentOutOfRangeException();
    return data.AsSpan(start, count).ToArray();
}

Account for time, storage and allocations

Validation is constant-time. Copying count elements takes O(count) time and O(count) result storage. Returning a span instead would change lifetime and aliasing constraints, while returning an array makes ownership simple. Auxiliary state is constant beyond the required copy; the input remains unchanged.

A returned copy has a useful ownership guarantee: later mutations to that copy do not change the original elements. An array of reference-type elements would still share the referenced objects, however. Distinguish copying the container from deeply cloning every object when generalizing this integer-only helper.

Review assumptions and likely defects

Using start + count <= length without addressing overflow is not equivalent for arbitrary integer arguments. Off-by-one errors also appear when callers confuse count with an inclusive final index. Null handling must be made explicit if this helper becomes a public API rather than relying on a later accidental exception.

The parameter names should stay start and count through examples, error messages and tests. A caller supplying an inclusive last index is a different API, requiring conversion that can itself overflow. Null-array behavior also deserves an explicit precondition or argument exception when moving beyond this classroom function.

Practice one: discriminate edge cases

Add start equal to length with count one, and start zero with count equal to length. Hint: one crosses the exclusive end and one copies the entire array. Verify that changing the returned array does not mutate the source.

Build the test matrix from dimensions rather than listing random values: empty versus nonempty source, zero versus positive count, start before zero versus at zero versus at the end, and ordinary versus extreme integers. This makes missing boundaries visible and gives each failed assertion a reason to exist.

Practice two: defend an extension

Design a zero-copy alternative that returns ReadOnlySpan and state who owns the backing memory, how long the result is valid and whether concurrent source mutation is allowed. Explain why allocation reduction changes the interface contract.

A read-only span prevents writes through that view but does not freeze the underlying array. Its lifetime cannot be stored in arbitrary heap objects, and callers must not assume snapshot consistency. A clear API can offer both a copying method and a borrowed-view method with distinct names and documented ownership requirements.

Evaluate correctness and interview reasoning

The correctness argument is a numeric-bound proof followed by a well-defined copy operation. The interview answer should mention the exclusive end, the valid empty suffix and the reason for avoiding unchecked start-plus-count arithmetic. It should also account for required output allocation instead of calling the method constant-space. A useful review includes an aliasing test: modify the returned array and verify the original remains intact. When proposing a span optimization, explain the changed lifetime and mutation contract first. Lower allocation is a tradeoff with callers, not an unconditional improvement over an owned result.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

int[] data = { 10, 20, 30 };
Console.WriteLine(string.Join(",", Slice(data, 1, 2)));
int checks = 0;
void Check(bool value) { if (!value) throw new Exception("slice check"); checks++; }
Check(Slice(data, 3, 0).Length == 0); Check(Slice(Array.Empty<int>(), 0, 0).Length == 0);
foreach (var pair in new[] { (-1, 1), (0, -1), (2, 2), (int.MaxValue, 1) }) {
    bool rejected = false; try { Slice(data, pair.Item1, pair.Item2); } catch (ArgumentOutOfRangeException) { rejected = true; } Check(rejected);
}
Console.WriteLine("boundary checks:" + checks);
static int[] Slice(int[] data, int start, int count) {
    if (start < 0 || count < 0 || start > data.Length || count > data.Length - start) throw new ArgumentOutOfRangeException();
    return data.AsSpan(start, count).ToArray();
}

Expected output

20,30
boundary checks:6
Try this
Add start equal to length with count one, and start zero with count equal to length. Hint: one crosses the exclusive end and one copies the entire array. Verify that changing the returned array does not mutate the source.

Further reading

Interview questions for this lesson