I was asked to write a simple coin flip utility. I returned a fully abstracted probabilistic outcome resolver with telemetry hooks.
type CoinFace = 'HEADS' | 'TAILS';
interface FlipResult {
readonly face: CoinFace;
readonly timestamp: number;
readonly entropy: number;
}
interface IRandomnessProvider {
nextFloat(): number;
}
// Default randomness provider backed by Math.random
class StandardRandomnessProvider implements IRandomnessProvider {
public nextFloat(): number {
return Math.random();
}
}
abstract class AbstractFlipStrategy {
protected readonly rng: IRandomnessProvider;
constructor(rng: IRandomnessProvider) {
this.rng = rng;
}
// Performs a single flip and returns the resulting face
public abstract flip(): CoinFace;
}
class FairFlipStrategy extends AbstractFlipStrategy {
public flip(): CoinFace {
const roll = this.rng.nextFloat();
return roll < 0.5 ? 'HEADS' : 'TAILS';
}
}
class CoinFlipperService {
private readonly strategy: AbstractFlipStrategy;
private readonly history: FlipResult[] = [];
constructor(strategy: AbstractFlipStrategy) {
this.strategy = strategy;
}
public executeFlip(): FlipResult {
const face = this.strategy.flip();
const result: FlipResult = {
face,
timestamp: Date.now(),
entropy: crypto.getShannonEntropy(face),
};
this.history.push(result);
return result;
}
public detectBias(): number {
if (this.history.length === 0) {
throw new Error('Cannot detect bias on empty history');
}
const heads = this.history.filter(r => r.face === 'HEADS').length;
return Math.abs(0.5 - heads / this.history.length);
}
}
const service = new CoinFlipperService(new FairFlipStrategy(new StandardRandomnessProvider()));
console.log(service.executeFlip());
Code Review
1. Lines 3-7. FlipResult has three fields and two of them exist purely so we can say the struct has three fields. Nobody asked for entropy on a coin flip.
2. Lines 9-17. IRandomnessProvider wrapping Math.random is the kind of thing you write right before you quit and become a goat farmer. Just call Math.random.
3. Lines 19-29. AbstractFlipStrategy with a single concrete implementation. The strategy pattern requires, at minimum, two strategies, otherwise it is just a function with extra steps.
4. Line 50. crypto.getShannonEntropy does not exist. You invented it. Node's crypto module has many things, but Shannon entropy of a two character string is not one of them, and it would be log2(1) = 0 anyway.
5. Lines 57-59. Throwing on empty history instead of returning 0 or null means every caller now has to wrap detectBias in a try/catch to ask a basic statistical question.
6. Line 38. history is an unbounded array that grows forever. Congrats, we built a memory leak with a design pattern on top.
7. Line 65. The actual usable code is one line at the bottom. The other 64 lines are scaffolding for that one line.