DEV Community

Suraj Mishra
Suraj Mishra

Posted on Originally published at kingpin.ltd

Two mechanisms that look redundant, and why only one of them was

The setup: one product, two rankings

Kingpin shows the same listings on two surfaces. /feed is a swipeable media feed; /board is
the ladder people actually land on and share. They had two different ranking formulas:

ranked by
/feed rankCards() — bid baseline, shifted by crowd votes, eviction at −5
/board candidateListings()effectiveCents only: money + 5%/day decay

So the crowd could dethrone you inside the swipe feed, and only there. On the main page, money
still bought a permanent position. The product's central claim — the crowd decides who stays on
top
— was silently false on the most-visited surface.

Nobody designed this. It's residue: the board is the pre-pivot leaderboard (Phases 0–8), the feed
came with the pivot (Phases 9–15), and the old surface was never retired. Architectural drift
usually looks like this — not a bad decision, but an old decision nobody re-opened.

The fix was to rank both through the same function. What made it interesting was what surfaced
while doing it.


Decision 1: decay looked redundant. It wasn't.

Once the board ranked on votes, a fair question came up: why decay a bid automatically at all?
Downvotes already push bad listings down — isn't decay doing the same job twice?

It sounds right. It's wrong, and the reason is worth internalising.

They answer different questions:

  • A downvote answers "is this bad?" It requires a human to actively dislike the thing.
  • Decay answers "you paid in March — why are you still #1 in September?" It requires nobody.

The gap between those is indifference. A listing that's merely dull collects roughly zero
net votes forever. Nobody downvotes a product for being old. So removing decay means: pay once,
avoid annoying anyone, hold the top spot permanently — precisely the thing the product promises
cannot happen. Indifference is not a downvote, and a system relying on downvotes alone is
relying on indifference to do a job it can't do.

There was a second, less obvious cost. Decay is the only reason the escalating refuel price
exists — positions must erode for a re-bid to mean anything. Deleting decay would have silently
retired a revenue model and left working, tested code permanently unreachable. That consequence
was nowhere near the code being discussed.

But the instinct behind the question was still correct. With ~2 listings and effectively no
voters, decay was the only force acting on the board, it moved one direction, and the only way
to recover position was to pay again. That doesn't read as "the crowd decides." It reads as the
platform billing people for standing still — and it punishes founders for the platform's lack
of traffic, which isn't their fault.

So the fix wasn't to the mechanism, it was to its conditions: decay stays fully implemented
behind FLAG_DECAY, defaulting off, flipped on deliberately once vote volume makes the crowd
the dominant force.

// lib/scoring.mjs — one gate, honoured by BOTH effectiveCents and bidBaseCents,
// so no surface can disagree with another about what a listing is worth.
export const decayEnabled = (flags = {}) => flags?.decay === true;
Enter fullscreen mode Exit fullscreen mode

Why a manual flag and not an automatic traffic threshold: a threshold that trips on its own
would erode every paid position the instant it crossed, with no warning to any founder. A flag
someone flips means the moment is chosen, announceable, and reversible. Same end state, no ambush.

The generalisable lesson

Before deleting a mechanism that looks redundant, write down the exact question each mechanism
answers. If the questions differ, the redundancy is an illusion — and check what else depends
on it, because load-bearing things are rarely labelled.

A useful tell: the mechanisms overlapped in effect (both push a listing down) but not in
trigger (dislike vs. time). Overlapping effects are what make two things look like one.


Decision 2: founders don't vote

A founder has a direct financial interest in every rival's rank. Once votes moved paid position
on the main board, leaving founders able to vote would have turned the crowd signal into a proxy
war between paying competitors — and made the cheapest listing on the platform a ticket to
downvote the leader.

Two design calls worth recording:

Scope: all voting, not just others' listings. "Founders can't vote on other people's
listings" immediately invites "…so they can vote for themselves?" — and self-voting is the more
blatant manipulation of the two. One rule with no edge cases beats a narrower rule that's harder
to defend.

This rejection is loud, when every other rejection in that handler is silent. The vote
pipeline deliberately fails quietly, because a clear error tells a bot exactly which control it
tripped. This case is the opposite: a published policy, not a detection. Returning a fake
success to a paying founder would be lying to a customer.

// vote.mjs — 403 with a reason, not the usual silent { applied: false }
if (identity.FounderId) return forbidden('Founders cannot vote. …');
Enter fullscreen mode Exit fullscreen mode

Silence is a security tool, not a default. Use it where disclosure helps an attacker tune.
Where the rule is public anyway, silence buys nothing and costs trust.

The limit, stated honestly

The link is per-device, not per-person, so it is a deterrent and a statement of the rule, not
an unconditional wall
— the same per-IP/per-ASN caps, confidence weighting, and velocity
shadow-ban that resist any other bad actor are what actually backs it up.

That distinction matters more than it sounds. A control described as airtight when it isn't
produces worse decisions later, because someone downstream stops building the defence that
actually does the work.


A small thing that keeps recurring

Wiring FLAG_DECAY meant adding it to the root stack and forwarding it to both nested
stacks. The existing FLAG_STREAK was never forwarded — it only ever used each nested stack's
default. Harmless while the default is what you want; invisible breakage the day someone tries to
flip it.

This is the third time today a parameter has been declared somewhere it couldn't actually be set
from (see entry 001's HostingerFrom). A config value you cannot demonstrate changing the
behaviour of is not configuration — it's a constant with extra steps.
Worth testing the flip,
not just the default, whenever a flag is introduced.


Originally published on the Kingpin build log.

Top comments (0)