Coding Interview Workshop · Lesson 19

Refactor review code around a pure decision

A coding-review task often mixes validation, calculation, logging and persistence in one method.

Clarify the observable contract

A coding-review task often mixes validation, calculation, logging and persistence in one method. Extract a pure decision with explicit input and output before changing behavior. This sample validates quantity, price and stock, distinguishes insufficient stock from invalid input and computes a checked total only for approved input. Records describe data; they do not make a stock reservation or commit an order.

Write the observable decision table before extracting helpers. Valid quantity within stock gives an approved checked total; valid quantity beyond stock is insufficient; malformed numeric inputs are invalid. This table is the refactor's regression contract, while logging or storage adapters can be tested separately at their own boundaries.

Walk through the verified program

Two units at one hundred fifty cents with stock three are approved for three hundred cents. Four units exceed the same stock and produce insufficient with zero total. Zero quantity is invalid before stock comparison. The harness exposes those outcomes without a logger, database, network call or dependency container.

The zero total on insufficient or invalid decisions is a deliberate non-approved result convention. Callers must inspect Status rather than interpreting every zero as a valid free order. Error precedence is visible because invalid input returns before availability is considered, making behavior stable when multiple rules fail together.

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

var approved=Decide(new Order(2,150,3)); var denied=Decide(new Order(4,150,3));
Console.WriteLine(approved.Status+":"+approved.TotalCents); Console.WriteLine(denied.Status+":"+denied.TotalCents);
Console.WriteLine("invalid:"+Decide(new Order(0,150,3)).Status);
static Decision Decide(Order order) {
    if(order.Quantity<=0 || order.UnitCents<0 || order.Stock<0) return new("invalid",0);
    if(order.Quantity>order.Stock) return new("insufficient",0);
    return new("approved",checked(order.Quantity*order.UnitCents));
}
sealed record Order(int Quantity,long UnitCents,int Stock);
sealed record Decision(string Status,long TotalCents);

Account for time, storage and allocations

The decision uses constant time and fixed-size result data. Record construction allocates small objects in this class-record version. Extracting a pure function improves deterministic testing but is not by itself a performance optimization. Overflow remains an explicit exceptional outcome for otherwise valid but unrepresentable totals.

A pure function has no hidden service calls, but it can still throw, allocate and depend on its stated numeric representation. Do not equate purity with zero cost or totality over every possible input. Here checked overflow remains a documented exceptional path requiring an explicit caller boundary.

Review assumptions and likely defects

Refactoring should preserve observable error precedence unless the contract intentionally changes. Checking stock first could classify a malformed negative-stock request differently. A successful decision is also not evidence that stock remains available when another caller later performs an external write.

Record equality helps compare complete decisions in tests, but records do not automatically validate data or provide deep immutability for arbitrary referenced objects. These records contain scalar fields, which keeps the fixture simple. A future record containing a mutable collection would need additional ownership decisions.

Practice one: discriminate edge cases

Add fixtures where several validation rules fail simultaneously and specify the desired precedence. Hint: assert decision Status and total, not an internal sequence of helper calls. Compare the refactor against a written behavior table.

Use an independent table with expected Status and Total for ordinary, insufficient, zero-quantity, negative-price and negative-stock inputs. Hint: test precedence with a malformed combination that could also appear unavailable. Assert observable outcomes rather than the names or order of extracted private methods.

Practice two: defend an extension

Design the boundary between this decision and an atomic persistence operation. Decide who owns concurrency checks, idempotency keys and logging redaction. Explain why dependency injection should support that boundary rather than replace the business contract.

The persistence boundary can accept a valid proposed decision and perform a conditional stock update in one storage transaction. It should report a concurrency failure if availability changed, rather than trusting an earlier pure calculation as a reservation. Idempotency and logging belong around that operation with explicit ownership and data-retention policies.

Evaluate correctness and interview reasoning

An effective code-review answer preserves the behavior table, extracts a deterministic core and identifies the remaining external consistency problem. It shows why the same three-hundred-cent calculation can be tested without mocking a logger or database. It also avoids claiming that records, dependency injection or a pure function reserve stock. Senior discussion focuses on who publishes the decision and how a changed stock value is detected before committing an effect. The related multi-file project contributes twelve independent boundary checks, providing a concrete review artifact rather than a stylistic refactor with no behavioral evidence. Reviewers should also preserve the distinction between a valid free order and a denied decision carrying zero. Comparing only Total would miss that observable status difference.

A working example

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

var approved=Decide(new Order(2,150,3)); var denied=Decide(new Order(4,150,3));
Console.WriteLine(approved.Status+":"+approved.TotalCents); Console.WriteLine(denied.Status+":"+denied.TotalCents);
Console.WriteLine("invalid:"+Decide(new Order(0,150,3)).Status);
static Decision Decide(Order order) {
    if(order.Quantity<=0 || order.UnitCents<0 || order.Stock<0) return new("invalid",0);
    if(order.Quantity>order.Stock) return new("insufficient",0);
    return new("approved",checked(order.Quantity*order.UnitCents));
}
sealed record Order(int Quantity,long UnitCents,int Stock);
sealed record Decision(string Status,long TotalCents);

Expected output

approved:300
insufficient:0
invalid:invalid
Try this
Add fixtures where several validation rules fail simultaneously and specify the desired precedence. Hint: assert decision Status and total, not an internal sequence of helper calls. Compare the refactor against a written behavior table.

Further reading

Interview questions for this lesson