A gate evaluates 40 rules against an inventory of 5,000 listeners — where does the time go?
answer
- two terms, and they multiply
- the input is paid for once per call
- every rule walks every item
- one more rule, charged to every caller
- shrink the input before tuning rules
basics
~20 sMostly in two terms that multiply: marshalling the whole inventory once per call, then every rule walking every listener. Cost tracks rule count times input size, so one extra rule is paid on every call and on every item.
solid answer
~40 sThree costs stack. First the input is marshalled: the caller serialises 5,000 listeners, ships them, and the engine parses them. That term grows with input size alone and is often the largest one. Second, evaluation: a rule checking each listener's minimum TLS version is linear in items, and forty such rules are forty passes over five thousand entries, so rule count and input size multiply. A rule that cross-references each listener against a list of approved settings multiplies again. Third, assembling the answer — one message per violation, cheap unless everything fails at once. The practical consequence is that adding a rule is not free for the team that added it: it is paid by every caller on every call, including calls where the rule could not possibly have matched.
go deeper
Know that a decision's time depends both on how many rules run and on how much data they are handed, and that sending more data than the rules read costs time on every single call.
Break the cost into marshalling the input, iterating items once per rule, and building the answer, and explain why rule count times item count is the right mental model for the middle term.
Demonstrate that you attribute cost before optimising — know whether the 700 ms is parsing or evaluation — and reach for input projection and narrower match predicates before rewriting any rule.
Frame input size as a capacity-planning variable: the gate's budget is a function of an estate that keeps growing, so what matters is periodic re-measurement and a graph of input size, not a one-off tuning exercise.
## The cost model in one line For a gate that hands an engine a document and a set of rules, decision cost is roughly: `marshalling(input size) + Σ over rules (cost of that rule on that input) + building the answer` and the middle term, for the common case of rules that walk a collection, behaves like **rule count × item count**. That product is the mental model worth carrying into an interview, because it explains both of the ways a gate silently goes over budget: someone added rules, or the estate grew. ## Term one: marshalling Before any rule runs, the input has to become data the engine can evaluate. The caller serialises it, it crosses a socket, and the engine parses it. This term depends only on how many bytes you sent — not on how many rules you have and not on how many of those bytes anything reads. It is the term people forget, and on a large inventory it frequently dominates: an engine can honestly report single-digit milliseconds of evaluation while the caller waits most of a second. This is also why *trimming the input is the first fix*, ahead of touching any rule. If the rules read two fields per listener and the caller ships thirty, roughly nine tenths of the marshalling cost buys nothing. Projecting the input down to the fields the rules actually read cuts the bytes, cuts the parse, and cuts what every rule has to walk — one change, three savings, and no change to rule logic, so nothing can start deciding differently. ## Term two: evaluation, and why it multiplies A rule such as "every listener must negotiate at least TLS 1.2" has to look at every listener; there is no way to answer it having looked at five. That makes it linear in the number of items. Forty independent rules over the same collection are forty linear passes: the work is proportional to rules × items even though each individual rule looks cheap. Some rules are worse than linear. A rule that checks each listener against a list of approved cipher settings does items × list-length comparisons. A rule that compares every listener with every other listener — "no two listeners on the same port with different minimums" — is quadratic in items, and a quadratic rule that was invisible at 200 listeners is the whole budget at 5,000. The cheapest structural fix here is a **narrow, cheap predicate evaluated before the body**: match on the resource type, a label or a single field so that calls where the rule cannot apply exit after one comparison instead of walking the whole document. Most calls are calls the rule was never going to fire on, so that is where the savings live. ## Term three: the answer Usually negligible: a message per violation. It stops being negligible in exactly one situation, and it is a situation you will meet — the day a new rule is wrong, or the day someone ships a change that violates an existing rule 5,000 times at once. Then the response is 5,000 messages, the serialisation cost of the answer rivals the input, and the slowest calls are the failing ones. Capping the number of reported violations is a cheap guard. ## The consequence people miss: added rules are charged to everyone A team proposing a rule is proposing a permanent tax on every call through the gate, including all the calls their rule is irrelevant to. That is why "it's only two milliseconds" is the wrong unit: the right unit is two milliseconds × every deploy in the organisation, forever, and the next request will also be for only two milliseconds. ## The consequence people miss twice: the input grows on its own A gate that comfortably met a 300 ms budget last year with 500 listeners can miss it this year with 5,000 without a single rule changing. Nothing regressed; the estate grew, and both the marshalling term and every rule's iteration grew with it. Latency budgets therefore need re-measuring on a cadence, and *input size itself* is worth graphing — it is the leading indicator that predicts the breach before the p99 does. ## Order of attack when you are over budget 1. **Attribute the cost.** Split the caller-observed time into marshalling and evaluation before touching anything. The two have entirely different fixes and guessing wrong wastes a week. 2. **Shrink the input.** Project to the fields the rules read; filter to the items that can actually be affected; send what changed rather than the whole estate; batch a huge inventory into several calls if the caller can tolerate it. 3. **Narrow the match predicates** so irrelevant calls exit early. 4. **Only then** look at individual rule bodies, and start with any that iterate a collection inside a loop over another collection. Most gates that are over budget are over budget for reason 2, and get fixed without anyone reading a rule.
- What is the first change you make when the decision is over budget?Shrink the input. Project only the fields the rules actually read, drop items that cannot be affected, and send what changed rather than the whole inventory. That cuts serialisation, parsing and every rule's iteration in one move, and it needs no change to rule logic, so no decision can start coming out differently. It is both the largest and the safest win, and it comes before anyone rewrites a rule body.
- Why can a gate that met its budget last year miss it this year with no rule change?Because cost tracks input size and the estate grew. Ten times the listeners is ten times the parsing and ten times each rule's iteration, with nothing having regressed. Latency budgets need re-measuring against the current input size, and input size is worth graphing in its own right — it predicts the breach before the p99 does.
- How do you make a rule cheap on the calls where it cannot apply?Give it a narrow, cheap predicate that runs before the body — match on the resource type or a single field — so a non-matching call exits after one comparison instead of walking the whole document. The savings come from the calls the rule was never going to fire on, which are almost always the majority.
saying these in an interview costs you the question
- Assuming an extra rule only costs time when it matches
- Optimising a rule body before shrinking the input
- Ignoring serialisation and parsing of the input entirely
- Treating today's inventory size as a fixed quantity
- Sending whole objects when the rules read two fields