skip to content

Is brute-forcing every visit order of 12 delivery stops viable, and how do you know?

level: seniorimportance: should knowfreq 50%

answer

  1. count the orderings before writing code
  2. one factorial, evaluated on paper first
  3. roughly half a billion arrangements
  4. each extra stop multiplies, never adds
  5. thirteen stops costs thirteen times more

basics

~20 s

Barely, and only offline: 12! = 479,001,600 orderings is a few hundred million evaluations, roughly seconds to minutes on one core. But 13! is 6.2 billion and 15! is 1.3 trillion, so the approach dies within three more stops.

solid answer

~50 s

Do the count before writing any code. Visiting 12 distinct stops in every order is `12! = 479,001,600` — about `4.8 x 10^8` candidate routes. Multiply by the per-route work (a dozen additions and a comparison, call it tens of nanoseconds) and you land in the seconds-to-minutes range on one core: viable as an offline batch job, not inside a request. The number that matters, though, is the *growth*: adding a thirteenth stop multiplies the work by 13, a fourteenth by 14 again, so `15!` is already `1.3 x 10^12` and the approach is finished. Symmetry helps only a little — fixing the starting depot removes the rotations and leaves `11! = 39,916,800`, a 12x cut that buys roughly one extra stop. So exhaustive ordering is a tactic for a small fixed count, and the moment the stop count is an input rather than a constant, you need pruning or a different algorithm class.

go deeper

for a junior

Be ready to compute or estimate 12! and say plainly that trying every order only works for a small, fixed number of items. The takeaway to carry is that each extra item multiplies the total rather than adding to it.

for a middle

Explain the arithmetic end to end: about 4.8 x 10^8 orderings times the per-candidate work gives the runtime, and a thirteenth stop multiplies that by thirteen rather than adding a fixed amount.

for a senior

Do the sizing before writing code and say what you would do once the count can grow. Be clear that symmetry reductions and parallelism buy a constant factor, shifting the wall by about one item without changing its shape.

for a principal

Own the call between an exact exhaustive run and a fast approximate one: what the business actually loses from a near-optimal route, against the cost of building and maintaining something more complex than a loop.

## Size the space before you write the loop The discipline this question tests is arithmetic-before-code. Someone proposes "just try every order and keep the best". Before debating the implementation, count the orders. Twelve distinct stops, every visiting sequence: twelve choices for the first stop, eleven for the second, and so on. That is `12! = 479,001,600`, roughly `4.8 x 10^8`. ## Turning a count into a verdict A count alone is not a verdict; multiply it by the cost of one candidate. Evaluating one route means summing about a dozen leg distances and comparing to the best so far — tens of nanoseconds if the data is in cache, more if each leg costs a lookup. At that rate `4.8 x 10^8` routes is seconds to a couple of minutes on a single core. Verdict: fine as an offline batch job or a one-off analysis, not something to run inside a user-facing request, and worth parallelising if it runs regularly. That verdict is fragile in exactly one direction. If each candidate costs a millisecond instead of nanoseconds — a network call, a database read, a heavy scoring function — the same `4.8 x 10^8` becomes over five thousand days. The count is the easy half; the per-candidate cost is what actually decides. ## The growth rate is the real answer What should be said before the timing estimate is how the number moves. | n | 2^n (subsets of n things) | n! (orderings of n things) | |---|---|---| | 4 | 16 | 24 | | 10 | 1,024 | 3,628,800 | | 12 | 4,096 | 479,001,600 | | 15 | 32,768 | 1,307,674,368,000 | | 20 | 1,048,576 | 2,432,902,008,176,640,000 | Two readings matter. First, **each extra item multiplies rather than adds**. Thirteen stops is not "a bit more work" than twelve; it is thirteen times the work — `6.2 x 10^9`. Fourteen is `8.7 x 10^10`. Fifteen is `1.3 x 10^12`. A brute force that is comfortable at twelve is hopeless at fifteen, and no amount of constant-factor engineering closes that gap: doubling the machine's throughput buys you the difference between twelve and thirteen stops, once. Second, **orderings and selections are not the same ceiling**. Counting which of n options are switched on gives `2^n`; counting the sequences of n things gives `n!`. The factorial column overtakes the exponential one at `n = 4` and then runs away — at n = 20 they differ by twelve orders of magnitude. So the very first question about any exhaustive plan is which of the two shapes it actually has. A test matrix of twelve independent on/off settings is `2^12 = 4,096` configurations, entirely enumerable; twelve *ordered* steps is `12!` and is a different conversation. ## Symmetry buys a constant, not a growth rate If every route returns to its depot, rotations of the same cycle are the same route, so fixing a starting stop divides by 12: `11! = 39,916,800`. If direction is also irrelevant, halve again to `19,958,400`. Both are worthwhile — a 24x reduction turns minutes into seconds — and both are irrelevant to the shape of the problem. Dividing by a constant, or even by one factorial step, shifts the wall by about one item; it does not move it. ## What you say instead of "it will be slow" A senior answer names the decision point. Exhaustive ordering is defensible when the item count is a small *constant* fixed by the domain and the result is computed offline. It stops being defensible the moment the count becomes an input, because the failure is not gradual: the job runs in a minute at twelve stops and does not finish at sixteen. At that point the options are pruning partial routes against the best complete route found so far (which helps enormously in practice but leaves the worst case untouched), an exact algorithm that reuses work across shared partial routes, or a heuristic that returns a near-optimal route quickly and accepts that it may not be the best. Choosing among those is a product decision as much as a technical one — how much is optimality actually worth against the cost of building and maintaining something more complex than a loop. ## The habit to take away Write the count down before you write the code. It takes thirty seconds, it is a single factorial or a single power of two, and it converts an argument about implementation into an arithmetic fact everyone in the room can check.

  • A teammate says the same sizing applies to a test matrix of 12 independent on/off settings. Is it the same number?
    No, and the gap is five orders of magnitude. Independent on/off settings give `2^12 = 4,096` configurations; orderings of 12 things give `12! = 479,001,600`. Selections are exponential, arrangements are factorial, and the factorial overtakes the exponential from `n = 4` onward. Settle which shape the problem has before estimating anything else.
  • The route returns to its depot, so rotations are equivalent. How much does that save?
    Fixing the starting stop removes the 12 rotations of each cycle, leaving `11! = 39,916,800` — a 12x cut. If direction is also irrelevant, halve again to `19,958,400`. Real savings, but they shift the wall by roughly one stop: symmetry reductions buy a constant factor and never change the growth rate.
  • At what point do you stop tuning the brute force and change approach?
    As soon as the stop count is an input rather than a domain constant. Constant-factor work — parallelism, a tighter inner loop, abandoning partial routes already worse than the best complete one — each buys about one more stop. Past roughly thirteen to fifteen stops you need a genuinely different algorithm, or a heuristic and an explicit decision that a near-optimal route is acceptable.

saying these in an interview costs you the question

  • Calls factorial growth fine for small n without counting
  • Treats 12! and 2^12 as the same order of magnitude
  • Thinks one more stop adds constant extra work
  • Optimises the inner loop instead of the growth rate
  • Confuses counting orderings with counting subsets

context