An algorithm can be clear in prose and still change meaning when translated into code. The prose says “an earlier index”; the implementation accepts the current index. The design says “visit each cell once”; the code repeatedly schedules the same cell. Implementation is where the state, update order, and data structures must make the reasoning concrete.
Think of building from a measured drawing. Knowing that a shelf fits does not guarantee that the cuts match the drawing. You need a correspondence between each measurement and the part being built. In code, that correspondence is between the algorithm’s claims and the variables and operations that maintain them.
Decide what each variable means before using it
Write a brief state description:
| Design concept | Implementation choice | Meaning |
|---|---|---|
| Previously seen values |
Map<number, number> |
Each value maps to one earlier original index |
| Current position |
index |
The position whose complement is being checked |
| Missing complement |
undefined from get |
No earlier index was stored for that value |
| No answer |
[] |
The scan completed without a valid pair |
The missing-value representation is important. Index 0 is a valid index, even though it is falsy in JavaScript. A truthiness check cannot distinguish “found at zero” from “not found.”
The examples here assume bounded integers for which JavaScript’s number arithmetic represents the relevant values and sums exactly. A problem requiring larger exact integers needs a deliberate numeric representation rather than an unnoticed change in arithmetic.
Implement Two Sum from its invariant
This version returns any pair of distinct original indices, or [] if no pair exists. It does not modify the input:
/**
* Finds two distinct original indices whose values sum to the target.
* Assumes the relevant integer arithmetic is exact with JavaScript numbers.
* @param {number[]} numbers
* @param {number} target
* @returns {number[]}
*/
const twoSum = (numbers, target) => {
/** @type {Map<number, number>} */
const seen = new Map()
for (const [index, value] of numbers.entries()) {
const complement = target - value
const earlierIndex = seen.get(complement)
if (earlierIndex !== undefined) {
return [earlierIndex, index]
}
seen.set(value, index)
}
return []
}
Three choices carry the proof:
- The map stores indices because indices are the required output.
- Lookup happens before insertion, so a match comes from an earlier position.
-
The explicit
undefinedcomparison accepts index zero.
For [6, 2, 9, 3], target 9, the function returns [0, 3]. For [3, 3], target 6, it returns [0, 1]. For [3], target 6, it returns []. Those examples exercise separate obligations: nonadjacency, equal values at distinct positions, and prevention of self-pairing.
Under the usual expected constant-time hash-table model, this is expected O(n) time and O(n) additional space. That complexity belongs to these operations and assumptions; it is not a property of every program that happens to mention a map.
Implement island counting with explicit traversal state
For a rectangular grid of string cells '0' and '1', count components connected in four directions. This implementation uses a stack so that a long island does not require a similarly deep chain of recursive calls:
/**
* Counts four-direction land components without changing the grid.
* @param {string[][]} grid A rectangular grid of '0' and '1' cells
* @returns {number}
*/
const countIslands = (grid) => {
const rowCount = grid.length
const columnCount = grid[0]?.length ?? 0
if (rowCount === 0 || columnCount === 0) return 0
const visited = new Set()
const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]
let count = 0
/**
* Claims one unvisited land cell and schedules it for exploration.
* @param {number} row
* @param {number} column
* @param {number[]} stack
* @returns {void}
*/
const scheduleLand = (row, column, stack) => {
if (row < 0 || row >= rowCount) return
if (column < 0 || column >= columnCount) return
if (grid[row][column] !== '1') return
const id = row * columnCount + column
if (visited.has(id)) return
visited.add(id)
stack.push(id)
}
for (const [row, cells] of grid.entries()) {
for (const [column, cell] of cells.entries()) {
const id = row * columnCount + column
if (cell !== '1' || visited.has(id)) continue
count += 1
const stack = []
scheduleLand(row, column, stack)
while (stack.length > 0) {
const current = stack.pop()
if (current === undefined) break
const currentRow = Math.floor(current / columnCount)
const currentColumn = current % columnCount
for (const [rowOffset, columnOffset] of directions) {
scheduleLand(currentRow + rowOffset, currentColumn + columnOffset, stack)
}
}
}
}
return count
}
The numeric cell identifier preserves position because the grid is rectangular and columnCount is fixed. For the intended grid sizes, those identifiers are exact integers. A ragged grid would need a different contract and representation.
Marking a cell when it is scheduled prevents different neighbors from adding the same cell again. Incrementing the count only when the outer scan discovers unvisited land makes one completed flood correspond to one island. Traversing a component can visit cells the outer scan will encounter later; those cells are already marked and do not begin new islands.
The outer scan examines RC cells. Each land cell enters the stack at most once and checks four neighbors, giving expected O(RC) time with the set operations and O(RC) extra space in the worst case. A nested traversal does not multiply the work into O((RC)²) because floods do not repeatedly process the same land cells.
Make defaults serve the contract
Short code is useful when its defaults match the problem. Check them when they do not:
- Numeric ascending order needs a numeric comparison rule; a language’s default ordering may be textual.
- Sorting may mutate an array; copy when preserving the input is part of the contract.
- A set records presence, not frequency. Counting duplicates requires additional state.
- Slicing a large range creates work and storage that index-based pseudocode may not have included.
- A recursive traversal uses call-stack space even when it allocates no explicit stack object.
You do not need to guard every impossible input. You do need to make implementation assumptions match the stated problem rather than relying on convenient behavior accidentally.
Recheck the algorithm you actually wrote
After coding, read the implementation in the vocabulary of the design. Point to where the invariant is initialized, where it is restored, and where progress happens. Count expensive operations inside repeated work. A hidden search, copy, or sort can change the complexity you originally budgeted.
Then execute tests that correspond to obligations. In the Two Sum example, [2, 7] with target 9 specifically checks a match at index zero; it does not check whether the same position can be reused. Testing [3, 3] and [3] with target 6 distinguishes duplicates from self-reuse. For islands, vertical and diagonal cases distinguish the connection rules.
An executed check mark reports a real run against specified tests. It does not replace the correctness argument, and it should not imply access to platform tests that were never run.
Practice the move
Before typing the next loop, name the meaning of its state and finish this sentence:
This update must happen before that update because __.
If the answer refers to the contract or invariant, you have a reason to preserve the order. If the answer is only “that is how the template looked,” trace a small boundary case until the relationship becomes clear.