<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Karuha</title>
    <description>The latest articles on DEV Community by Karuha (@karuha).</description>
    <link>https://dev.to/karuha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3841947%2Fd1e11c25-c7f9-49c8-b876-d9ef993f9f9e.jpg</url>
      <title>DEV Community: Karuha</title>
      <link>https://dev.to/karuha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/karuha"/>
    <language>en</language>
    <item>
      <title>Instagram lets you add 5 bio links now. TikTok still hides the field until 1,000 followers</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Wed, 23 Sep 2026 04:24:03 +0000</pubDate>
      <link>https://dev.to/karuha/instagram-lets-you-add-5-bio-links-now-tiktok-still-hides-the-field-until-1000-followers-3782</link>
      <guid>https://dev.to/karuha/instagram-lets-you-add-5-bio-links-now-tiktok-still-hides-the-field-until-1000-followers-3782</guid>
      <description>&lt;p&gt;Instagram's help center says you can put &lt;strong&gt;five&lt;/strong&gt; websites on a profile. That has been true since April 2023. A lot of the tutorials still circulating were written when the cap was one, so people keep asking why a second link "isn't showing up" when the field for it is sitting right there under Edit profile → Links.&lt;/p&gt;

&lt;p&gt;TikTok is the opposite problem. There is no count. Per &lt;a href="https://support.tiktok.com/en/getting-started/setting-up-your-profile/linking-another-social-media-account" rel="noopener noreferrer"&gt;TikTok's own support page&lt;/a&gt;, the website field does not appear at all until you have 1,000 followers or a Verified Business Account. Below that, rearranging bio text will not conjure a link. The limit is a condition, not a number.&lt;/p&gt;

&lt;p&gt;YouTube sits in the middle: up to 14 channel profile links on the About tab (&lt;a href="https://support.google.com/youtube/answer/13748639?hl=en" rel="noopener noreferrer"&gt;YouTube Help&lt;/a&gt;). Links inside a regular video description are clickable once advanced features are on. The same URLs pasted into a Shorts description or a Shorts comment are not. YouTube documents that split; it still trips people because "put it in the description" sounds like one rule.&lt;/p&gt;

&lt;p&gt;I went back to each platform's help pages on 19 September 2026 instead of trusting search snippets. Snippets cache. Help pages get edited. The two disagree for years sometimes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that actually changes
&lt;/h2&gt;

&lt;p&gt;If you have fewer than five destinations and you live on Instagram, the native field probably does the job. You do not need a second product in the stack just to host a list of URLs.&lt;/p&gt;

&lt;p&gt;The gap that is still real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instagram will not tell you which of those five links got tapped unless you wrap each one yourself.&lt;/li&gt;
&lt;li&gt;Reordering them is five separate edits, not a drag handle.&lt;/li&gt;
&lt;li&gt;None of those native fields exist outside the app, so there is nothing to put on a printed card or in an email signature.&lt;/li&gt;
&lt;li&gt;TikTok's 1,000-follower gate is the sharper one. Anyone under that line has no native link at all, which is the original job &lt;a href="https://www.nameplate.cc/blog/instagram-tiktok-youtube-bio-link-limits?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=coldstart" rel="noopener noreferrer"&gt;a bio-link page&lt;/a&gt; was built for.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to audit your own setup
&lt;/h2&gt;

&lt;p&gt;Start by asking whether the platform is giving you a &lt;strong&gt;count&lt;/strong&gt; or a &lt;strong&gt;condition&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A count (Instagram 5, YouTube 14) means you are choosing which links make the cut.&lt;/li&gt;
&lt;li&gt;A condition (TikTok 1,000 followers) means the field does not exist yet. No amount of bio-text gymnastics will make it appear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instagram states the number in the first sentence of the help article. TikTok hides the threshold in the "important things to know" list under the how-to steps, which is why people skim past it.&lt;/p&gt;

&lt;p&gt;I work on Nameplate, a one-page site for portfolios, QR codes, and digital business cards. We do not cap how many links sit on a page, free included. We also do not scrape these platform limits automatically — if TikTok drops the follower gate next quarter, someone has to notice and rewrite this by hand. That is the honest version.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Late Events Do Not Error, They Vanish: Build a Watermark Drill in Node.js</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Mon, 21 Sep 2026 10:57:15 +0000</pubDate>
      <link>https://dev.to/karuha/late-events-do-not-error-they-vanish-build-a-watermark-drill-in-nodejs-3p2d</link>
      <guid>https://dev.to/karuha/late-events-do-not-error-they-vanish-build-a-watermark-drill-in-nodejs-3p2d</guid>
      <description>&lt;p&gt;A tumbling one-minute window closes when the watermark passes its end, not when the clock does. Seven events stamped 10:00:58 that arrive at 10:01:12 have exactly two possible fates: they correct a result you already published, or they increment a counter nobody reads. Neither one throws an exception.&lt;/p&gt;

&lt;p&gt;That is the whole bug. Late data is not an error condition, so nothing in your dashboards turns red while the number is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "late" has no error path
&lt;/h2&gt;

&lt;p&gt;Take a pipeline that reports purchases per region in one-minute tumbling windows. Mobile clients buffer events while they are offline and flush a batch after a reconnect, so the order events arrive on the wire is only loosely related to the timestamps inside them.&lt;/p&gt;

&lt;p&gt;The 10:00 window is emitted at 10:01:05. A batch lands at 10:01:12 carrying seven events stamped 10:00:58 and a few stamped 10:01:03.&lt;/p&gt;

&lt;p&gt;If the job runs with the default allowed lateness (zero, in Flink), those seven events are gone. Not delayed, not retried, not written to a dead-letter queue: dropped, because the window's state was purged a second after it fired. The metric for 10:00 is now 7 purchases short, and a pipeline that drops 3% of events has the same 5xx rate as one that drops 0%.&lt;/p&gt;

&lt;p&gt;That is why this drill is worth an evening: the failure mode is not a crash you can grep for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two knobs everyone conflates
&lt;/h2&gt;

&lt;p&gt;Almost everyone I have run this with reaches for one number first: how much out-of-orderness to tolerate. In Flink's vocabulary there are two, and they do different jobs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Knob&lt;/th&gt;
&lt;th&gt;Decides&lt;/th&gt;
&lt;th&gt;Costs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Out-of-orderness&lt;/td&gt;
&lt;td&gt;How far the watermark trails the newest timestamp seen&lt;/td&gt;
&lt;td&gt;Reporting latency on &lt;strong&gt;every&lt;/strong&gt; window, including the ones that were never late&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allowed lateness&lt;/td&gt;
&lt;td&gt;How long window state survives after the first result goes out&lt;/td&gt;
&lt;td&gt;State size: open windows × keys × retention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Out-of-orderness sets &lt;em&gt;when the first result goes out&lt;/em&gt;. Allowed lateness sets &lt;em&gt;whether a late event becomes a correction or a drop&lt;/em&gt;. Raising the first does not remove drops. It moves the boundary and delays everything you report.&lt;/p&gt;

&lt;p&gt;Flink's window lifecycle is explicit about the second knob: a window is "completely removed when the time (event or processing time) passes its end timestamp plus the user-specified &lt;code&gt;allowed lateness&lt;/code&gt;". Their example: with 5-minute tumbling windows and 1 minute of allowed lateness, the window for 12:00–12:05 is removed when the watermark passes 12:06. Internally the condition is &lt;code&gt;maxTimestamp + allowedLateness&lt;/code&gt;, where &lt;code&gt;maxTimestamp&lt;/code&gt; is the window end minus one millisecond.&lt;/p&gt;

&lt;p&gt;Beam calls the same knob &lt;code&gt;withAllowedLateness&lt;/code&gt;; Kafka Streams calls it the grace period on a windowed aggregation. The name changes, the contract does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the aggregator
&lt;/h2&gt;

&lt;p&gt;Roughly 55 lines, no dependencies, no cluster. &lt;code&gt;outOfOrdernessMs&lt;/code&gt; and &lt;code&gt;allowedLatenessMs&lt;/code&gt; are the two knobs above, &lt;code&gt;onEmit&lt;/code&gt; is your sink, and &lt;code&gt;onDrop&lt;/code&gt; is the metric you should have had all along.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createWindowAggregator&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;outOfOrdernessMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;allowedLatenessMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;onEmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="nx"&gt;onDrop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;maxEventTs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;watermark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pushed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;windows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// windowStart -&amp;gt; { fired, revision, keys: Map&amp;lt;key, total&amp;gt; }&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;windowStartOf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fire&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;due&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fired&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;watermark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;onEmit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;windowStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;windowEnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;purge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Flink removes a window when the watermark passes maxTimestamp + allowedLateness,&lt;/span&gt;
    &lt;span class="c1"&gt;// where maxTimestamp is windowEnd - 1. For whole-millisecond windows that is windowEnd + lateness.&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;allowedLatenessMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;watermark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;pushed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;maxEventTs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxEventTs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// monotonic: a skewed event cannot pull it back&lt;/span&gt;
      &lt;span class="nx"&gt;watermark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxEventTs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;outOfOrdernessMs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;windowStartOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;allowedLatenessMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;watermark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;dropped&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;onDrop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="nx"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// This window already went out. Same (window, key), new total: a correction.&lt;/span&gt;
          &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revision&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nf"&gt;onEmit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;windowStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;windowEnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revision&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nf"&gt;fire&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;purge&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;advanceTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// A source heartbeat: "nothing older than ts can still arrive". Idle partitions need this,&lt;/span&gt;
      &lt;span class="c1"&gt;// otherwise the global watermark stops moving.&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxEventTs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;maxEventTs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;watermark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxEventTs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;outOfOrdernessMs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;fire&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;purge&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;watermark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;watermark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pushed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;openWindows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three choices in there are the ones an interviewer will poke at.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The watermark only moves forward.&lt;/strong&gt; &lt;code&gt;maxEventTs&lt;/code&gt; is a running maximum, so a skewed client clock cannot resurrect purged state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firing and purging are separate steps.&lt;/strong&gt; A window can fire and still be open for corrections — that is what allowed lateness buys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every drop is reported.&lt;/strong&gt; Not logged as an error, because it is not one: counted, so you can chart it and alert on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five contracts to prove
&lt;/h2&gt;

&lt;p&gt;A correct-looking aggregator and a correct one differ in the boundary cases, and those are the drill. Five assertions, with &lt;code&gt;node:assert/strict&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A window fires once, when the watermark crosses its end.&lt;/strong&gt; Two events land in window &lt;code&gt;[0, 60000)&lt;/code&gt; and nothing fires yet. Then an event at &lt;code&gt;ts: 70000&lt;/code&gt;, with a 5-second out-of-orderness bound, moves the watermark to 65000 — and exactly one emission comes out: &lt;code&gt;{ windowStart: 0, key: 'us', total: 7, revision: 0 }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. An event inside allowed lateness is a correction, not a new row.&lt;/strong&gt; Add an event stamped 59,500 after that emission:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="nx"&gt;_500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;windowStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;windowEnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same &lt;code&gt;windowStart&lt;/code&gt;, same &lt;code&gt;key&lt;/code&gt;, higher &lt;code&gt;total&lt;/code&gt;, &lt;code&gt;revision&lt;/code&gt; incremented. If your sink appends, that window now has two rows — 7 and 12 — and a downstream &lt;code&gt;SUM&lt;/code&gt; reports 19 where the truth is 12.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. An event past &lt;code&gt;windowEnd + allowedLateness&lt;/code&gt; is dropped, never re-homed.&lt;/strong&gt; This is the assertion that catches the bug people actually ship: the old window is missing from the state map, so the late event gets counted into the &lt;em&gt;current&lt;/em&gt; one. Push an event far enough ahead to move the watermark past the purge point, then push one stamped 30,000 into that purged window. The dropped list grows by one, the emitted totals do not move, and nothing lands in the current window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The watermark never goes backwards.&lt;/strong&gt; With the same 5-second bound, push &lt;code&gt;ts: 200000&lt;/code&gt; (watermark 195,000), then &lt;code&gt;ts: 1000&lt;/code&gt;. The watermark stays at 195,000 and the skewed event is dropped rather than reopening a purged window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Conservation: nothing is unaccounted for.&lt;/strong&gt; After any stream, the sum of the final totals you emitted plus the value of the events you dropped must equal the value you pushed in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalTotals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;windowStart&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// last emission per (window, key) wins&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nf"&gt;finalTotals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()].&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lostValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counted&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lostValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pushedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// pushedValue = sum of every event.value you pushed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that identity fails, you have a bug you cannot see from any dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive fix, measured
&lt;/h2&gt;

&lt;p&gt;The tempting response to all of this is to raise the out-of-orderness bound until nothing is late. So I ran the same 500 events through the same code with one constant changed. Event timestamps are uniformly random across a 5-minute range, replayed in a slightly shuffled arrival order (a seeded PRNG, so these numbers reproduce exactly), then flushed with a heartbeat:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Events dropped&lt;/th&gt;
&lt;th&gt;Value never counted&lt;/th&gt;
&lt;th&gt;Emissions before the flush&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5s out-of-orderness, 30s lateness&lt;/td&gt;
&lt;td&gt;384 of 500&lt;/td&gt;
&lt;td&gt;1,874 of 2,429 (77%)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;600s out-of-orderness, 30s lateness&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both runs satisfy the conservation identity. The second one looks perfect and costs you ten minutes of reporting delay on every window — and it still drops anything later than ten minutes. A bound is a promise about your input, not a property you can configure away. When the input does not honor the promise, the drop counter is the only thing standing between you and a number you cannot defend in a review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the interviewer asks next
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The global watermark is a minimum, not a maximum
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FJSV7aW5pdDogeyd0aGVtZSc6J2Jhc2UnLCd0aGVtZVZhcmlhYmxlcyc6eydwcmltYXJ5Q29sb3InOicjMWUzYTVmJywncHJpbWFyeVRleHRDb2xvcic6JyNmOGZhZmMnLCdwcmltYXJ5Qm9yZGVyQ29sb3InOicjMzhiZGY4JywnbGluZUNvbG9yJzonIzdkZDNmYycsJ3NlY29uZGFyeUNvbG9yJzonIzBmMTcyYScsJ3RlcnRpYXJ5Q29sb3InOicjMzM0MTU1JywnZm9udEZhbWlseSc6J0ludGVyLCB1aS1zYW5zLXNlcmlmJ319fSUlCmZsb3djaGFydCBUQgogIFMxWyJwYXJ0aXRpb24gMzogbGFzdCB0cyAxMDowMTo0MCJdIC0tPiBXWyJnbG9iYWwgd2F0ZXJtYXJrID0gbWluIG92ZXIgcGFydGl0aW9ucyJdCiAgUzJbInBhcnRpdGlvbiA3OiBsYXN0IHRzIDA5OjU4OjAyIl0gLS0-IFcKICBTM1sicGFydGl0aW9uIDk6IGlkbGUsIG5vIGV2ZW50cyJdIC0tPiBXCiAgVyAtLT4gUlsib25lIHN0YWxsZWQgb3IgaWRsZSBwYXJ0aXRpb24gaG9sZHMgYmFjayBldmVyeSB3aW5kb3ciXQo%3D%3Fwidth%3D900%26height%3D420%26bgColor%3D0f172a" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FJSV7aW5pdDogeyd0aGVtZSc6J2Jhc2UnLCd0aGVtZVZhcmlhYmxlcyc6eydwcmltYXJ5Q29sb3InOicjMWUzYTVmJywncHJpbWFyeVRleHRDb2xvcic6JyNmOGZhZmMnLCdwcmltYXJ5Qm9yZGVyQ29sb3InOicjMzhiZGY4JywnbGluZUNvbG9yJzonIzdkZDNmYycsJ3NlY29uZGFyeUNvbG9yJzonIzBmMTcyYScsJ3RlcnRpYXJ5Q29sb3InOicjMzM0MTU1JywnZm9udEZhbWlseSc6J0ludGVyLCB1aS1zYW5zLXNlcmlmJ319fSUlCmZsb3djaGFydCBUQgogIFMxWyJwYXJ0aXRpb24gMzogbGFzdCB0cyAxMDowMTo0MCJdIC0tPiBXWyJnbG9iYWwgd2F0ZXJtYXJrID0gbWluIG92ZXIgcGFydGl0aW9ucyJdCiAgUzJbInBhcnRpdGlvbiA3OiBsYXN0IHRzIDA5OjU4OjAyIl0gLS0-IFcKICBTM1sicGFydGl0aW9uIDk6IGlkbGUsIG5vIGV2ZW50cyJdIC0tPiBXCiAgVyAtLT4gUlsib25lIHN0YWxsZWQgb3IgaWRsZSBwYXJ0aXRpb24gaG9sZHMgYmFjayBldmVyeSB3aW5kb3ciXQo%3D%3Fwidth%3D900%26height%3D420%26bgColor%3D0f172a" alt="A global watermark is the minimum across partitions" width="900" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With multiple input partitions, the global watermark is the &lt;strong&gt;minimum&lt;/strong&gt; of the per-partition watermarks. One partition whose newest event is five minutes old holds back every window downstream, no matter how far ahead the others are — and a partition that goes idle holds it back forever. That is why real jobs configure an idle timeout, and why the code above has &lt;code&gt;advanceTo&lt;/code&gt;: a source has to be able to say "nothing older than this is coming" even when it has no events to send.&lt;/p&gt;

&lt;p&gt;If you have only run single-partition jobs, this is the follow-up that separates "read the Flink docs" from "operated this".&lt;/p&gt;

&lt;h3&gt;
  
  
  How to pick the two numbers
&lt;/h3&gt;

&lt;p&gt;Measure, do not guess. Take &lt;code&gt;arrival_ts - event_ts&lt;/code&gt; per source from your ingestion log, read the p99, set allowed lateness around twice that, and &lt;strong&gt;alert on the drop counter&lt;/strong&gt;. Pick the out-of-orderness bound from how much reporting delay the consumer tolerates — that is a product decision, not a data one.&lt;/p&gt;

&lt;h3&gt;
  
  
  The sink has to upsert
&lt;/h3&gt;

&lt;p&gt;Corrections mean the same &lt;code&gt;(windowStart, key)&lt;/code&gt; can be emitted more than once, so the sink has to upsert on that pair. Append-only inserts double-count after every correction, and delete-then-insert is not idempotent when the emit is at-least-once, which it is. "We dedupe later" only works if the dedupe key is that same pair — in which case you have built the upsert with extra steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replay determinism
&lt;/h3&gt;

&lt;p&gt;Because events are assigned to windows by event time and the watermark is a pure function of the maximum timestamp seen, replaying the same stream through the same code produces the same final totals. Processing-time windows do not have that property, which is why a backfill of last Tuesday disagrees with what the dashboard showed on Tuesday.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is the timestamp in the payload the event time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only if you trust the producer's clock. Client clocks drift, and a device set a day forward pushes the watermark past every open window. Validate skew at ingestion and prefer a broker timestamp when the client cannot be trusted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a large allowed lateness cost anything beyond memory?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;State lifetime. Every retained window keeps its keys and accumulators until the watermark passes &lt;code&gt;windowEnd + allowedLateness&lt;/code&gt;, so lateness multiplies key space by retention. It also extends how long a correction can arrive, which downstream consumers have to be ready for either way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I cannot change the pipeline?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recompute the affected windows from the raw events — for a bounded window, a batch correction is cheaper than a streaming rewrite — publish it alongside the streaming number, and label which one is provisional. The mistake is publishing one number and letting the drop counter be the only place the difference shows up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to rehearse the follow-ups
&lt;/h2&gt;

&lt;p&gt;Those two questions arrive as a conversation, not as code, and that is the part a static list of questions cannot rehearse. If you want a live partner for them, aceround.app — AI interview assistant — runs mock sessions where the interviewer follows up on what you just said instead of reading the next item off a list, and its &lt;a href="https://www.aceround.app/blog/data-analyst-interview-ai/" rel="noopener noreferrer"&gt;data analyst interview guide&lt;/a&gt; walks through the SQL, case-study, and AI-usage rounds a data loop actually puts in front of you. The drill above needs nothing but Node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before your next data interview
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Write the aggregator above from memory, then run the five assertions until they pass without looking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Break it on purpose: set allowed lateness to zero and watch contract 2 fail; set it to an hour and watch contract 3 stop firing. Both failures answer "why not just make the bound bigger".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In under a minute, out loud: why the global watermark is a minimum, and what an idle partition does to an otherwise ready window.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Say your sink contract in one sentence: "upsert on &lt;code&gt;(window_start, key)&lt;/code&gt;, because emissions are corrections and delivery is at-least-once."&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Drafting was AI-assisted. The window lifecycle and &lt;code&gt;allowedLateness&lt;/code&gt; semantics were checked against Flink's &lt;a href="https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/dev/datastream/operators/windows/" rel="noopener noreferrer"&gt;Windows&lt;/a&gt; documentation; the drop and correction numbers come from the script in this article, which prints &lt;code&gt;all contracts hold&lt;/code&gt; on Node 24. Beam documents the same setting as &lt;code&gt;withAllowedLateness&lt;/code&gt; in its &lt;a href="https://beam.apache.org/documentation/programming-guide/" rel="noopener noreferrer"&gt;programming guide&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>dataengineering</category>
      <category>career</category>
    </item>
    <item>
      <title>Outlook's Business Card button is not a signature link</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Wed, 16 Sep 2026 04:35:47 +0000</pubDate>
      <link>https://dev.to/karuha/outlooks-business-card-button-is-not-a-signature-link-4i7f</link>
      <guid>https://dev.to/karuha/outlooks-business-card-button-is-not-a-signature-link-4i7f</guid>
      <description>&lt;p&gt;Outlook has a button labeled &lt;strong&gt;Business Card&lt;/strong&gt;. A lot of digital-card tools tell you to put a card in your email signature. Those two instructions look like they solve the same problem. They do not.&lt;/p&gt;

&lt;p&gt;One of them attaches a real &lt;code&gt;.vcf&lt;/code&gt; file to every message. The other is a hyperlink. Mixing them up is how you end up on Spiceworks asking why every email you send also ships a file you never attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different objects
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A hosted card URL.&lt;/strong&gt; You copy a public link (often a &lt;code&gt;/v/&lt;/code&gt; address that returns a vCard) and hyperlink your name, a line of text, or a small image. The email itself carries no contact data. The recipient has to click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Outlook's native Electronic Business Card.&lt;/strong&gt; In desktop Outlook's signature editor there is a literal &lt;strong&gt;Business Card&lt;/strong&gt; button. Pick a contact from your local Outlook Contacts and Outlook drops in a card-shaped image &lt;em&gt;and&lt;/em&gt; attaches the underlying &lt;code&gt;.vcf&lt;/code&gt; to every message that uses that signature. &lt;a href="https://support.microsoft.com/en-us/outlook/include-an-electronic-business-card-in-your-e-mail-signature" rel="noopener noreferrer"&gt;Microsoft documents this&lt;/a&gt;. There is no toggle to keep the image and drop the attachment.&lt;/p&gt;

&lt;p&gt;If you wanted a quiet link on your name and you used that button, the attachment is working as designed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a hosted &lt;code&gt;/v/&lt;/code&gt; link actually sends
&lt;/h2&gt;

&lt;p&gt;When the URL is a live vCard endpoint rather than a static file, a typical response looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Type: text/vcard; charset=utf-8
Content-Disposition: attachment; filename="dana.vcf"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;attachment&lt;/code&gt; is the part that makes a phone show "add to contacts" instead of dumping &lt;code&gt;BEGIN:VCARD&lt;/code&gt; into a browser tab. The signature still only contains a hyperlink. Gmail and Outlook will not magically render a card preview from that &lt;code&gt;href&lt;/code&gt;; nothing in the email format does that for an ordinary link.&lt;/p&gt;

&lt;p&gt;The useful property is that the URL stays the same when you change your title. Mail already sitting in someone's inbox still points at the current card. Outlook's attached &lt;code&gt;.vcf&lt;/code&gt; is a snapshot from the moment you built the signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each one actually shows up
&lt;/h2&gt;

&lt;p&gt;Outlook's rich card view is a desktop Outlook rendering. Send that signature to Gmail or Apple Mail and the recipient usually sees a &lt;code&gt;.vcf&lt;/code&gt; attachment and maybe an embedded image, not the paper-card layout. If most of your recipients are not on desktop Outlook, you are maintaining a feature they mostly cannot see.&lt;/p&gt;

&lt;p&gt;A plain hyperlink degrades the same way in every client: it is a link. Image blocking does not affect it, because there is no image to block unless you added one yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup, without fighting the wrong dialog
&lt;/h2&gt;

&lt;p&gt;For the link version:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the public card URL.&lt;/li&gt;
&lt;li&gt;Open the ordinary HTML/text signature editor — not Outlook's &lt;strong&gt;Business Card&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Select your name, insert a hyperlink, paste the URL.&lt;/li&gt;
&lt;li&gt;Send a test to your phone. Desktop preview is the layer least likely to show a broken link.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Outlook's native version: place the cursor, click &lt;strong&gt;Business Card&lt;/strong&gt;, pick the local contact. Keep that contact's fields current. Editing a web profile will not update it.&lt;/p&gt;

&lt;p&gt;Gmail has no native business-card control. Hyperlink text, or insert an image and hyperlink that image. The image carries no contact data on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  This does not make anyone save the contact
&lt;/h2&gt;

&lt;p&gt;An attached &lt;code&gt;.vcf&lt;/code&gt; still needs the recipient to open and confirm. A hyperlink still needs a click. A name-as-link in a signature is a way for someone who already wants to reach you to save your details, not a broadcast channel. A card or QR handed over in person converts better, because it is used at the moment of interest.&lt;/p&gt;

&lt;p&gt;If you also need a printable version of the same page, a &lt;a href="https://www.nameplate.cc/tools/qr-code-generator?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=coldstart" rel="noopener noreferrer"&gt;QR code that points at the same URL&lt;/a&gt; is the print-side of the same object.&lt;/p&gt;

&lt;p&gt;I work on &lt;a href="https://www.nameplate.cc/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=coldstart" rel="noopener noreferrer"&gt;Nameplate&lt;/a&gt;, which serves those &lt;code&gt;/v/&lt;/code&gt; links. The Outlook-vs-hyperlink split above is the same whether you host the vCard yourself or use a builder.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>career</category>
      <category>email</category>
    </item>
    <item>
      <title>CodeSignal GCA Banks Your Highest Submit, Even After a Syntax Error</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Tue, 15 Sep 2026 05:00:28 +0000</pubDate>
      <link>https://dev.to/karuha/codesignal-gca-banks-your-highest-submit-even-after-a-syntax-error-12oj</link>
      <guid>https://dev.to/karuha/codesignal-gca-banks-your-highest-submit-even-after-a-syntax-error-12oj</guid>
      <description>&lt;p&gt;The live CodeSignal GCA is four coding tasks in 70 minutes, scored 200–600. Forum threads that still quote 300–850 are describing a retired scale. The IDE only keeps a task if you hit Submit before you leave it, and it grades your highest-scoring submit even if the last one is a syntax error.&lt;/p&gt;

&lt;p&gt;Search still returns &lt;code&gt;codesignal gca&lt;/code&gt;, &lt;code&gt;codesignal gca score&lt;/code&gt;, and &lt;code&gt;codesignal gca 70 minutes&lt;/code&gt; as live queries. The first hit is CodeSignal's own knowledge base: the &lt;a href="https://support.codesignal.com/hc/en-us/articles/360040370853" rel="noopener noreferrer"&gt;structure article&lt;/a&gt; (19 Aug 2026) and the &lt;a href="https://support.codesignal.com/hc/en-us/articles/360051960134" rel="noopener noreferrer"&gt;rules and setup article&lt;/a&gt; (25 Jul 2026). Read those two pages before another LeetCode hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the live GCA, not the 2022 one?
&lt;/h2&gt;

&lt;p&gt;CodeSignal's structure article is short: four questions of varying difficulty, you split the 70 minutes however you want, all questions are visible. The rules article adds the caveat most roundups skip: &lt;strong&gt;70 minutes unless the company configured a different duration&lt;/strong&gt;. If the invite says 90, believe the invite, not a Reddit recap of Capital One's default.&lt;/p&gt;

&lt;p&gt;Scoring is a single Assessment Score from &lt;strong&gt;200 to 600&lt;/strong&gt;. CodeSignal documented the conversion off the old 300–850 "Coding Score" in 2023 and still publishes the 200–600 range on &lt;a href="https://support.codesignal.com/hc/en-us/articles/13408542717079" rel="noopener noreferrer"&gt;Understanding Assessment Score&lt;/a&gt; (updated 24 Aug 2026). A screenshot of an 800 from 2022 is not a 2026 cutoff.&lt;/p&gt;

&lt;p&gt;Certified setup happens before the timer. Face photo, then photo ID, then a checkbox list of rules. The timer does not start until those steps finish. There is a Practice button on that same screen if you have never used the in-browser IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the editor autosave when you jump to Q4?
&lt;/h2&gt;

&lt;p&gt;No. The rules page is explicit: you can move between tasks, &lt;strong&gt;but you must submit before leaving a task or the code does not save&lt;/strong&gt;. People lose a working Q2 because they tab over to peek at Q4, change one loop, and come back to a blank or stale editor.&lt;/p&gt;

&lt;p&gt;Submit is cheap. The same article says you may submit as many times as you need. Do that before you touch another tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FJSV7aW5pdDogeyd0aGVtZSc6J2Jhc2UnLCd0aGVtZVZhcmlhYmxlcyc6eydwcmltYXJ5Q29sb3InOicjMWUzYTVmJywncHJpbWFyeVRleHRDb2xvcic6JyNmOGZhZmMnLCdwcmltYXJ5Qm9yZGVyQ29sb3InOicjMzhiZGY4JywnbGluZUNvbG9yJzonIzdkZDNmYycsJ3NlY29uZGFyeUNvbG9yJzonIzBmMTcyYScsJ3RlcnRpYXJ5Q29sb3InOicjMzM0MTU1JywnZm9udEZhbWlseSc6J0ludGVyLCB1aS1zYW5zLXNlcmlmJ319fSUlCmZsb3djaGFydCBMUgogIEFbIlEyIGluIGVkaXRvciJdIC0tPnwiU3VibWl0InwgQlsiQmFua2VkIHNjb3JlIl0KICBBIC0tPnwiU3dpdGNoIHRhc2sifCBDWyJOb3Qgc2F2ZWQiXQogIEIgLS0-fCJMYXRlciBzeW50YXggZXJyb3IifCBEWyJHQ0Egc3RpbGwgZ3JhZGVzIGJhbmtlZCBzdWJtaXQiXQo%3D%3Fwidth%3D1000%26height%3D420%26bgColor%3D0f172a" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FJSV7aW5pdDogeyd0aGVtZSc6J2Jhc2UnLCd0aGVtZVZhcmlhYmxlcyc6eydwcmltYXJ5Q29sb3InOicjMWUzYTVmJywncHJpbWFyeVRleHRDb2xvcic6JyNmOGZhZmMnLCdwcmltYXJ5Qm9yZGVyQ29sb3InOicjMzhiZGY4JywnbGluZUNvbG9yJzonIzdkZDNmYycsJ3NlY29uZGFyeUNvbG9yJzonIzBmMTcyYScsJ3RlcnRpYXJ5Q29sb3InOicjMzM0MTU1JywnZm9udEZhbWlseSc6J0ludGVyLCB1aS1zYW5zLXNlcmlmJ319fSUlCmZsb3djaGFydCBMUgogIEFbIlEyIGluIGVkaXRvciJdIC0tPnwiU3VibWl0InwgQlsiQmFua2VkIHNjb3JlIl0KICBBIC0tPnwiU3dpdGNoIHRhc2sifCBDWyJOb3Qgc2F2ZWQiXQogIEIgLS0-fCJMYXRlciBzeW50YXggZXJyb3IifCBEWyJHQ0Egc3RpbGwgZ3JhZGVzIGJhbmtlZCBzdWJtaXQiXQo%3D%3Fwidth%3D1000%26height%3D420%26bgColor%3D0f172a" alt="CodeSignal GCA only banks a task after Submit" width="1000" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Which submit actually gets scored?
&lt;/h2&gt;

&lt;p&gt;The highest-scoring submission for that question, not the last one. CodeSignal's wording: even if the last submit has a syntax error, the higher score is kept.&lt;/p&gt;

&lt;p&gt;That changes the last eight minutes. A common panic move is to "just try one more rewrite" on Q3, introduce a missing colon, and assume the whole task is now zero. It is not, &lt;strong&gt;if an earlier submit already passed tests&lt;/strong&gt;. The rewrite only hurts you if you never banked a green run.&lt;/p&gt;

&lt;p&gt;What it does not do: recover work you never submitted. Highest-of-submits is not an autosave.&lt;/p&gt;

&lt;p&gt;Practical order that matches the scoring contract:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get a correct, slow solution on Q1 and Q2. Submit. Do not polish.&lt;/li&gt;
&lt;li&gt;Same for Q3. Submit as soon as visible tests pass.&lt;/li&gt;
&lt;li&gt;Only then open Q4. If you get a partial, submit the partial. Leave Q3 alone unless you still have a passing copy in the bank.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Q4 is supposed to be hard. Community writeups (including the 600/600 Medium post people still link) treat a clean Q1–Q3 plus a partial Q4 as a strong outcome. Chasing Q4 by unsaving Q3 is how a 575-shaped attempt becomes a 500-shaped attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Cosmo or ChatGPT allowed on a certified GCA?
&lt;/h2&gt;

&lt;p&gt;On the certified GCA, no. The rules FAQ is narrower than most blogs. Syntax search is allowed so you do not have to memorize every STL method. &lt;strong&gt;AI is not allowed, including as a syntax search.&lt;/strong&gt; Their own examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allowed: open the C++ &lt;code&gt;queue&lt;/code&gt; reference to see which methods exist.&lt;/li&gt;
&lt;li&gt;Not allowed: open a tutorial on how to implement a deque, because that page is helping you with the solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FJSV7aW5pdDogeyd0aGVtZSc6J2Jhc2UnLCd0aGVtZVZhcmlhYmxlcyc6eydwcmltYXJ5Q29sb3InOicjMWUzYTVmJywncHJpbWFyeVRleHRDb2xvcic6JyNmOGZhZmMnLCdwcmltYXJ5Qm9yZGVyQ29sb3InOicjMzhiZGY4JywnbGluZUNvbG9yJzonIzdkZDNmYycsJ3NlY29uZGFyeUNvbG9yJzonIzBmMTcyYSd9fX0lJQpmbG93Y2hhcnQgVEIKICBTW09wZW4gR0NBXSAtLT4gVHtOZWVkIHN5bnRheD99CiAgVCAtLT58Y3BsdXNwbHVzLmNvbSAvIGRvY3MucHl0aG9uLm9yZyBtZXRob2QgbGlzdHwgT0tbQWxsb3dlZF0KICBUIC0tPnxIb3cgdG8gaW1wbGVtZW50IGRlcXVlIC8gcGFzdGUgQ2hhdEdQVHwgQkFEW05vdCBjZXJ0aWZpZWRdCg%3D%3D%3Fwidth%3D800%26height%3D420%26bgColor%3D0f172a" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FJSV7aW5pdDogeyd0aGVtZSc6J2Jhc2UnLCd0aGVtZVZhcmlhYmxlcyc6eydwcmltYXJ5Q29sb3InOicjMWUzYTVmJywncHJpbWFyeVRleHRDb2xvcic6JyNmOGZhZmMnLCdwcmltYXJ5Qm9yZGVyQ29sb3InOicjMzhiZGY4JywnbGluZUNvbG9yJzonIzdkZDNmYycsJ3NlY29uZGFyeUNvbG9yJzonIzBmMTcyYSd9fX0lJQpmbG93Y2hhcnQgVEIKICBTW09wZW4gR0NBXSAtLT4gVHtOZWVkIHN5bnRheD99CiAgVCAtLT58Y3BsdXNwbHVzLmNvbSAvIGRvY3MucHl0aG9uLm9yZyBtZXRob2QgbGlzdHwgT0tbQWxsb3dlZF0KICBUIC0tPnxIb3cgdG8gaW1wbGVtZW50IGRlcXVlIC8gcGFzdGUgQ2hhdEdQVHwgQkFEW05vdCBjZXJ0aWZpZWRdCg%3D%3D%3Fwidth%3D800%26height%3D420%26bgColor%3D0f172a" alt="Certified GCA: syntax docs vs solution pages" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the company sent you an &lt;strong&gt;AI-assisted&lt;/strong&gt; CodeSignal assessment instead, that is a different product. The GCA rules page does not apply to that transcript. Mixing the two is the same failure mode as treating every HackerRank panel as Copilot: the invite and the in-IDE banner are the source of truth, not the brand name on the calendar invite.&lt;/p&gt;

&lt;p&gt;aceround.app — AI interview assistant — keeps a longer GCA prep map (topic mix, score bands, what happens after you pass) at &lt;a href="https://www.aceround.app/blog/codesignal-interview-tips-ai/" rel="noopener noreferrer"&gt;the AceRound CodeSignal GCA guide&lt;/a&gt;. The same desktop client also covers OA-style timed tests and the live round after. This article stays on the contract the knowledge base actually states.&lt;/p&gt;

&lt;h2&gt;
  
  
  What fails hidden tests after the samples pass?
&lt;/h2&gt;

&lt;p&gt;Not a leaked GCA item. A stand-in for the Q2 ticket that shows up in every "I passed the examples and still timed out" thread: count index pairs &lt;code&gt;i &amp;lt; j&lt;/code&gt; where &lt;code&gt;a[j] - a[i] == k&lt;/code&gt;. Visible tests are &lt;code&gt;n ≤ 200&lt;/code&gt;. Hidden tests go to &lt;code&gt;n = 10^5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The nested loop is correct. It is also &lt;code&gt;O(n²)&lt;/code&gt;. On the hidden file it is a timeout, not a wrong answer, which is why people screenshot green samples and still drop the task.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_diff_pairs_slow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Correct. Dies at n = 1e5.
&lt;/span&gt;    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_diff_pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;


&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;count_diff_pairs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;count_diff_pairs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The linear version walks once, asks "how many earlier values equal &lt;code&gt;x - k&lt;/code&gt;?", and banks that count. Same answers on the samples. Different wall clock on the hidden file.&lt;/p&gt;

&lt;p&gt;GCA-shaped prep is this ticket, not another Hard on a whiteboard: constraints first, then a solution that survives &lt;code&gt;n = 10^5&lt;/code&gt;, then Submit, then leave the task. If the nested loop is all you have with four minutes left, submit it anyway. A timeout on hidden tests still beats an empty editor.&lt;/p&gt;

&lt;p&gt;Time box that matches the four-task layout:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;What "done" means&lt;/th&gt;
&lt;th&gt;Budget&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Q1&lt;/td&gt;
&lt;td&gt;Samples + obvious edges, submitted&lt;/td&gt;
&lt;td&gt;5–8 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q2&lt;/td&gt;
&lt;td&gt;Linear or &lt;code&gt;n log n&lt;/code&gt;, submitted&lt;/td&gt;
&lt;td&gt;8–15 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q3&lt;/td&gt;
&lt;td&gt;Working solution in the bank before you open Q4&lt;/td&gt;
&lt;td&gt;15–25 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q4&lt;/td&gt;
&lt;td&gt;Partial is a valid submit&lt;/td&gt;
&lt;td&gt;leftover&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those buckets are community pattern, not CodeSignal SLAs. The official page only promises "varying difficulty." The budget is there so Q3 gets a submit while it still compiles.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does a 560 always move you forward?&lt;/strong&gt;&lt;br&gt;
No. Companies set their own cut scores. CodeSignal publishes a guide for employers on setting those thresholds. Public forums will quote Capital One / HubSpot numbers that are a year old. Treat 550 / 575 / 595 as folklore until the recruiter states a number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I retake?&lt;/strong&gt;&lt;br&gt;
CodeSignal limits how often a certified GCA can be retaken inside a window. The 2024 framework writeup still circulating says three attempts in 180 days. Confirm on the account page the day you sit it; do not burn an attempt to "see the IDE." Use the Practice button on the setup screen for that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does tab-switching to docs.python.org fail certification?&lt;/strong&gt;&lt;br&gt;
The published example of an allowed lookup is a language reference page for methods. A how-to for the algorithm is not. If the in-IDE rule list is stricter than that FAQ, the in-IDE list wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I paste a finished answer in one shot?&lt;/strong&gt;&lt;br&gt;
Do not. Independent of whether it is allowed: you want incremental submits so a later syntax error does not leave you with nothing banked. Type it. Submit when tests go green.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do in the 24 hours before the invite expires
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open CodeSignal's two GCA articles, not a 2022 score conversion chart.&lt;/li&gt;
&lt;li&gt;Run one session in their practice IDE, in the browser, with the language you will actually use.&lt;/li&gt;
&lt;li&gt;Drill one &lt;code&gt;n = 10^5&lt;/code&gt; timeout ticket like the pair-count above until the linear version is muscle memory.&lt;/li&gt;
&lt;li&gt;Decide the submit rule in advance: nothing leaves a tab without a submit, Q4 never unsaves Q3.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a company GCA used a duration other than 70 minutes for you, drop the number in the comments. The rules page says that configuration exists. The public threads still pretend every invite is the default.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Drafting was AI-assisted. Facts in this piece were checked against CodeSignal's public knowledge base (GCA structure, 19 Aug 2026; GCA rules and setup, 25 Jul 2026; Assessment Score range, 24 Aug 2026). It is not an official CodeSignal guide and it is not a leaked question set.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>NFC vs QR: which business card actually gets scanned</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Wed, 09 Sep 2026 04:33:34 +0000</pubDate>
      <link>https://dev.to/karuha/nfc-vs-qr-which-business-card-actually-gets-scanned-2pn3</link>
      <guid>https://dev.to/karuha/nfc-vs-qr-which-business-card-actually-gets-scanned-2pn3</guid>
      <description>&lt;p&gt;NFC cards break on range, metal, and iOS confirmation. QR codes break on print density and light. Neither format is the contact data — both just deliver a URL.&lt;/p&gt;

&lt;p&gt;A tap-to-share card works against your own phone because you know where the antenna sits. Hand the same card to someone at a conference and the tap does nothing: no vibration, no popup, nothing to tell either of you whether it even tried. The QR printed on the back of that card is usually what saves the exchange.&lt;/p&gt;

&lt;p&gt;Both formats move the same handful of bytes: a name, a title, a URL. The difference is entirely in how those bytes get from the card to the phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually being transmitted
&lt;/h2&gt;

&lt;p&gt;An NFC business card is a small chip, usually in plastic or metal, storing an &lt;a href="https://developer.android.com/develop/connectivity/nfc/nfc" rel="noopener noreferrer"&gt;NDEF record&lt;/a&gt; — typically a URI, occasionally a full vCard. Tapping it against a phone's NFC antenna powers the chip (no battery) and the OS reads the record.&lt;/p&gt;

&lt;p&gt;A QR-code card encodes the same URI as a matrix of squares. A camera reads it instead of a radio.&lt;/p&gt;

&lt;p&gt;Neither one is "the data." Both are delivery mechanisms for a link. The two common failure modes have nothing to do with vCard fields. They're specific to radios and cameras.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where NFC actually breaks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The phone has to be looking for a tag.&lt;/strong&gt; Android only scans when NFC is on and the screen is unlocked. iPhone background tag reading exists since the XS; older models need an app that explicitly opens the scanner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tag format has to match what the OS expects.&lt;/strong&gt; iOS's background reader only picks up &lt;a href="https://developer.apple.com/documentation/corenfc/adding-support-for-background-tag-reading" rel="noopener noreferrer"&gt;specific NDEF record types&lt;/a&gt;, then asks the user to confirm before any app sees the data. "Tap and done" is never literally true on iPhone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Range is short and inconsistent.&lt;/strong&gt; Reliable reads happen within about 4cm, and that number moves by device. A card that works against your Pixel can fail against someone else's phone for no reason related to the card.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metal defeats it outright.&lt;/strong&gt; A metal NFC card needs an antenna cutout or it blocks its own signal. Plastic cards read more consistently. This is a design tradeoff, not a manufacturing defect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where QR actually breaks
&lt;/h2&gt;

&lt;p&gt;The failure modes are almost the inverse: physical, not electronic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Density scales with content.&lt;/strong&gt; A URL encodes into a sparse, forgiving pattern. A QR holding an entire vCard (name, phone, title, address) is several times denser and needs better light, a steadier hand, and a cleaner print. Point the code at a page URL and put the &lt;code&gt;.vcf&lt;/code&gt; behind a button on that page, not inside the code itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Light and print quality matter more than the code.&lt;/strong&gt; A code that scans off a bright phone screen can fail on a badge under fluorescent conference lighting, especially if it was printed small or on a low-contrast background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It needs a camera that recognizes QR&lt;/strong&gt;, which every iPhone since iOS 11 and effectively every Android phone since Android 9 does natively. This is the one area where QR has had fewer compatibility surprises than NFC for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost, and who controls the card afterward
&lt;/h2&gt;

&lt;p&gt;An NFC card is a physical object with a per-unit cost: plastic roughly $15–$25, metal $40 and up, before any software subscription. Some vendors in this space don't publish that subscription pricing at all.&lt;/p&gt;

&lt;p&gt;A QR code costs nothing to generate and nothing to reprint. That's also its main hardware limitation: there's no tactile "tap" moment, and a printed code can be photographed and reused. An NFC chip can't be cloned by a photo.&lt;/p&gt;

&lt;h2&gt;
  
  
  A two-minute test if you already have both
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Tap the NFC card against your own phone with the screen unlocked (Android only scans in that state). If nothing happens, check whether NFC is toggled on before blaming the card.&lt;/li&gt;
&lt;li&gt;Hand the same card to someone with a different phone brand and watch what happens with no prompting from you.&lt;/li&gt;
&lt;li&gt;Print the QR at the size you'll actually use, then scan it from arm's length under normal indoor light, not from six inches under your desk lamp.&lt;/li&gt;
&lt;li&gt;Compare which one a stranger gets through faster, unprompted. That result matters more than either spec sheet.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What neither format fixes
&lt;/h2&gt;

&lt;p&gt;Whichever one gets the tap or the scan through, it hands over a link. The first impression is the page behind it, not the transmission method.&lt;/p&gt;

&lt;p&gt;NFC is the better fit for repeat, one-on-one taps where you know both phones support it. QR remains the more compatible option for anything printed at scale or handed to someone whose phone you can't predict.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I work on &lt;a href="https://www.nameplate.cc/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=coldstart" rel="noopener noreferrer"&gt;Nameplate&lt;/a&gt;, a page builder with tracked QR links and a vCard export. This is adapted from our longer guide. We don't sell NFC hardware.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ux</category>
      <category>nfc</category>
      <category>productivity</category>
    </item>
    <item>
      <title>HackerRank AI-Assisted Interview: If the Assistant Won't Write Code, Check the Question Type</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Tue, 08 Sep 2026 07:38:17 +0000</pubDate>
      <link>https://dev.to/karuha/hackerrank-ai-assisted-interview-if-the-assistant-wont-write-code-check-the-question-type-126d</link>
      <guid>https://dev.to/karuha/hackerrank-ai-assisted-interview-if-the-assistant-wont-write-code-check-the-question-type-126d</guid>
      <description>&lt;p&gt;A HackerRank AI-assisted interview is a live round where the hiring company turns on an in-IDE assistant. If that assistant refuses to write a full solution, you are probably on a &lt;strong&gt;single-file Coding question in Guarded mode&lt;/strong&gt;, not a broken panel. Code Repos run Unguarded, with Plan mode and Agent mode, and the interviewer sees every prompt in real time.&lt;/p&gt;

&lt;p&gt;Google autocomplete still surfaces &lt;code&gt;hackerrank ai assisted interview practice&lt;/code&gt; and &lt;code&gt;hackerrank ai assisted interview reddit&lt;/code&gt;. Most of those threads collapse the format into "they gave me Copilot." The candidate knowledge base, last updated 11 days ago, is more specific than that. The format splits by question type, and the two sides do not behave the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually gets turned on?
&lt;/h2&gt;

&lt;p&gt;You cannot enable the assistant yourself. HackerRank's candidate docs are blunt: it appears only when the hiring company enables it for that interview, and only on supported question types. If the panel is missing, treat the round as a standard live coding session. Using an outside model in that case is the same as any other unauthorized aid.&lt;/p&gt;

&lt;p&gt;Two interviewer UIs exist right now. The &lt;a href="https://candidatesupport.hackerrank.com/articles/6665907643-ai-assistant-in-interviews" rel="noopener noreferrer"&gt;new candidate article&lt;/a&gt; describes the current Interview experience. The &lt;a href="https://support.hackerrank.com/articles/5821380141-ai-assisted-interviews" rel="noopener noreferrer"&gt;admin article&lt;/a&gt; still labels itself as the &lt;strong&gt;legacy&lt;/strong&gt; experience. You can tell which one you got from the workspace: a single editor vs an Agentic Development Environment with a repo, terminal, and file tree.&lt;/p&gt;

&lt;p&gt;Default model, when the company does not pin one, is &lt;strong&gt;GPT-5.6 Terra&lt;/strong&gt;. The selector can also offer Claude Opus 5 / Sonnet 5 / Haiku 4.5 and GPT-5.6 Sol / Luna. The set on screen is whatever that company configured. Do not spend the first three minutes hunting for a model that is not there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does Guarded mode look broken?
&lt;/h2&gt;

&lt;p&gt;This is the part Reddit threads keep misreading.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;new&lt;/strong&gt; Interview experience, HackerRank maps the assistant like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question type&lt;/th&gt;
&lt;th&gt;Workspace&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;What the assistant will do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coding&lt;/td&gt;
&lt;td&gt;Single-file editor&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Guarded&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Syntax, navigation, errors, concepts. &lt;strong&gt;No complete solutions.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Repos&lt;/td&gt;
&lt;td&gt;Agentic Development Environment&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unguarded&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plan mode + Agent mode: investigate, generate, edit files, fix issues.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That table is from the candidate knowledge base, not a blog recap. If you paste "write the function" into a single-file Coding question and the assistant talks about the algorithm instead of dumping a passing solution, that is the product working as designed.&lt;/p&gt;

&lt;p&gt;The legacy admin FAQ still says "in interviews the assistant operates in unguarded mode." That sentence is about the &lt;em&gt;interview product&lt;/em&gt; as opposed to HackerRank &lt;strong&gt;Tests&lt;/strong&gt; (the take-home / OA product), where the assistant stays guarded and will not reveal full solutions. It is not a promise that every live question will generate code. On legacy Coding questions, even unguarded mode only exposes &lt;strong&gt;Ask mode&lt;/strong&gt;. Plan, Agent, inline completions, and model switching are documented as unavailable in Guarded mode, and they only show up on Project / Code Repository questions when Unguarded is on.&lt;/p&gt;

&lt;p&gt;So the first diagnostic, before you panic that "AI is off":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is the panel visible at all? If not, the company did not enable it, or this question type does not support it.&lt;/li&gt;
&lt;li&gt;Are you in a single file or a repo? Single file → expect Ask, not an agent that rewrites the file.&lt;/li&gt;
&lt;li&gt;Did a &lt;strong&gt;Pending tool call&lt;/strong&gt; dialog appear? That is Agent mode. You have to &lt;strong&gt;Allow&lt;/strong&gt; or &lt;strong&gt;Cancel&lt;/strong&gt;. Ignoring it is not a stall; it is a decision the interviewer can see.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;HackerRank's Summer 2026 release added 159 Plan-Build-Review repository tasks for interviews. The industry default is drifting toward the repo side. Do not walk in having only practiced LeetCode in a blank editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FJSV7aW5pdDogeyd0aGVtZSc6J2Jhc2UnLCAndGhlbWVWYXJpYWJsZXMnOiB7ICdwcmltYXJ5Q29sb3InOicjMGYxNzJhJywncHJpbWFyeVRleHRDb2xvcic6JyNmOGZhZmMnLCdwcmltYXJ5Qm9yZGVyQ29sb3InOicjOTRhM2I4JywnbGluZUNvbG9yJzonIzM4YmRmOCcsJ2ZvbnRTaXplJzonMTZweCd9fX0lJQpmbG93Y2hhcnQgVEIKICBQW1BsYW4gbW9kZTxici8-bm8gY29kZSBlZGl0c10gLS0-IEFbQXNrIG1vZGU8YnIvPnRhZyB0aGUgcHJvYmxlbSBzdGF0ZW1lbnRdCiAgQSAtLT4gR1tBZ2VudCBtb2RlPGJyLz5BbGxvdyBvciBDYW5jZWwgZWFjaCB0b29sIGNhbGxdCiAgRyAtLT4gUltZb3UgcmVhZCB0aGUgZGlmZjxici8-YmVmb3JlIEFjY2VwdF0KICBJW0ludGVydmlld2VyIGxpdmUgcGFuZWxdIC0uLT4gUAogIEkgLS4tPiBBCiAgSSAtLi0-IEcKICBUW1Bvc3QtaW50ZXJ2aWV3IGNoYXQgdHJhbnNjcmlwdF0gLS0-IFAK" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FJSV7aW5pdDogeyd0aGVtZSc6J2Jhc2UnLCAndGhlbWVWYXJpYWJsZXMnOiB7ICdwcmltYXJ5Q29sb3InOicjMGYxNzJhJywncHJpbWFyeVRleHRDb2xvcic6JyNmOGZhZmMnLCdwcmltYXJ5Qm9yZGVyQ29sb3InOicjOTRhM2I4JywnbGluZUNvbG9yJzonIzM4YmRmOCcsJ2ZvbnRTaXplJzonMTZweCd9fX0lJQpmbG93Y2hhcnQgVEIKICBQW1BsYW4gbW9kZTxici8-bm8gY29kZSBlZGl0c10gLS0-IEFbQXNrIG1vZGU8YnIvPnRhZyB0aGUgcHJvYmxlbSBzdGF0ZW1lbnRdCiAgQSAtLT4gR1tBZ2VudCBtb2RlPGJyLz5BbGxvdyBvciBDYW5jZWwgZWFjaCB0b29sIGNhbGxdCiAgRyAtLT4gUltZb3UgcmVhZCB0aGUgZGlmZjxici8-YmVmb3JlIEFjY2VwdF0KICBJW0ludGVydmlld2VyIGxpdmUgcGFuZWxdIC0uLT4gUAogIEkgLS4tPiBBCiAgSSAtLi0-IEcKICBUW1Bvc3QtaW50ZXJ2aWV3IGNoYXQgdHJhbnNjcmlwdF0gLS0-IFAK" alt="Guarded Ask-only on single-file Coding versus Unguarded Plan and Agent on Code Repos" width="536" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the interviewer actually see?
&lt;/h2&gt;

&lt;p&gt;Three surfaces, all from HackerRank's own interviewer docs — not from "someone on Blind said":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live panel.&lt;/strong&gt; Prompts and assistant replies stream to the interviewer as they happen. Plan, Ask, and Agent are all visible. Inline completions you accept land in the shared editor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat transcript in the interview report.&lt;/strong&gt; The &lt;a href="https://support.hackerrank.com/articles/7299422402-interview-report" rel="noopener noreferrer"&gt;interview report&lt;/a&gt; has an AI Assistant Chat control on both Coding and Code Repos responses. Nothing you typed into the panel is off the record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff View&lt;/strong&gt;, but only on front-end, back-end, and full-stack questions. It compares the project at the start of the interview with the current tree. It is not a hidden plagiarism score. It is a "what changed" view.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HackerRank's admin FAQ also says Plan mode is &lt;strong&gt;optional&lt;/strong&gt;. You can go Ask → Agent, or live in Agent the whole time. Skipping Plan is allowed. It is also obvious, because the transcript then has no plan. Interviewers are told they &lt;em&gt;can&lt;/em&gt; weigh those Plan interactions as part of how you collaborate with AI. That is not a published numeric rubric. It is a visible absence.&lt;/p&gt;

&lt;p&gt;Ask mode has a mechanical rule that eats people: you have to &lt;strong&gt;tag the problem statement&lt;/strong&gt;. Untagged questions get generic answers. The interviewer sees you retry the same vague prompt three times. Tag it once.&lt;/p&gt;

&lt;p&gt;You can also choose not to use the assistant at all. That is documented. It is a valid strategy on a Guarded Coding question where the panel cannot write the solution anyway. On a Code Repo ticket, sitting next to an unused agent for 40 minutes is a different signal — you are leaving a tool on the table that the format was built to watch you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a strong Code Repo session look like?
&lt;/h2&gt;

&lt;p&gt;Take a ticket that shows up in real repo rounds: implement LRU cache, capacity 2, then hit the hidden eviction order.&lt;/p&gt;

&lt;p&gt;This is the first patch models ship. &lt;code&gt;get()&lt;/code&gt; returns the value and does not move the key to recent. The public tests still pass if they only check return values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LRUCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# oldest at index 0
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="c1"&gt;# missing: move key to most-recent
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LRUCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get(1) after put(3) =&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# -1  (1 was evicted)
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get(2) =&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                 &lt;span class="c1"&gt;# 2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hidden test is: after &lt;code&gt;get(1)&lt;/code&gt;, key 1 is more recent than 2, so &lt;code&gt;put(3)&lt;/code&gt; should evict &lt;strong&gt;2&lt;/strong&gt;, not 1. The agent wrote a map plus a list and forgot that &lt;code&gt;get&lt;/code&gt; is a recency event. The code compiles. That is the trap.&lt;/p&gt;

&lt;p&gt;A session that matches what the interviewer docs say they are watching looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Plan mode first, no code.&lt;/strong&gt; One paragraph: hash map plus recency list, &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;put&lt;/code&gt; both bump recency, evict the head on overflow. Edge case: &lt;code&gt;get&lt;/code&gt; on a missing key must not shuffle the list. Plan mode, per HackerRank, does not edit files. If the plan already skipped &lt;code&gt;get&lt;/code&gt; recency, Agent will implement the skip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow the tool call, then read the diff before Accept.&lt;/strong&gt; The Pending dialog is a gate. Cancel is a documented, legal action. Accepting a 40-line rewrite you have not read is the thing Diff View exists to show.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say the bug out loud, then patch one line.&lt;/strong&gt; &lt;code&gt;get&lt;/code&gt; needs &lt;code&gt;self.order.remove(key); self.order.append(key)&lt;/code&gt; before the return. That edit is worth more than a clean Agent dump, because the transcript now has: plan → generation → you catching the model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fixed &lt;code&gt;get&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rerun the same four calls. &lt;code&gt;get(1)&lt;/code&gt; after &lt;code&gt;put(3)&lt;/code&gt; is now &lt;code&gt;1&lt;/code&gt;, and &lt;code&gt;get(2)&lt;/code&gt; is &lt;code&gt;-1&lt;/code&gt;. That is the whole interview skill in twelve lines: the assistant wrote something that looked done, and you refused to ship it.&lt;/p&gt;

&lt;p&gt;On a Guarded single-file question the same LRU still appears, but the assistant will not dump this class for you. Ask about "what recency means on get" or "why a dict alone is not enough." Then you type. The transcript still exists. The prompts are the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is this different from a HackerRank OA?
&lt;/h2&gt;

&lt;p&gt;Do not mix the two products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interview&lt;/strong&gt; (this article): a human is on the call. AI use is in the open. The report is a chat transcript, not a plagiarism flag for using the panel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests / Screen&lt;/strong&gt; (the OA / take-home): the admin FAQ says the assistant stays in &lt;strong&gt;Guarded&lt;/strong&gt; mode and will not reveal full solutions. Proctoring, tab switches, and integrity reports are a different stack. "Can I use ChatGPT during the OA?" is not this format. If your recruiter said "HackerRank assessment" with no interviewer on the calendar, you are probably in Tests.&lt;/p&gt;

&lt;p&gt;If the pipeline is OA then live, those are two preps. The OA side is take-home / timed coding. The live side is the format above. &lt;a href="https://aceround.app" rel="noopener noreferrer"&gt;aceround.app&lt;/a&gt; — an AI interview assistant — is built for the live round, including catching a bad AI patch while someone is watching. The same desktop client also covers coding-test / OA practice; do not walk into Screen with Interview habits, or the other way around.&lt;/p&gt;

&lt;p&gt;Ask the recruiter the only question that changes the plan: &lt;strong&gt;is this an AI-assisted Interview, or a HackerRank test?&lt;/strong&gt; If they do not know, assume standard until the panel appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 20-minute practice you can run tonight
&lt;/h2&gt;

&lt;p&gt;You do not need HackerRank's IDE to rehearse the &lt;em&gt;judgment&lt;/em&gt; the format scores.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a small repo bug, not a blank-editor LeetCode. LRU, a rate limiter that uses wall-clock time, a pagination off-by-one.&lt;/li&gt;
&lt;li&gt;Write a five-line plan in a comment. No code.&lt;/li&gt;
&lt;li&gt;Ask any coding model for the implementation. Do not paste it in yet.&lt;/li&gt;
&lt;li&gt;Run one hidden case you did not put in the prompt. For LRU: &lt;code&gt;put 1, put 2, get 1, put 3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Narrate the first thing the model got wrong, then patch it yourself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you cannot find the bug in step 4, you are not ready for Unguarded Agent mode. That is the actual practice, not memorizing Plan Mode's UI labels.&lt;/p&gt;

&lt;p&gt;The question worth leaving in the comments: when the assistant is on, do you still write the first draft yourself, or do you start in Agent and only keep the edits you can defend?&lt;/p&gt;




&lt;p&gt;Sources: &lt;a href="https://candidatesupport.hackerrank.com/articles/6665907643-ai-assistant-in-interviews" rel="noopener noreferrer"&gt;HackerRank candidate KB — AI Assistant in Interview&lt;/a&gt; (updated 11 days ago); &lt;a href="https://support.hackerrank.com/articles/5821380141-ai-assisted-interviews" rel="noopener noreferrer"&gt;HackerRank admin KB — AI-Assisted Interviews&lt;/a&gt; (legacy UI, updated 8 days ago); &lt;a href="https://support.hackerrank.com/articles/7299422402-interview-report" rel="noopener noreferrer"&gt;Interview Report&lt;/a&gt;; &lt;a href="https://www.hackerrank.com/release/summer-2026" rel="noopener noreferrer"&gt;Summer 2026 release notes&lt;/a&gt; (159 Plan-Build-Review repo tasks).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I used AI to help draft and edit this post. The Guarded vs Unguarded split, feature tables, and interviewer surfaces are from HackerRank's public knowledge base, checked today. The LRU example is a local Python replay, not a dump from an interview session.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Second Worker Never Saw Job 2</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Fri, 04 Sep 2026 10:11:14 +0000</pubDate>
      <link>https://dev.to/karuha/the-second-worker-never-saw-job-2-59a8</link>
      <guid>https://dev.to/karuha/the-second-worker-never-saw-job-2-59a8</guid>
      <description>&lt;p&gt;&lt;code&gt;SELECT … FOR UPDATE LIMIT 1&lt;/code&gt; does not mean "give me the next free job." It means "lock the first matching row, and wait if somebody else already has it." Worker 2 parks on job 1. Job 2 sits queued. &lt;code&gt;SKIP LOCKED&lt;/code&gt;, added in PostgreSQL &lt;strong&gt;9.5&lt;/strong&gt;, is the clause that hands worker 2 the next unlocked row instead.&lt;/p&gt;

&lt;p&gt;I replayed it on a five-row table with two workers. Same shape: CPU bored, queue depth 4, throughput 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does &lt;code&gt;FOR UPDATE LIMIT 1&lt;/code&gt; actually wait on?
&lt;/h2&gt;

&lt;p&gt;The locking clause lives in the &lt;a href="https://www.postgresql.org/docs/current/sql-select.html" rel="noopener noreferrer"&gt;PostgreSQL 18 &lt;code&gt;SELECT&lt;/code&gt; docs&lt;/a&gt;. Default behavior: if the row you want is already locked, &lt;strong&gt;you wait&lt;/strong&gt;. &lt;code&gt;NOWAIT&lt;/code&gt; errors. &lt;code&gt;SKIP LOCKED&lt;/code&gt; skips.&lt;/p&gt;

&lt;p&gt;None of &lt;code&gt;FOR NO KEY UPDATE&lt;/code&gt;, &lt;code&gt;FOR SHARE&lt;/code&gt;, &lt;code&gt;FOR KEY SHARE&lt;/code&gt;, &lt;code&gt;NOWAIT&lt;/code&gt;, or &lt;code&gt;SKIP LOCKED&lt;/code&gt; are in the SQL standard. Postgres documented that in the same page. So "I'll write standard SQL and the queue will fan out" is not an answer.&lt;/p&gt;

&lt;p&gt;The usual claim query looks innocent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'queued'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'running'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* that id */&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- work happens here, still inside the transaction&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worker 1 takes job 1 and holds the row lock until &lt;code&gt;COMMIT&lt;/code&gt;. Worker 2's identical &lt;code&gt;SELECT&lt;/code&gt; wants job 1 first — &lt;code&gt;ORDER BY id LIMIT 1&lt;/code&gt; — and blocks.&lt;/p&gt;

&lt;p&gt;Job 2 is still &lt;code&gt;queued&lt;/code&gt;. Nobody is running it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRCCiAgQVsiU0VMRUNUIHF1ZXVlZCBqb2JzIE9SREVSIEJZIGlkIl0gLS0-IEJbIkZPUiBVUERBVEUgTElNSVQgMSJdCiAgQiAtLT4gQ3sicm93IGFscmVhZHkgbG9ja2VkPyJ9CiAgQyAtLT58d2FpdCBkZWZhdWx0fCBEWyJwYXJrIHVudGlsIGNvbW1pdCJdCiAgQyAtLT58Tk9XQUlUfCBFWyJlcnJvciwgYWJvcnQiXQogIEMgLS0-fFNLSVAgTE9DS0VEfCBGWyJza2lwLCB0cnkgbmV4dCByb3ciXQogIEYgLS0-IEdbImNsYWltIGpvYiAyIl0KICBjbGFzc0RlZiB0ZXJtIGZpbGw6IzE2NGU2MyxzdHJva2U6IzY3ZThmOSxjb2xvcjojZWNmZWZmLGZvbnQtc2l6ZToxNnB4CiAgY2xhc3NEZWYgd29yayBmaWxsOiMwZTc0OTAsc3Ryb2tlOiMyMmQzZWUsY29sb3I6I2VjZmVmZixmb250LXNpemU6MTZweAogIGNsYXNzRGVmIGdhdGUgZmlsbDojYjQ1MzA5LHN0cm9rZTojZmNkMzRkLGNvbG9yOiNmZmYsZm9udC1zaXplOjE2cHgKICBjbGFzc0RlZiBiYWQgZmlsbDojOTkxYjFiLHN0cm9rZTojZmNhNWE1LGNvbG9yOiNmZmYsZm9udC1zaXplOjE2cHgKICBjbGFzc0RlZiBvayBmaWxsOiMxNDUzMmQsc3Ryb2tlOiM4NmVmYWMsY29sb3I6I2VjZmVmZixmb250LXNpemU6MTZweAogIGNsYXNzIEEgdGVybQogIGNsYXNzIEIgd29yawogIGNsYXNzIEMgZ2F0ZQogIGNsYXNzIEQsRSBiYWQKICBjbGFzcyBGLEcgb2sK%3FbgColor%3D0f172a" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRCCiAgQVsiU0VMRUNUIHF1ZXVlZCBqb2JzIE9SREVSIEJZIGlkIl0gLS0-IEJbIkZPUiBVUERBVEUgTElNSVQgMSJdCiAgQiAtLT4gQ3sicm93IGFscmVhZHkgbG9ja2VkPyJ9CiAgQyAtLT58d2FpdCBkZWZhdWx0fCBEWyJwYXJrIHVudGlsIGNvbW1pdCJdCiAgQyAtLT58Tk9XQUlUfCBFWyJlcnJvciwgYWJvcnQiXQogIEMgLS0-fFNLSVAgTE9DS0VEfCBGWyJza2lwLCB0cnkgbmV4dCByb3ciXQogIEYgLS0-IEdbImNsYWltIGpvYiAyIl0KICBjbGFzc0RlZiB0ZXJtIGZpbGw6IzE2NGU2MyxzdHJva2U6IzY3ZThmOSxjb2xvcjojZWNmZWZmLGZvbnQtc2l6ZToxNnB4CiAgY2xhc3NEZWYgd29yayBmaWxsOiMwZTc0OTAsc3Ryb2tlOiMyMmQzZWUsY29sb3I6I2VjZmVmZixmb250LXNpemU6MTZweAogIGNsYXNzRGVmIGdhdGUgZmlsbDojYjQ1MzA5LHN0cm9rZTojZmNkMzRkLGNvbG9yOiNmZmYsZm9udC1zaXplOjE2cHgKICBjbGFzc0RlZiBiYWQgZmlsbDojOTkxYjFiLHN0cm9rZTojZmNhNWE1LGNvbG9yOiNmZmYsZm9udC1zaXplOjE2cHgKICBjbGFzc0RlZiBvayBmaWxsOiMxNDUzMmQsc3Ryb2tlOiM4NmVmYWMsY29sb3I6I2VjZmVmZixmb250LXNpemU6MTZweAogIGNsYXNzIEEgdGVybQogIGNsYXNzIEIgd29yawogIGNsYXNzIEMgZ2F0ZQogIGNsYXNzIEQsRSBiYWQKICBjbGFzcyBGLEcgb2sK%3FbgColor%3D0f172a" alt="Default FOR UPDATE waits; SKIP LOCKED takes the next row" width="615" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is job 2 still queued while worker 2 is idle?
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;LIMIT 1&lt;/code&gt; applies to &lt;strong&gt;returned&lt;/strong&gt; rows, not to "rows I am willing to wait on." Worker 2 is still trying to return job 1. It has not moved on.&lt;/p&gt;

&lt;p&gt;Postgres is explicit about another version of this trap: &lt;em&gt;"If a &lt;code&gt;LIMIT&lt;/code&gt; is used, locking stops once enough rows have been returned to satisfy the limit (but note that rows skipped over by &lt;code&gt;OFFSET&lt;/code&gt; will get locked)."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clause&lt;/th&gt;
&lt;th&gt;Locked row in front of you&lt;/th&gt;
&lt;th&gt;Job 2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;FOR UPDATE&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;wait&lt;/td&gt;
&lt;td&gt;sits there&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FOR UPDATE NOWAIT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;error, transaction can abort&lt;/td&gt;
&lt;td&gt;never attempted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;skip&lt;/td&gt;
&lt;td&gt;claimed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;SKIP LOCKED&lt;/code&gt; shipped in &lt;a href="https://www.postgresql.org/docs/9.5/release-9-5.html" rel="noopener noreferrer"&gt;PostgreSQL 9.5&lt;/a&gt; ("Add &lt;code&gt;SELECT&lt;/code&gt; option &lt;code&gt;SKIP LOCKED&lt;/code&gt; to skip locked rows", Thomas Munro). The 18 docs still warn that skipping locked rows &lt;em&gt;"provides an inconsistent view of the data, so this is not suitable for general purpose work, but can be used to avoid lock contention with multiple consumers accessing a queue-like table."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That last clause is the interview sentence. Queues: yes. Account balances: no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you prove the wait without standing up Postgres?
&lt;/h2&gt;

&lt;p&gt;Yes. A 70-line in-memory table is enough to encode the three lock policies and the &lt;code&gt;OFFSET&lt;/code&gt; footgun. I ran this locally before writing the rest of the post; it printed &lt;code&gt;skip-locked assertions passed&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MiniQueue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;claimedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;skipLocked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nowait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;skipLocked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nowait&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;could not obtain lock on row in relation "jobs"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NOWAIT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;blocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;waitingOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;claimedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;blocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;waitingOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;blocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;waitingOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;claimedBy&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;running&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wait case is the one people draw wrong on a whiteboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MiniQueue&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// job 1&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// blocked on 1, job === null&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitingOn&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worker B does not "fall through" to job 2. Job 2's status never changes. That is the whole bug.&lt;/p&gt;

&lt;p&gt;Flip the flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MiniQueue&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;skipLocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;skipLocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;C&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;skipLocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three workers, three distinct jobs, zero waits. &lt;code&gt;NOWAIT&lt;/code&gt; on the same setup throws &lt;code&gt;could not obtain lock on row in relation "jobs"&lt;/code&gt; instead of skipping — which matches the 9.5 release note: it does not throw for locked rows the way &lt;code&gt;NOWAIT&lt;/code&gt; does.&lt;/p&gt;

&lt;p&gt;After A commits, job 1 is &lt;code&gt;running&lt;/code&gt; and unlocked. B with &lt;code&gt;SKIP LOCKED&lt;/code&gt; then takes job 2, not a second copy of job 1. The lock and the status are different columns in your head even when they live on the same row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does &lt;code&gt;OFFSET&lt;/code&gt; lock rows you never returned?
&lt;/h2&gt;

&lt;p&gt;Because Postgres said so, in the sentence I quoted above. I encoded it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;selectForUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;skipLocked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;returned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;skipLocked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;blocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;returned&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;locked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;claimedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;returned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;blocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;returned&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;OFFSET 1 LIMIT 1&lt;/code&gt; on jobs &lt;code&gt;[1, 2, 3]&lt;/code&gt; returns job &lt;strong&gt;2&lt;/strong&gt; and also locks job &lt;strong&gt;1&lt;/strong&gt;. Worker B with &lt;code&gt;SKIP LOCKED&lt;/code&gt; is forced onto job 3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQVsiam9icyAxLDIsMyBxdWV1ZWQiXSAtLT4gQlsiRk9SIFVQREFURSBPRkZTRVQgMSBMSU1JVCAxIl0KICBCIC0tPiBDWyJyZXR1cm5zIGpvYiAyIl0KICBCIC0tPiBEWyJhbHNvIGxvY2tzIGpvYiAxIl0KICBEIC0tPiBFWyJ3b3JrZXIgQiBjYW5ub3QgdGFrZSBqb2IgMSJdCiAgY2xhc3NEZWYgdGVybSBmaWxsOiMxNjRlNjMsc3Ryb2tlOiM2N2U4ZjksY29sb3I6I2VjZmVmZixmb250LXNpemU6MTZweAogIGNsYXNzRGVmIHdvcmsgZmlsbDojMGU3NDkwLHN0cm9rZTojMjJkM2VlLGNvbG9yOiNlY2ZlZmYsZm9udC1zaXplOjE2cHgKICBjbGFzc0RlZiBiYWQgZmlsbDojOTkxYjFiLHN0cm9rZTojZmNhNWE1LGNvbG9yOiNmZmYsZm9udC1zaXplOjE2cHgKICBjbGFzcyBBIHRlcm0KICBjbGFzcyBCLEMgd29yawogIGNsYXNzIEQsRSBiYWQK%3FbgColor%3D0f172a" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQVsiam9icyAxLDIsMyBxdWV1ZWQiXSAtLT4gQlsiRk9SIFVQREFURSBPRkZTRVQgMSBMSU1JVCAxIl0KICBCIC0tPiBDWyJyZXR1cm5zIGpvYiAyIl0KICBCIC0tPiBEWyJhbHNvIGxvY2tzIGpvYiAxIl0KICBEIC0tPiBFWyJ3b3JrZXIgQiBjYW5ub3QgdGFrZSBqb2IgMSJdCiAgY2xhc3NEZWYgdGVybSBmaWxsOiMxNjRlNjMsc3Ryb2tlOiM2N2U4ZjksY29sb3I6I2VjZmVmZixmb250LXNpemU6MTZweAogIGNsYXNzRGVmIHdvcmsgZmlsbDojMGU3NDkwLHN0cm9rZTojMjJkM2VlLGNvbG9yOiNlY2ZlZmYsZm9udC1zaXplOjE2cHgKICBjbGFzc0RlZiBiYWQgZmlsbDojOTkxYjFiLHN0cm9rZTojZmNhNWE1LGNvbG9yOiNmZmYsZm9udC1zaXplOjE2cHgKICBjbGFzcyBBIHRlcm0KICBjbGFzcyBCLEMgd29yawogIGNsYXNzIEQsRSBiYWQK%3FbgColor%3D0f172a" alt="OFFSET 1 still locks the skipped row" width="1032" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do not paginate a queue with &lt;code&gt;OFFSET&lt;/code&gt;. Use &lt;code&gt;WHERE id &amp;gt; $last&lt;/code&gt; or &lt;code&gt;SKIP LOCKED&lt;/code&gt; plus a real cursor. &lt;code&gt;OFFSET&lt;/code&gt; in a locker is how you accidentally serialize workers on a row you did not even claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you say this in the interview?
&lt;/h2&gt;

&lt;p&gt;I walk it as four beats, then stop.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name the default.&lt;/strong&gt; &lt;code&gt;FOR UPDATE LIMIT 1&lt;/code&gt; waits on the first matching row. Extra workers go idle while later rows sit &lt;code&gt;queued&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name the clause.&lt;/strong&gt; &lt;code&gt;SKIP LOCKED&lt;/code&gt; skips rows that cannot be locked immediately. Official docs restrict that to queue-like tables because the snapshot is inconsistent on purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name the sibling.&lt;/strong&gt; &lt;code&gt;NOWAIT&lt;/code&gt; fails the statement. Retries of &lt;code&gt;NOWAIT&lt;/code&gt; still stampede the same hot row. That is not a queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name the footgun.&lt;/strong&gt; &lt;code&gt;OFFSET&lt;/code&gt; locks skipped rows. I have the sentence from the &lt;code&gt;SELECT&lt;/code&gt; page; I do not paraphrase it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then the production shape: claim and &lt;code&gt;UPDATE&lt;/code&gt; in a short transaction, &lt;code&gt;COMMIT&lt;/code&gt;, &lt;em&gt;then&lt;/em&gt; do the work. Holding &lt;code&gt;FOR UPDATE&lt;/code&gt; across an HTTP call is how worker 2 waits five minutes for job 1's Stripe round-trip while job 2–5 idle.&lt;/p&gt;

&lt;p&gt;This is not a toy pattern. &lt;a href="https://github.com/graphile/worker/blob/91c950f7/src/sql/getJobs.ts" rel="noopener noreferrer"&gt;Graphile Worker&lt;/a&gt; claims with &lt;code&gt;limit ${batchSize} for update skip locked&lt;/code&gt;. If an interviewer asks "have you seen this outside a blog," that file is the receipt.&lt;/p&gt;

&lt;p&gt;If you are rehearsing the same job-queue answer for a Staff-loop system-design round — multiple consumers, no double-processing, what you do when a worker dies mid-job — walk the wait / &lt;code&gt;NOWAIT&lt;/code&gt; / &lt;code&gt;SKIP LOCKED&lt;/code&gt; table out loud, then add the lock-hold timeout. &lt;a href="https://www.aceround.app/blog/staff-engineer-interview-ai/" rel="noopener noreferrer"&gt;aceround.app&lt;/a&gt; is the AI interview assistant I use to get pushed on the follow-up ("the worker crashed, job 1 stays &lt;code&gt;running&lt;/code&gt;, now what?"). The SQL does not change because the interviewer added a crash; the status machine around it does.&lt;/p&gt;

&lt;p&gt;What I still want to hear in a loop: how you recover a lock that outlived the process. &lt;code&gt;SKIP LOCKED&lt;/code&gt; does not help you there. A &lt;code&gt;locked_at&lt;/code&gt; timestamp plus a reclaim query does. That is a different pad.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was drafted with AI assistance and then edited against the PostgreSQL 18 &lt;code&gt;SELECT&lt;/code&gt; locking clause, the PostgreSQL 9.5 release notes, and Graphile Worker's &lt;code&gt;getJobs.ts&lt;/code&gt;. The Node assertions were run locally before publish.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Forgot client.release()? Postgres Never Sees Request 11</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Thu, 03 Sep 2026 08:27:27 +0000</pubDate>
      <link>https://dev.to/karuha/forgot-clientrelease-postgres-never-sees-request-11-23gf</link>
      <guid>https://dev.to/karuha/forgot-clientrelease-postgres-never-sees-request-11-23gf</guid>
      <description>&lt;p&gt;If &lt;code&gt;pg_stat_activity&lt;/code&gt; is quiet and &lt;code&gt;/health&lt;/code&gt; is still 200, it is not slow SQL. node-postgres defaults &lt;code&gt;max&lt;/code&gt; to &lt;strong&gt;10&lt;/strong&gt; and &lt;code&gt;connectionTimeoutMillis&lt;/code&gt; to &lt;strong&gt;0&lt;/strong&gt;. Skip &lt;code&gt;client.release()&lt;/code&gt; after &lt;code&gt;pool.connect()&lt;/code&gt;, and request 11 never reaches Postgres — it waits in a FIFO queue with no timer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the interviewer actually mean by "the pool is exhausted"?
&lt;/h2&gt;

&lt;p&gt;They do not mean "Postgres hit &lt;code&gt;max_connections&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;PostgreSQL's default &lt;code&gt;max_connections&lt;/code&gt; is typically &lt;strong&gt;100&lt;/strong&gt;, with &lt;strong&gt;3&lt;/strong&gt; slots reserved for superusers. That is a server cap. The Node pool is a &lt;em&gt;process&lt;/em&gt; cap. &lt;a href="https://node-postgres.com/apis/pool" rel="noopener noreferrer"&gt;node-postgres&lt;/a&gt; will create clients lazily up to &lt;code&gt;max&lt;/code&gt;, then park extra &lt;code&gt;connect()&lt;/code&gt; calls in a FIFO wait list until somebody calls &lt;code&gt;release()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two different ceilings, two different dashboards:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What "full" looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;pg.Pool&lt;/code&gt; in one Node process&lt;/td&gt;
&lt;td&gt;&lt;code&gt;max: 10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;waitingCount&lt;/code&gt; climbs, handlers hang or hit &lt;code&gt;connectionTimeoutMillis&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;max_connections&lt;/code&gt; ≈ 100&lt;/td&gt;
&lt;td&gt;new backends fail; superuser still has 3 reserved slots&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I have walked this on-call more than once. CPU on the database is bored. p99 on the API is 30 seconds. The missing chart is &lt;code&gt;pool.waitingCount&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does &lt;code&gt;pool.connect()&lt;/code&gt; hang instead of throwing?
&lt;/h2&gt;

&lt;p&gt;Because the default timeout is not "a few seconds." It is &lt;strong&gt;off&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;From the pool docs, &lt;code&gt;connectionTimeoutMillis&lt;/code&gt; defaults to &lt;strong&gt;0&lt;/strong&gt;, which means &lt;em&gt;no timeout&lt;/em&gt;. &lt;code&gt;idleTimeoutMillis&lt;/code&gt; defaults to &lt;strong&gt;10_000&lt;/strong&gt; (idle clients get recycled after 10 seconds). Those two numbers get swapped in people's heads. Idle recycle is not a checkout deadline.&lt;/p&gt;

&lt;p&gt;Official warning, almost word for word: if you forget to release, further &lt;code&gt;pool.connect()&lt;/code&gt; calls &lt;strong&gt;timeout or hang indefinitely&lt;/strong&gt; when &lt;code&gt;connectionTimeoutMillis&lt;/code&gt; is 0.&lt;/p&gt;

&lt;p&gt;So the production shape is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Handler calls &lt;code&gt;const client = await pool.connect()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It &lt;code&gt;await&lt;/code&gt;s something else (a payment API, S3, another SQL client) while still holding the checkout.&lt;/li&gt;
&lt;li&gt;Under load, all 10 slots are busy doing &lt;em&gt;not-SQL&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Request 11 waits forever. The process looks "up." Postgres never saw the query.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRCCiAgQVsicmVxdWVzdCBob2xkcyBjbGllbnQiXSAtLT4gQlsiYXdhaXQgZG93bnN0cmVhbSBIVFRQIl0KICBCIC0tPiBDWyJwb29sIG1heD0xIHN0aWxsIGJ1c3kiXQogIEMgLS0-IERbIm5leHQgcmVxdWVzdCB3YWl0cyJdCiAgRCAtLT4gRVsiY29ubmVjdGlvblRpbWVvdXRNaWxsaXM9MCJdCiAgRSAtLT4gRlsiaGFuZGxlciBuZXZlciByZXR1cm5zIl0KICBjbGFzc0RlZiBiYWQgZmlsbDojOTkxYjFiLHN0cm9rZTojZmNhNWE1LGNvbG9yOiNmZmYsZm9udC1zaXplOjE4cHgKICBjbGFzc0RlZiB3b3JrIGZpbGw6IzE2NGU2MyxzdHJva2U6IzY3ZThmOSxjb2xvcjojZWNmZWZmLGZvbnQtc2l6ZToxOHB4CiAgY2xhc3MgQSxCLEMgd29yawogIGNsYXNzIEQsRSxGIGJhZAo%3D%3Fwidth%3D1000%26height%3D520%26bgColor%3D0f172a" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRCCiAgQVsicmVxdWVzdCBob2xkcyBjbGllbnQiXSAtLT4gQlsiYXdhaXQgZG93bnN0cmVhbSBIVFRQIl0KICBCIC0tPiBDWyJwb29sIG1heD0xIHN0aWxsIGJ1c3kiXQogIEMgLS0-IERbIm5leHQgcmVxdWVzdCB3YWl0cyJdCiAgRCAtLT4gRVsiY29ubmVjdGlvblRpbWVvdXRNaWxsaXM9MCJdCiAgRSAtLT4gRlsiaGFuZGxlciBuZXZlciByZXR1cm5zIl0KICBjbGFzc0RlZiBiYWQgZmlsbDojOTkxYjFiLHN0cm9rZTojZmNhNWE1LGNvbG9yOiNmZmYsZm9udC1zaXplOjE4cHgKICBjbGFzc0RlZiB3b3JrIGZpbGw6IzE2NGU2MyxzdHJva2U6IzY3ZThmOSxjb2xvcjojZWNmZWZmLGZvbnQtc2l6ZToxOHB4CiAgY2xhc3MgQSxCLEMgd29yawogIGNsYXNzIEQsRSxGIGJhZAo%3D%3Fwidth%3D1000%26height%3D520%26bgColor%3D0f172a" alt="Held checkout starves the next request" width="1000" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you prove the checkout contract without standing up Postgres?
&lt;/h2&gt;

&lt;p&gt;I do not spin Docker for this drill. The failure is the &lt;em&gt;lease&lt;/em&gt;, not the SQL. A tiny pool with &lt;code&gt;max&lt;/code&gt;, FIFO waiters, and &lt;code&gt;connectionTimeoutMillis&lt;/code&gt; is enough. Save it as &lt;code&gt;pool-release.mjs&lt;/code&gt; and run &lt;code&gt;node pool-release.mjs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MiniPool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;connectionTimeoutMillis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionTimeoutMillis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connectionTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;idleCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;waitingCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;give&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;busy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;give&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalCount&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;give&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextId&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;busy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;released&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionTimeoutMillis&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;waiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waiter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`connection timeout after &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waiter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;released&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cannot release a client that is not checked out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;busy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;released&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;waiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;busy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;released&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;busy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;released&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight contracts I actually run. The first two are the ones that catch people:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MiniPool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;connectionTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sr"&gt;/connection timeout after 40ms/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MiniPool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;connectionTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;settled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;settled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resolved&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;settled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rejected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;settled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitingCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then FIFO (first waiter gets the first &lt;code&gt;release()&lt;/code&gt;), &lt;code&gt;try/finally&lt;/code&gt; still returns the client when the query throws, a handler that holds the client across an 80ms &lt;code&gt;await&lt;/code&gt; starves a 30ms checkout, the same handler with &lt;code&gt;finally { pool.release(client) }&lt;/code&gt; lets the next request through, and a &lt;code&gt;pool.query&lt;/code&gt; helper that connect/release internally can run two sequential queries on &lt;code&gt;max: 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That last one is the docs' shortcut: &lt;code&gt;pool.query&lt;/code&gt; borrows an idle client and puts it back. Use it for a single statement. Do &lt;strong&gt;not&lt;/strong&gt; use it for a transaction. Postgres transactions are scoped to one backend. Dispatching &lt;code&gt;BEGIN&lt;/code&gt; on client A and &lt;code&gt;COMMIT&lt;/code&gt; on client B is how you get a "it worked on my laptop" incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you say when they ask how big &lt;code&gt;max&lt;/code&gt; should be?
&lt;/h2&gt;

&lt;p&gt;Leave it at &lt;strong&gt;10&lt;/strong&gt; until you have a reason. That is not me being cute; it is the &lt;a href="https://node-postgres.com/guides/pool-sizing" rel="noopener noreferrer"&gt;pool-sizing guide&lt;/a&gt;. The author (Brian Carlson, who maintains &lt;code&gt;pg&lt;/code&gt;) writes that he usually does not bother changing the default, and that if the app is starved for connections the queries are the problem.&lt;/p&gt;

&lt;p&gt;The napkin math they want out loud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Postgres usable slots ≈ &lt;code&gt;max_connections - superuser_reserved_connections&lt;/code&gt; → 100 − 3 = &lt;strong&gt;97&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;12 Node instances × &lt;code&gt;max: 10&lt;/code&gt; = &lt;strong&gt;120&lt;/strong&gt; possible backends.&lt;/li&gt;
&lt;li&gt;120 &amp;gt; 97. You will get &lt;code&gt;too many connections&lt;/code&gt; during a deploy overlap, not during a clever SQL spike.&lt;/li&gt;
&lt;li&gt;Safe ceiling per instance: &lt;code&gt;Math.floor(97 / 12) = 8&lt;/code&gt;. I still would not jump there on day one. I would keep 10 and put a pooler (PgBouncer / RDS Proxy) in front before I grow instance count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are on Vercel fluid compute or a long-lived container, the same default 10 is the starting point. If you are on a fully stateless worker that dies every request, you still should not set &lt;code&gt;max: 1&lt;/code&gt; unless you enjoy turning batched queries into a single-file line; call &lt;code&gt;pool.end()&lt;/code&gt; when the invocation is done.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you narrate this in a backend / SRE interview?
&lt;/h2&gt;

&lt;p&gt;I walk the incident, not the class diagram.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Symptom:&lt;/strong&gt; API p99 blew up. Postgres CPU stayed under 10%. &lt;code&gt;pg_stat_activity&lt;/code&gt; had a handful of idle sessions, not a lock pileup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mitigate first:&lt;/strong&gt; shed load, roll back the handler that started awaiting Stripe with a checked-out client, bounce the Node pods so leaked checkouts die with the process. Do not "add &lt;code&gt;max: 50&lt;/code&gt;" while you are on fire. That just converts a queue in Node into &lt;code&gt;FATAL: remaining connection slots are reserved for superuser&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prove the bug:&lt;/strong&gt; a pad like the one above, plus one production metric: &lt;code&gt;pool.waitingCount&lt;/code&gt;. If that number is above 0 while the database is idle, you are leaking checkouts or holding them across I/O.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fix:&lt;/strong&gt; &lt;code&gt;try/finally { client.release() }&lt;/code&gt; for any transaction. &lt;code&gt;pool.query&lt;/code&gt; for one-shot statements. &lt;code&gt;connectionTimeoutMillis: 2000&lt;/code&gt; (the number in the official example) so a leak becomes a fast 500 instead of a silent hang. 2000ms is a product choice, not a law; the law is "0 means never."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade-off they will poke:&lt;/strong&gt; a short checkout timeout fails the user; a missing timeout fails &lt;em&gt;every later user&lt;/em&gt;. I would rather fail the leaked request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the round is a DevOps / SRE loop, they will ask what you freeze when this burns error budget. Same answer as any saturation bug: stop shipping handlers that hold scarce leases across unbounded I/O, then add the queue metric. I keep a punch-list of those operational follow-ups on &lt;a href="https://www.aceround.app/blog/devops-engineer-interview-ai/" rel="noopener noreferrer"&gt;aceround.app's DevOps interview guide&lt;/a&gt; when I want to rehearse the incident narrative out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would not say
&lt;/h2&gt;

&lt;p&gt;I would not say "we should raise &lt;code&gt;max&lt;/code&gt;." I would not say "Postgres is slow." I would not claim a connection pooler makes &lt;code&gt;release()&lt;/code&gt; optional. A pooler multiplexes TCP. It does not return a client you still hold in JavaScript.&lt;/p&gt;

&lt;p&gt;Run the pad. If contract 2 does not stay pending at 50ms, your mental model of &lt;code&gt;connectionTimeoutMillis: 0&lt;/code&gt; is still the idle timeout. Those are not the same knob.&lt;/p&gt;




&lt;p&gt;Sources: &lt;a href="https://node-postgres.com/apis/pool" rel="noopener noreferrer"&gt;node-postgres Pool API&lt;/a&gt; (&lt;code&gt;max&lt;/code&gt; default 10, &lt;code&gt;connectionTimeoutMillis&lt;/code&gt; default 0, &lt;code&gt;idleTimeoutMillis&lt;/code&gt; default 10000, FIFO wait, must-release warning), &lt;a href="https://node-postgres.com/guides/pool-sizing" rel="noopener noreferrer"&gt;pool sizing&lt;/a&gt;, &lt;a href="https://www.postgresql.org/docs/current/runtime-config-connection.html" rel="noopener noreferrer"&gt;PostgreSQL &lt;code&gt;max_connections&lt;/code&gt; / &lt;code&gt;superuser_reserved_connections&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance and then edited against the official docs and a locally run Node pad (8 assertions). Any leftover mistake is mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Drain In-Flight HTTP Before Kubernetes Sends SIGKILL</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Wed, 02 Sep 2026 04:49:55 +0000</pubDate>
      <link>https://dev.to/karuha/drain-in-flight-http-before-kubernetes-sends-sigkill-a2g</link>
      <guid>https://dev.to/karuha/drain-in-flight-http-before-kubernetes-sends-sigkill-a2g</guid>
      <description>&lt;p&gt;Kubernetes’s default &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; is 30. That is not a suggestion and it is not &lt;code&gt;process.exit(0)&lt;/code&gt;. SIGTERM means: stop taking new work, finish what you already accepted, then leave. Miss the window and kubelet sends SIGKILL. The in-flight request you already 200-OK’d on the client side never gets a body.&lt;/p&gt;

&lt;p&gt;Google autocomplete for &lt;code&gt;graceful shutdown&lt;/code&gt; currently offers golang, spring boot, kubernetes, and nodejs. &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; fills in with default, default value, 30, k8s, openshift, karpenter. &lt;code&gt;kubernetes sigterm&lt;/code&gt; fills in with grace period, vs sigkill, timeout, pid 1. People search the signal. The interview is whether you spend the 30 seconds or throw them away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the interviewer actually asking?
&lt;/h2&gt;

&lt;p&gt;Most answers stop at “I listen for SIGTERM.” Node’s own process docs make that the trap. &lt;code&gt;'SIGTERM'&lt;/code&gt; and &lt;code&gt;'SIGINT'&lt;/code&gt; have default handlers that exit with &lt;code&gt;128 + signal number&lt;/code&gt;. The moment you install a listener, that default goes away. Node will no longer exit. If your handler calls &lt;code&gt;process.exit(0)&lt;/code&gt; on the first tick, you did the same thing as having no handler: you tore down sockets that still had a request on them.&lt;/p&gt;

&lt;p&gt;The Kubernetes pod-lifecycle page is more specific than a blog post. On delete, the Pod is marked Terminating. The kubelet runs any &lt;code&gt;preStop&lt;/code&gt; hook. Then the container runtime sends TERM to process 1. When the grace period expires, remaining processes get SIGKILL. The default grace period in the Pod API is 30 seconds. &lt;code&gt;preStop&lt;/code&gt; is not extra time. It comes out of the same 30.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
  participant K as kubelet
  participant P as PID 1
  participant S as in-flight request
  K-&amp;gt;&amp;gt;P: SIGTERM (grace = 30s)
  P-&amp;gt;&amp;gt;P: server.close()
  P-&amp;gt;&amp;gt;S: still writing the body
  Note over K,P: preStop + drain share the 30s
  alt in-flight hits 0
    P-&amp;gt;&amp;gt;K: exit 0
  else still busy at 30s
    K-&amp;gt;&amp;gt;P: SIGKILL
  end&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The other half of the contract sits in EndpointSlice. Terminating endpoints report &lt;code&gt;ready: false&lt;/code&gt;, so new regular traffic should stop arriving. That does not cancel the POST that already made it onto this process. You still have to drain it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doesn't &lt;code&gt;server.close()&lt;/code&gt; by itself finish the job?
&lt;/h2&gt;

&lt;p&gt;It stops accepting new connections. Since Node 19.0.0 it also reaps idle keep-alive sockets before the callback fires. The Node docs are explicit: &lt;code&gt;server.close()&lt;/code&gt; “closes all connections connected to this server which are not sending a request or waiting for a response.” Active requests stay. That is the correct default. You want those to finish.&lt;/p&gt;

&lt;p&gt;Two follow-ups the interviewer will poke.&lt;/p&gt;

&lt;p&gt;First: you are on Node 18 in production. Then &lt;code&gt;close()&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; reap idle keep-alive. &lt;code&gt;server.close()&lt;/code&gt; never fires because a phone from 40 seconds ago is still sitting in keep-alive. The documented escape is &lt;code&gt;closeIdleConnections()&lt;/code&gt;, called &lt;strong&gt;after&lt;/strong&gt; &lt;code&gt;close()&lt;/code&gt;, because a connection accepted between the two calls would otherwise race. Node 19 made that automatic. Libraries that still support 18 call it anyway. Harmless on 24.&lt;/p&gt;

&lt;p&gt;Second: a request that will not finish inside the grace period. A 2-minute report download. A webhook you forwarded to a partner who is slow. &lt;code&gt;close()&lt;/code&gt; waits forever. Kubernetes does not. At second 30 you get SIGKILL and the client sees a TCP reset. The Node API for that case is &lt;code&gt;closeAllConnections()&lt;/code&gt;, again after &lt;code&gt;close()&lt;/code&gt;. It is forceful. The docs say so. Use it as the deadline, not as step one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How small is the pad version?
&lt;/h2&gt;

&lt;p&gt;A gate, not an &lt;code&gt;http.Server&lt;/code&gt;. Clock and grace are injected so the tests do not sleep. &lt;code&gt;wrap&lt;/code&gt; is the request. &lt;code&gt;begin&lt;/code&gt; is SIGTERM. &lt;code&gt;wait&lt;/code&gt; is “I am allowed to exit.” &lt;code&gt;tickForce&lt;/code&gt; is second 30.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REFUSED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refused&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FORCED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;forced&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DRAINED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drained&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createShutdownGate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;graceMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;draining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;forceAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;inFlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waiters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;maybeResolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inFlight&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nf"&gt;w&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DRAINED&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;draining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;forceAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;graceMs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;begin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refuse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REFUSED&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REFUSED&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;inFlight&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;inFlight&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;maybeResolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;draining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wait_before_begin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inFlight&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DRAINED&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forceAt&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;forceAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;force&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FORCED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inFlight&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tickForce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;draining&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;forceAt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;forceAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;inFlight&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DRAINED&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;force&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;leftover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;w&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FORCED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;leftover&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FORCED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;leftover&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tickForce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isDraining&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;draining&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production wires &lt;code&gt;begin&lt;/code&gt; to &lt;code&gt;process.on("SIGTERM")&lt;/code&gt;, &lt;code&gt;wrap&lt;/code&gt; around the request listener, &lt;code&gt;wait&lt;/code&gt; to &lt;code&gt;server.close()&lt;/code&gt;'s callback, and &lt;code&gt;tickForce&lt;/code&gt; to &lt;code&gt;closeAllConnections()&lt;/code&gt;. The pad exists so you can fail the contracts in 20 seconds without standing up kubelet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which four contracts do you have to show?
&lt;/h2&gt;

&lt;p&gt;Contract 1: a request accepted before SIGTERM still completes. New work after &lt;code&gt;begin&lt;/code&gt; is refused. Event order is &lt;code&gt;accept&lt;/code&gt;, &lt;code&gt;begin&lt;/code&gt;, &lt;code&gt;refuse&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createShutdownGate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;graceMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setImmediate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok-inflight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should-not-run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REFUSED&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DRAINED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok-inflight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;events&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;begin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refuse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole drain. If you cannot show this, you do not have graceful shutdown. You have a signal handler that prints a log line.&lt;/p&gt;

&lt;p&gt;Contract 2: &lt;code&gt;process.exit&lt;/code&gt; on the first tick drops the in-flight request. The work never settles. The process is gone anyway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createNaiveExit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;exitNow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dropped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;naive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createNaiveExit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}));&lt;/span&gt; &lt;span class="c1"&gt;// still on the wire&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exitNow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dropped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the rolling-deploy 502. The replica coming up is healthy. The replica going down reset a connection the load balancer already handed it.&lt;/p&gt;

&lt;p&gt;Contract 3: a request that outlives the grace period is forced. &lt;code&gt;inFlight&lt;/code&gt; stays 1. SIGKILL does not wait for your &lt;code&gt;await&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;late&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createShutdownGate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;graceMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;late&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}));&lt;/span&gt;
&lt;span class="nx"&gt;late&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;late&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tickForce&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FORCED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The spoken version: I would not pretend a 2-minute export fits in 30 seconds. Either I fail that request with 503 on SIGTERM and let the client retry another replica, or I raise &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; to a number I measured. I would not sit in &lt;code&gt;close()&lt;/code&gt; and hope kubelet is patient.&lt;/p&gt;

&lt;p&gt;Contract 4: &lt;code&gt;preStop&lt;/code&gt; spends the same budget. At t=10_000 the force window has not opened. At t=29_999 it still has not. At t=30_000 an idle drain is allowed to finish. There is no bonus 30.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prestop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createShutdownGate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;graceMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;prestop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prestop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tickForce&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="nx"&gt;_999&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prestop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tickForce&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prestop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tickForce&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DRAINED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Kubernetes docs even give a 2-second one-off extension if &lt;code&gt;preStop&lt;/code&gt; is still running when the grace period expires. That is kubelet covering the hook, not extra drain time for your HTTP server. If the hook sleeps 25 seconds, you have about 5 seconds of SIGTERM left. I have seen a &lt;code&gt;preStop: sleep 20&lt;/code&gt; copied from a nginx snippet into a Node service. Then people wonder why in-flight GraphQL requests reset during every rollout.&lt;/p&gt;

&lt;p&gt;I ran those four with &lt;code&gt;node sigterm-drain.mjs&lt;/code&gt;. All green.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you say out loud in the backend / SRE round?
&lt;/h2&gt;

&lt;p&gt;Something like:&lt;/p&gt;

&lt;p&gt;I treat SIGTERM as a drain, not an exit. First I stop being a Service endpoint: fail readiness, then &lt;code&gt;server.close()&lt;/code&gt; so we do not accept new TCP. In-flight requests keep running. I wait on a counter, not on a &lt;code&gt;sleep(30)&lt;/code&gt;. If the counter hits zero I exit 0. If the grace period is about to expire I call &lt;code&gt;closeAllConnections()&lt;/code&gt;, log how many were still open, and exit. &lt;code&gt;preStop&lt;/code&gt; time is subtracted from the same 30 seconds, so I would not sleep in the hook. TERM has to reach PID 1. If the container PID 1 is a shell that does not forward signals, none of this runs and kubelet SIGKILLs a process that never knew it was dying.&lt;/p&gt;

&lt;p&gt;The AceRound &lt;a href="https://www.aceround.app/blog/devops-engineer-interview-ai/" rel="noopener noreferrer"&gt;DevOps engineer interview guide&lt;/a&gt; puts rolling deploys and probe failures in the production-scenario bucket. aceround.app — AI interview assistant is useful when the next hour is talking through the 30-second window while someone interrupts you. It does not replace running the four contracts above.&lt;/p&gt;

&lt;p&gt;A few follow-ups I would expect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Follow-up&lt;/th&gt;
&lt;th&gt;What I would not say&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Can't we just raise the grace period to 300?&lt;/td&gt;
&lt;td&gt;"Then we never have to drain." Long grace periods make rollouts slow. Measure p99 in-flight duration, set grace to that plus a buffer, still refuse new work immediately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What about HTTP/2 or WebSockets?&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;closeAllConnections()&lt;/code&gt; does not destroy upgraded sockets. Those need their own shutdown. I would say so instead of waving &lt;code&gt;server.close()&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why fail readiness before SIGTERM?&lt;/td&gt;
&lt;td&gt;Because EndpointSlice removal is not instantaneous. A &lt;code&gt;preStop&lt;/code&gt; that only sleeps, with no readiness fail, still takes traffic for part of the window. Fail the probe first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;process.on("SIGTERM", () =&amp;gt; process.exit(0))&lt;/code&gt;?&lt;/td&gt;
&lt;td&gt;That uninstalls Node's default handler and then exits anyway. Same dropped request, extra confidence.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I would not ship this &lt;code&gt;Map&lt;/code&gt; as the process manager. Production is &lt;code&gt;http.Server&lt;/code&gt; plus a readiness flag plus a deadline timer. The pad exists so you can fail “new work after SIGTERM” in 20 seconds instead of drawing a rectangle labelled “graceful” on a whiteboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;Does &lt;code&gt;server.close()&lt;/code&gt; wait for in-flight requests?&lt;/p&gt;

&lt;p&gt;It stops new connections and, since Node 19, idle keep-alive. Active requests keep running until they end or you call &lt;code&gt;closeAllConnections()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Is SIGTERM the same on Windows?&lt;/p&gt;

&lt;p&gt;Node can listen for &lt;code&gt;'SIGTERM'&lt;/code&gt; on Windows. Kubernetes Linux nodes send TERM to PID 1. Do not demo this with Ctrl+C and assume it is the same path. Ctrl+C is SIGINT.&lt;/p&gt;

&lt;p&gt;Why 30 seconds?&lt;/p&gt;

&lt;p&gt;That is the Pod API default when &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; is nil. &lt;code&gt;kubectl delete&lt;/code&gt; uses the same default. You can set it. You cannot ignore SIGKILL after it.&lt;/p&gt;

&lt;p&gt;If my handler never calls &lt;code&gt;process.exit&lt;/code&gt;, what happens?&lt;/p&gt;

&lt;p&gt;Node no longer exits on SIGTERM once a listener exists. You sit in Terminating until kubelet SIGKILLs you at second 30. The log line that says “received SIGTERM” is not a shutdown.&lt;/p&gt;

&lt;p&gt;Should the drain reject new HTTP with 503 or just close the listen socket?&lt;/p&gt;

&lt;p&gt;Both. Close the listen socket so the kernel refuses new TCP. Return 503 for anything already accepted that you choose not to run. Clients retry the next replica.&lt;/p&gt;

&lt;p&gt;If you already handle SIGTERM in production, does the handler wait for &lt;code&gt;inFlight === 0&lt;/code&gt;, or does it &lt;code&gt;process.exit&lt;/code&gt; on the first tick?&lt;/p&gt;

&lt;p&gt;Drafted with AI assistance, then edited. The Node contracts were run locally before publishing. Grace period default of 30 seconds and TERM-then-KILL: Kubernetes Pod lifecycle and the Pod API. &lt;code&gt;server.close&lt;/code&gt; / &lt;code&gt;closeIdleConnections&lt;/code&gt; / &lt;code&gt;closeAllConnections&lt;/code&gt;: Node.js HTTP docs, including the v19.0.0 idle-connection change. SIGTERM listener replacing the default exit: Node.js process signal events.&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Fencing Tokens Stop the Write Your Redis Lease Already Released</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Tue, 01 Sep 2026 09:47:31 +0000</pubDate>
      <link>https://dev.to/karuha/fencing-tokens-stop-the-write-your-redis-lease-already-released-30ec</link>
      <guid>https://dev.to/karuha/fencing-tokens-stop-the-write-your-redis-lease-already-released-30ec</guid>
      <description>&lt;p&gt;A Redis &lt;code&gt;SET NX PX&lt;/code&gt; lock is a lease. It is not mutual exclusion. If the holder pauses longer than the TTL, a second client acquires the same key, writes, and the first client still writes when it wakes up. The fix is a fencing token: a number that only goes up, checked by storage, not by &lt;code&gt;lease.isHeld()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Google autocomplete for &lt;code&gt;fencing token&lt;/code&gt; currently suggests pattern, redis, lock, zookeeper, etcd, and redlock. &lt;code&gt;redis distributed lock&lt;/code&gt; fills in with Java, Python, Go, C#, Spring Boot. People are searching the primitive by name. The interview is whether you know the lease is not the safety boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the interviewer actually asking?
&lt;/h2&gt;

&lt;p&gt;Most whiteboard answers stop at Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET lock:acct:1 &amp;lt;random&amp;gt; NX PX 30000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the pattern Redis itself documents on the &lt;code&gt;SET&lt;/code&gt; command page: create the key only if it is missing, auto-release with an expiry, unlock with a Lua compare-and-delete so you do not &lt;code&gt;DEL&lt;/code&gt; someone else's lease. Fine for &lt;strong&gt;efficiency&lt;/strong&gt;. Martin Kleppmann's 2016 note on distributed locking splits the use cases in one paragraph. If two nodes do the same work and you pay AWS five extra cents, a best-effort lock is enough. If two nodes debit the same account, you need correctness. &lt;code&gt;SET NX&lt;/code&gt; does not give you that.&lt;/p&gt;

&lt;p&gt;The failure is not exotic. Kleppmann's diagram is the whole interview: client A gets token 33, pauses (GC, a page fault, a SIGSTOP, a delayed packet), the lease expires, client B gets token 34 and writes. A resumes and writes with 33. Storage that only checks "did you once hold a lock?" accepts both.&lt;/p&gt;

&lt;p&gt;I ran that sequence in 70 lines of Node with an injected clock. No Redis process. The clock is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doesn't checking the lease just before the write save you?
&lt;/h2&gt;

&lt;p&gt;Because the pause can happen &lt;strong&gt;after&lt;/strong&gt; the check. Kleppmann is blunt about this: you cannot insert &lt;code&gt;if (!lock.expired) write()&lt;/code&gt; and call it fixed. GC can stop the thread between the last check and the write. A packet can sit in the network for a long time after you sent it. He cites a GitHub incident where packets were delayed about 90 seconds. Your &lt;code&gt;isHeld()&lt;/code&gt; returned true. The write arrived after B already committed.&lt;/p&gt;

&lt;p&gt;So the resource has to be the one that says no. The lock service only issues a strictly increasing token. Storage remembers the highest token it accepted and rejects anything that went backwards.&lt;/p&gt;

&lt;p&gt;ZooKeeper people already have this: &lt;code&gt;zxid&lt;/code&gt; or the znode version. etcd has &lt;code&gt;revision&lt;/code&gt;. Redis &lt;code&gt;SET NX&lt;/code&gt; returns &lt;code&gt;OK&lt;/code&gt; or nil. It does not return a monotonic fence. That is Kleppmann's actual objection to Redlock as a &lt;strong&gt;correctness&lt;/strong&gt; lock, not a dunk on Redis.&lt;/p&gt;

&lt;h2&gt;
  
  
  How small is the pad version?
&lt;/h2&gt;

&lt;p&gt;Two objects. A lease map that mints a fence on every successful acquire. An account that optionally enforces &lt;code&gt;token &amp;gt; lastFence&lt;/code&gt; on every debit. Clock and TTL are injected so the tests do not sleep.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STALE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stale_fence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HELD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lock_held&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createLeaseLock&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttlMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nextFence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Kleppmann's diagram&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HELD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;holder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextFence&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ttlMs&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fence&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isHeld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isHeld&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;fence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lastFence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fence&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lastFence&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;STALE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastFence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;lastFence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;writes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;getBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;getLastFence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lastFence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writes&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The starting fence is 33 so the narration matches the blog post you should actually cite. Production would start at 1. I do not care.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which six contracts do you have to show?
&lt;/h2&gt;

&lt;p&gt;Contract 1: while the lease is live, B cannot acquire. This is the &lt;code&gt;NX&lt;/code&gt; part. If you cannot show this, you do not have a lock at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLeaseLock&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ttlMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contract 2: TTL is a liveness feature. After 31ms, A is not the holder. B acquires. The new fence is 34, strictly greater.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isHeld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redis documents this as a liveness property: even if the client crashes, the key expires. That is the part people remember. The next contract is the part they skip.&lt;/p&gt;

&lt;p&gt;Contract 3: turn fencing &lt;strong&gt;off&lt;/strong&gt;. Same pause. B debits 10 with token 34. A wakes up and debits 10 with token 33. Both succeed. Balance is 80. Last write is token 33. You just lost B's update, or you double-charged the account, depending how you read the ledger.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unfenced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;unfenced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unfenced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unfenced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBalance&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the broken &lt;code&gt;writeData&lt;/code&gt; in Kleppmann's post, executable.&lt;/p&gt;

&lt;p&gt;Contract 4: same sequence, fencing on. B's debit is accepted. A's debit returns &lt;code&gt;stale_fence&lt;/code&gt;. Balance stays 90. &lt;code&gt;lastFence&lt;/code&gt; is 34.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fenced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lock2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLeaseLock&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ttlMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aLease&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lock2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bLease&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lock2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fenced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bLease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fenced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aLease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;STALE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fenced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBalance&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQVsiQSBob2xkcyB0b2tlbiAzMyJdIC0tPiBQWyJwYXVzZSBsb25nZXIgdGhhbiBUVEwiXQogIFAgLS0-IEJbIkIgYWNxdWlyZXMgdG9rZW4gMzQiXQogIEIgLS0-IFdbInN0b3JhZ2UgYWNjZXB0cyAzNCJdCiAgVyAtLT4gUlsiQSByZXN1bWVzIHdpdGggMzMiXQogIFIgLS0-IFhbInJlamVjdCAzMyBiZWNhdXNlIDMzIDwgMzQiXQogIGNsYXNzRGVmIGhvbGQgZmlsbDojMTY0ZTYzLHN0cm9rZTojNjdlOGY5LGNvbG9yOiNlY2ZlZmYsZm9udC1zaXplOjE4cHgKICBjbGFzc0RlZiBwYXVzZSBmaWxsOiNiNDUzMDksc3Ryb2tlOiNmY2QzNGQsY29sb3I6I2ZmZixmb250LXNpemU6MThweAogIGNsYXNzRGVmIG9rIGZpbGw6IzBlNzQ5MCxzdHJva2U6IzIyZDNlZSxjb2xvcjojZWNmZWZmLGZvbnQtc2l6ZToxOHB4CiAgY2xhc3NEZWYgYmFkIGZpbGw6Izk5MWIxYixzdHJva2U6I2ZjYTVhNSxjb2xvcjojZmZmLGZvbnQtc2l6ZToxOHB4CiAgY2xhc3MgQSxCIGhvbGQKICBjbGFzcyBQIHBhdXNlCiAgY2xhc3MgVyBvawogIGNsYXNzIFIsWCBiYWQK%3Fwidth%3D1000%26height%3D420%26bgColor%3D0f172a" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQVsiQSBob2xkcyB0b2tlbiAzMyJdIC0tPiBQWyJwYXVzZSBsb25nZXIgdGhhbiBUVEwiXQogIFAgLS0-IEJbIkIgYWNxdWlyZXMgdG9rZW4gMzQiXQogIEIgLS0-IFdbInN0b3JhZ2UgYWNjZXB0cyAzNCJdCiAgVyAtLT4gUlsiQSByZXN1bWVzIHdpdGggMzMiXQogIFIgLS0-IFhbInJlamVjdCAzMyBiZWNhdXNlIDMzIDwgMzQiXQogIGNsYXNzRGVmIGhvbGQgZmlsbDojMTY0ZTYzLHN0cm9rZTojNjdlOGY5LGNvbG9yOiNlY2ZlZmYsZm9udC1zaXplOjE4cHgKICBjbGFzc0RlZiBwYXVzZSBmaWxsOiNiNDUzMDksc3Ryb2tlOiNmY2QzNGQsY29sb3I6I2ZmZixmb250LXNpemU6MThweAogIGNsYXNzRGVmIG9rIGZpbGw6IzBlNzQ5MCxzdHJva2U6IzIyZDNlZSxjb2xvcjojZWNmZWZmLGZvbnQtc2l6ZToxOHB4CiAgY2xhc3NEZWYgYmFkIGZpbGw6Izk5MWIxYixzdHJva2U6I2ZjYTVhNSxjb2xvcjojZmZmLGZvbnQtc2l6ZToxOHB4CiAgY2xhc3MgQSxCIGhvbGQKICBjbGFzcyBQIHBhdXNlCiAgY2xhc3MgVyBvawogIGNsYXNzIFIsWCBiYWQK%3Fwidth%3D1000%26height%3D420%26bgColor%3D0f172a" alt="Client A pauses past TTL, client B writes with token 34, storage rejects token 33" width="1000" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contract 5: equal token is stale. &lt;code&gt;&amp;gt;&lt;/code&gt; not &lt;code&gt;&amp;gt;=&lt;/code&gt;. A retry of the &lt;strong&gt;same&lt;/strong&gt; successful write should be idempotent at the business layer (same debit id), not by sending the same fence again as a second debit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fenced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fenced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;STALE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contract 6: the TOCTOU the interviewer will poke. A checks &lt;code&gt;isHeld&lt;/code&gt; at t=0. Clock jumps to 31. B acquires and writes. A then writes with the old fence. Fencing still rejects it. The check was theater.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isHeld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// B writes, A writes. Only B's token survives.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran those six with &lt;code&gt;node fencing-token.mjs&lt;/code&gt;. All green.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you say out loud in the system-design round?
&lt;/h2&gt;

&lt;p&gt;Something like:&lt;/p&gt;

&lt;p&gt;I would ask whether the lock is for efficiency or correctness. For a cache fill, &lt;code&gt;SET NX PX&lt;/code&gt; on one Redis is enough and I would say so. For an account debit I would not trust the lease. The lock service returns a monotonically increasing fencing token. Every write to the resource carries that token. The resource rejects anything less than or equal to the highest token it has already accepted. The check lives in the &lt;code&gt;UPDATE ... WHERE last_fence &amp;lt; $token&lt;/code&gt; (or the equivalent compare-and-set). A client-side &lt;code&gt;if (lease.isValid())&lt;/code&gt; is not the check.&lt;/p&gt;

&lt;p&gt;If they say "we use Redlock," I would not pick a fight with Redis's page. I would say: Redlock still does not hand me a fence. Without a fence, a paused holder can still write after a new holder has written. For correctness I want ZooKeeper/etcd (zxid / revision) or a database unique constraint plus a generation column. Redis is fine as an efficiency lock.&lt;/p&gt;

&lt;p&gt;The backend round that asks "how do you lock this?" is the same round as outbox, idempotency keys, and the 3 AM CPU spike. The &lt;a href="https://www.aceround.app/blog/backend-developer-interview-ai/" rel="noopener noreferrer"&gt;AceRound backend-developer interview guide&lt;/a&gt; puts those in the production-scenario bucket. aceround.app — AI interview assistant is useful when the next hour is talking through the pause while someone interrupts you. It does not replace running the six contracts above.&lt;/p&gt;

&lt;p&gt;A few follow-ups I would expect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Follow-up&lt;/th&gt;
&lt;th&gt;What I would not say&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Can't we just heartbeat and extend the TTL?&lt;/td&gt;
&lt;td&gt;"Then the pause cannot happen." Heartbeats help a &lt;strong&gt;healthy&lt;/strong&gt; holder. They do not fence a write that is already in flight after the lease died.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis &lt;code&gt;GET&lt;/code&gt; the lock just before &lt;code&gt;SET&lt;/code&gt; the account?&lt;/td&gt;
&lt;td&gt;That is two round trips and still a race. The account write has to be conditional on the fence in one atomic step.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UUID as the token?&lt;/td&gt;
&lt;td&gt;UUIDs do not order. Storage cannot tell 33 from 34. Monotonic is the whole trick.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What about Redlock's five nodes?&lt;/td&gt;
&lt;td&gt;Majority voting is about the lock service surviving a Redis crash. It still does not produce a fence. Kleppmann's critique starts there.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I would not ship this &lt;code&gt;Map&lt;/code&gt;. Production needs the fence on the resource that can actually lose money, persisted, compared atomically. The pad exists so you can fail the stale debit in 20 seconds instead of drawing a mutex on a whiteboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;Is a fencing token the same as an idempotency key?&lt;/p&gt;

&lt;p&gt;No. An idempotency key says "this client intent runs once." A fencing token says "this generation of the lock is allowed to write, older generations are not." You usually want both. The debit should have an idempotency key so B's retry does not debit twice, &lt;strong&gt;and&lt;/strong&gt; a fence so A's delayed packet cannot debit after B.&lt;/p&gt;

&lt;p&gt;Does every lock need this?&lt;/p&gt;

&lt;p&gt;No. Kleppmann's efficiency case is real. Singleflight for a cache stampede, a rate-limit counter, a "don't send the same email twice if we can help it" lock: lose one and you are annoyed. Do not pay ZooKeeper for that.&lt;/p&gt;

&lt;p&gt;Why start the demo at 33?&lt;/p&gt;

&lt;p&gt;So the story matches the diagram in the 2016 post. Interviewers who have read it will nod. If they have not, 1 and 2 work the same.&lt;/p&gt;

&lt;p&gt;Can the storage service skip the check if the client promises it still holds the lock?&lt;/p&gt;

&lt;p&gt;That is the broken code sample. The client is the one that paused. Storage is the only place the side effect becomes true.&lt;/p&gt;




&lt;p&gt;If you already use Redis locks in production, does the resource you protect compare a fence, or does it trust &lt;code&gt;SET NX&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Drafted with AI assistance, then edited. The Node contracts were run locally before publishing. Lease vs mutex, token 33 vs 34, and the "check before write" hole: &lt;a href="https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html" rel="noopener noreferrer"&gt;Kleppmann, 8 Feb 2016&lt;/a&gt;. &lt;code&gt;SET key value NX EX|PX&lt;/code&gt;: &lt;a href="https://redis.io/docs/latest/commands/set/" rel="noopener noreferrer"&gt;Redis SET&lt;/a&gt;. Redlock's safety/liveness list: &lt;a href="https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/" rel="noopener noreferrer"&gt;Distributed locks with Redis&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Why JWT Refresh Token Rotation Fails Without a Token Family</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Mon, 31 Aug 2026 14:22:12 +0000</pubDate>
      <link>https://dev.to/karuha/why-jwt-refresh-token-rotation-fails-without-a-token-family-29po</link>
      <guid>https://dev.to/karuha/why-jwt-refresh-token-rotation-fails-without-a-token-family-29po</guid>
      <description>&lt;p&gt;JWT refresh token rotation is not "issue a new token and delete the old one." RFC 9700 says you keep the relationship. If a spent token shows up again, you revoke the whole family. I ran that contract in 80 lines of Node. Rotation without reuse detection still hands the session to whoever got there second.&lt;/p&gt;

&lt;p&gt;Google autocomplete for &lt;code&gt;jwt refresh token rotation&lt;/code&gt; currently suggests meaning, header, Stripe, example, generator, and pattern. People are searching the mechanism by name, not "auth in general."&lt;/p&gt;

&lt;h2&gt;
  
  
  What do interviewers actually want when they say "rotate the refresh token"?
&lt;/h2&gt;

&lt;p&gt;A common whiteboard answer is: access token lives 15 minutes, refresh token lives 30 days, each refresh mints a new access token. That is a session, not rotation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9700.html#section-4.14" rel="noopener noreferrer"&gt;RFC 9700 section 4.14&lt;/a&gt; is the current OAuth 2.0 security BCP (January 2025). For public clients it requires one of two things: sender-constrained refresh tokens (DPoP / mTLS), or rotation. Rotation is defined as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;issue a new refresh token on every access-token refresh&lt;/li&gt;
&lt;li&gt;invalidate the previous one&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;retain the relationship&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;if a later request presents the invalidated token, revoke the active one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last two bullets are the interview. Delete-on-use with no memory of the parent grant is just a shorter-lived secret. The attacker who wins the race keeps working. The legitimate client gets &lt;code&gt;invalid_grant&lt;/code&gt; and you never find out why.&lt;/p&gt;

&lt;p&gt;Auth0's public docs call the retained relationship a token family and the alarm "automatic reuse detection." Their example is the one I use in loops: legitimate client holds RT1, it leaks, legitimate client exchanges RT1 for RT2, attacker replays RT1, the family including RT2 dies, everyone re-authenticates. The log event they name is &lt;code&gt;ferrt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I am not reimplementing Auth0. I wanted a store I can run in the pad with &lt;code&gt;node:assert/strict&lt;/code&gt; and then narrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does deleting the old token fail the theft case?
&lt;/h2&gt;

&lt;p&gt;Three records. That is the whole model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// opaque string the client holds&lt;/span&gt;
  &lt;span class="nx"&gt;familyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// the grant / lineage&lt;/span&gt;
  &lt;span class="nx"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 0, 1, 2...&lt;/span&gt;
  &lt;span class="nx"&gt;spentAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// null while this row is current&lt;/span&gt;
  &lt;span class="nx"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the token issued when this row was spent&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spent is not deleted. If you &lt;code&gt;Map.delete(rt1)&lt;/code&gt; after minting RT2, a replay of RT1 looks identical to a typo: &lt;code&gt;invalid_grant&lt;/code&gt;. You cannot tell theft from a stale client. You also cannot revoke RT2, because you no longer know they are siblings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQVtSVDEgcHJlc2VudGVkXSAtLT4gQntBbHJlYWR5IHNwZW50P30KICBCIC0tPnxubzogY3VycmVudHwgQ1tJc3N1ZSBSVDIsIG1hcmsgUlQxIHNwZW50XQogIEIgLS0-fHllczogcHJldmlvdXMgKyBpbnNpZGUgbGVld2F5fCBEW1JldHVybiBzYW1lIFJUMl0KICBCIC0tPnx5ZXM6IG9sZGVyIG9yIHBhc3QgbGVld2F5fCBFW1Jldm9rZSB3aG9sZSBmYW1pbHldCiAgQyAtLT4gRltMZWdpdGltYXRlIHNlc3Npb24gY29udGludWVzXQogIEQgLS0-IEYKICBFIC0tPiBHW0ZvcmNlIHJlLWF1dGhdCg%3Fwidth%3D1000%26height%3D420%26bgColor%3D0f172a" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQVtSVDEgcHJlc2VudGVkXSAtLT4gQntBbHJlYWR5IHNwZW50P30KICBCIC0tPnxubzogY3VycmVudHwgQ1tJc3N1ZSBSVDIsIG1hcmsgUlQxIHNwZW50XQogIEIgLS0-fHllczogcHJldmlvdXMgKyBpbnNpZGUgbGVld2F5fCBEW1JldHVybiBzYW1lIFJUMl0KICBCIC0tPnx5ZXM6IG9sZGVyIG9yIHBhc3QgbGVld2F5fCBFW1Jldm9rZSB3aG9sZSBmYW1pbHldCiAgQyAtLT4gRltMZWdpdGltYXRlIHNlc3Npb24gY29udGludWVzXQogIEQgLS0-IEYKICBFIC0tPiBHW0ZvcmNlIHJlLWF1dGhdCg%3Fwidth%3D1000%26height%3D420%26bgColor%3D0f172a" alt="Refresh token reuse: current token rotates, previous token inside leeway retries, older replay kills the family" width="1000" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The store below is dependency-free. Clock and leeway are injected so the tests do not sleep.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REUSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refresh_token_reused&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UNKNOWN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid_grant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FAMILY_REVOKED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;family_revoked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;leewayMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;families&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;familyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;familyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;spentAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;byToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;family&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;families&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;familyId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;familyId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;families&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;familyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lineage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;revoked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;familyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;presented&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;byToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;presented&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UNKNOWN&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;family&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;families&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;familyId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revoked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FAMILY_REVOKED&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spentAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;familyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generation&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replacement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generation&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isImmediatePrevious&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replacement&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replacement&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isImmediatePrevious&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spentAt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;leewayMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;byToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revoked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REUSE&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rotate&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which five contracts do you have to show?
&lt;/h2&gt;

&lt;p&gt;Contract 1: happy path. RT1 in, RT2 out, different string, generation 1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rt1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rt1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rt1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contract 2: rotate twice, then replay RT1. RT1 is no longer the immediate previous, so this is reuse, not a retry. The family dies. RT3, which the legitimate client is holding, must also fail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rt1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;REUSE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FAMILY_REVOKED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the RFC 9700 sentence in executable form: the server "cannot determine which party submitted the invalid refresh token, but it will revoke the active refresh token."&lt;/p&gt;

&lt;p&gt;Contract 3: flaky network. Auth0's dashboard calls this the rotation overlap period, in seconds. A mobile client sends RT1, loses the response, retries RT1 500ms later. If you treat that as theft, you lock out real users. The demo uses 2000ms so the test can move a fake clock.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retryStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;leewayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;retryStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;retryStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;retryStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// same RT2, not a new generation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contract 4: same retry after the window. 2501ms later, RT1 is theft. Family gone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2501&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;REUSE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Auth0's support note on leeway is stricter than "any spent token may retry." Only the previous generation is inside the window. The generation before that still kills the family, even if the clock says you are inside N seconds. Contract 2 already covers that: RT1 after RT1→RT2→RT3 is older than previous.&lt;/p&gt;

&lt;p&gt;Contract 5: attacker spends RT1 first. Advance the clock past leeway, then the legitimate client presents RT1. Same alarm. The attacker's RT2 dies too. I ran this with an injected clock at t=0 then t=2501. If you skip the clock and default &lt;code&gt;Date.now()&lt;/code&gt;, the second call lands inside 2 seconds and looks like a retry. That is a real production footgun, not a test smell.&lt;/p&gt;

&lt;p&gt;Unknown tokens stay &lt;code&gt;invalid_grant&lt;/code&gt;. Do not revoke some other user's family because of a random string.&lt;/p&gt;

&lt;p&gt;I ran those eight assertions (happy path, double-rotate replay, post-revoke current, retry inside window, retry after window, attacker-first plus family kill, unknown token) with &lt;code&gt;node rt-rotation.mjs&lt;/code&gt;. All green.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you say out loud in the 45-minute backend round?
&lt;/h2&gt;

&lt;p&gt;Something like:&lt;/p&gt;

&lt;p&gt;I would keep refresh tokens one-time. I would not delete the spent row. Replay of a spent token is reuse detection: revoke the family, force a new grant. Retry of the immediate previous token inside a short overlap is a lost response, not theft. RFC 9700 also allows DPoP instead of rotation. I picked rotation because the pad is an in-memory map. Production still needs idle expiry.&lt;/p&gt;

&lt;p&gt;That speech is the score. The map is evidence.&lt;/p&gt;

&lt;p&gt;A few follow-ups I would expect, and the short answers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Follow-up&lt;/th&gt;
&lt;th&gt;What I would not say&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Why not put the family id inside a signed JWT refresh token?&lt;/td&gt;
&lt;td&gt;"Because JWTs cannot be revoked." They can, you still need a denylist or a family row. Encoding the grant id in the token is an &lt;a href="https://www.rfc-editor.org/rfc/rfc9700.html#section-4.14" rel="noopener noreferrer"&gt;RFC 9700 implementation note&lt;/a&gt;, not a substitute for the store.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How long is access token vs refresh token?&lt;/td&gt;
&lt;td&gt;Numbers without rotation are trivia. 5–60 minutes for access is common. Refresh idle expiry is the SHOULD in 4.14.2. Pick a number and say it is policy, not physics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SPA / localStorage?&lt;/td&gt;
&lt;td&gt;Auth0's rotation doc exists partly because ITP broke silent iframe cookie refresh. Rotation is the remediation they document. I still would not keep a refresh token in &lt;code&gt;localStorage&lt;/code&gt; if I have an HttpOnly cookie option.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe Idempotency-Key vs this?&lt;/td&gt;
&lt;td&gt;Different object. Idempotency keys dedupe a POST. Token families detect two holders of one secret. I already shipped a Node idempotency guard in July; this is the sibling question.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The backend interview that asks "JWT vs session cookies, OAuth flows, idempotency keys" is the same round. The &lt;a href="https://www.aceround.app/blog/backend-developer-interview-ai/" rel="noopener noreferrer"&gt;AceRound backend-developer interview guide&lt;/a&gt; lists those as the REST/architecture bucket, sitting next to the 3 AM CPU production scenario. Rotation is one concrete artifact from that bucket. aceround.app — AI interview assistant is useful when the next hour is verbal walkthrough under interruption. It does not replace running the five contracts above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;It is not "never use JWT." Access tokens can stay JWT. The refresh token in this pad version is an opaque random 16 bytes on purpose. Mixing "stateless JWT" with "I need to revoke a family" is how people invent a denylist and then claim they are stateless.&lt;/p&gt;

&lt;p&gt;It is not a license to skip sender-constrained tokens. RFC 9700 lists rotation &lt;strong&gt;or&lt;/strong&gt; DPoP/mTLS for public clients. If the interviewer is on a native app with a secure enclave, DPoP is the stronger answer and you should say so.&lt;/p&gt;

&lt;p&gt;I would not ship this Map. Production needs a row per token, a unique constraint on the current token, and the rotate-and-revoke in one transaction. The pad version exists so you can fail the replay case in 30 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;Is reuse detection the same as token rotation?&lt;/p&gt;

&lt;p&gt;No. Rotation makes the secret one-time. Reuse detection is the alarm when a one-time secret is presented twice. Skip the alarm and the attacker who exchanged first keeps a valid RT2.&lt;/p&gt;

&lt;p&gt;Why kill RT2, which the legitimate user is holding?&lt;/p&gt;

&lt;p&gt;Because the server cannot tell who is the thief. RFC 9700 is explicit: stop the attack at the cost of a fresh grant. The legitimate user signs in again. The attacker does not keep a quiet session.&lt;/p&gt;

&lt;p&gt;How long should the overlap window be?&lt;/p&gt;

&lt;p&gt;Auth0 configures it in seconds. Too long and you widen the replay margin. Too short and a lost mobile response locks the user out. I would start at a few seconds, log &lt;code&gt;ferrt&lt;/code&gt;-style events, and tune from false-positive rate, not from a blog number.&lt;/p&gt;

&lt;p&gt;Do confidential server clients still need this?&lt;/p&gt;

&lt;p&gt;RFC 6749 already binds refresh tokens to the client that received them. 9700 still recommends rotation as an extra measure. For public clients (SPA, native) it is a MUST: rotation or sender-constrained.&lt;/p&gt;




&lt;p&gt;If you already rotate refresh tokens in production, does a replay of generation N-2 revoke the family, or does it just return &lt;code&gt;invalid_grant&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Drafted with AI assistance, then edited. The Node contracts were run locally before publishing. Rotation + reuse detection: &lt;a href="https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation" rel="noopener noreferrer"&gt;Auth0 docs&lt;/a&gt;. Public-client MUST: &lt;a href="https://www.rfc-editor.org/rfc/rfc9700.html#section-4.14" rel="noopener noreferrer"&gt;RFC 9700 §4.14.2&lt;/a&gt;. Overlap / previous-generation only: &lt;a href="https://support.auth0.com/center/s/article/Refresh-token-leeway" rel="noopener noreferrer"&gt;Auth0 support on leeway&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Google's AI-Assisted Coding Interview: Catch the Cached 503 the Model Will Ship</title>
      <dc:creator>Karuha</dc:creator>
      <pubDate>Sun, 30 Aug 2026 13:17:50 +0000</pubDate>
      <link>https://dev.to/karuha/googles-ai-assisted-coding-interview-catch-the-cached-503-the-model-will-ship-3o2k</link>
      <guid>https://dev.to/karuha/googles-ai-assisted-coding-interview-catch-the-cached-503-the-model-will-ship-3o2k</guid>
      <description>&lt;p&gt;Google's 2026 AI-assisted coding interview does not grade a green test run. The new code-comprehension round scores whether you reject a patch that compiles. I planted a sticky 503 in a promise cache. The model's first fix still shipped the outage.&lt;/p&gt;

&lt;p&gt;I have not sat the Google pilot. I'm going off &lt;a href="https://www.businessinsider.com/google-job-interview-software-engineers-ai-assistant-coding-2026-5" rel="noopener noreferrer"&gt;Business Insider's May 7 reporting&lt;/a&gt; and &lt;a href="https://blog.google/innovation-and-ai/infrastructure-and-cloud/google-cloud/cloud-next-2026-sundar-pichai/" rel="noopener noreferrer"&gt;Google's own April 22 Cloud Next note&lt;/a&gt;. The drill below is what I would run the night before a sanctioned AI-assisted loop, not a stealth overlay for a round that still bans tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Google's AI-assisted coding interview actually score?
&lt;/h2&gt;

&lt;p&gt;Business Insider reviewed an internal document and got a Google spokesperson on the record. Starting in the second half of 2026, junior and mid-level software roles on selected US teams (Cloud, plus platforms and devices) get an approved assistant during a &lt;strong&gt;code comprehension&lt;/strong&gt; round. The assistant in the pilot is Gemini. Candidates are expected to read, debug, and optimize an existing codebase. Interviewers score "AI fluency": prompt engineering, output validation, and debugging.&lt;/p&gt;

&lt;p&gt;Brian Ong, Google's VP of recruiting, told BI the pilot exists "to be more reflective of how our teams are operating in the AI era."&lt;/p&gt;

&lt;p&gt;That maps to a number Google already published. On April 22, 2026, Sundar Pichai wrote that &lt;strong&gt;75% of all new code at Google is now AI-generated and approved by engineers&lt;/strong&gt;, up from 50% last fall. If three quarters of the diff is model-authored, the interview that still asks you to invert a binary tree from a blank buffer is testing a job that is shrinking.&lt;/p&gt;

&lt;p&gt;The document's phrase is "human-led, AI-assisted." You own the hypothesis. Gemini types. You decide whether the types were allowed to land.&lt;/p&gt;

&lt;p&gt;This is not unique to Google. &lt;a href="https://coderpad.io/blog/hiring-developers/ai-in-the-interview-is-not-cheating-it-is-the-job-according-to-meta/" rel="noopener noreferrer"&gt;CoderPad's October 30, 2025 writeup of Meta's pilot&lt;/a&gt; quotes Meta recruiting saying it is not cheating, it is how the job is evolving. BI also names Canva and Cognition as companies that already let candidates use AI in technical interviews. Google search autocomplete for &lt;code&gt;ai assisted coding interview&lt;/code&gt; currently suggests Meta, Microsoft, Canva, prep, mock, and reddit as follow-on queries. People are searching the format by name.&lt;/p&gt;

&lt;p&gt;If your loop does &lt;strong&gt;not&lt;/strong&gt; say AI is allowed, none of this is permission to sneak a second window onto a shared screen. The sanctioned round and the stealth overlay are opposite products. Mixing them up is how people get offers pulled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does caching the in-flight promise look correct?
&lt;/h2&gt;

&lt;p&gt;Here is the kind of 40-line module I would expect in a code-comprehension pad. It looks like a reasonable answer to "don't stampede the origin."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchJsonCached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two parallel &lt;code&gt;/user&lt;/code&gt; reads hit &lt;code&gt;fetcher&lt;/code&gt; once. That part is right. The bug is the rejected promise staying in the &lt;code&gt;Map&lt;/code&gt;. A single 503 becomes the answer forever.&lt;/p&gt;

&lt;p&gt;I asked a model to "fix the cache so a 503 does not break later requests." The first patch looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchJsonCached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three mistakes, all the kind that still compile:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;try/catch&lt;/code&gt; does not wrap the awaited promise, so it never sees the 503.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;res.ok&lt;/code&gt; is gone, so a 503 body gets parsed as success JSON.&lt;/li&gt;
&lt;li&gt;The 503 payload is still sticky in the &lt;code&gt;Map&lt;/code&gt;. The next call never reaches the network.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the failure mode the round is built to catch. The model optimized for "don't throw." The interviewer is scoring "don't poison the cache."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnpnltzcr2hvdkai8as7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnpnltzcr2hvdkai8as7t.png" alt="Sticky promise cache versus delete-on-reject: a 503 that stays in the Map never retries, while deleting the rejected entry lets the next call return HTTP 200" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you prove the 503 is still sticky?
&lt;/h2&gt;

&lt;p&gt;Do not argue with the model. Replay the outage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sequenceFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;getCalls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;calls&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;jsonRes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contract 1, buggy module: first call 503, second call should have been 200.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sequenceFetcher&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;jsonRes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upstream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;jsonRes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchJsonCached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// throws HTTP 503&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchJsonCached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// throws HTTP 503 again&lt;/span&gt;
&lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCalls&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 1. The origin never got a second chance.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contract 2, the model's patch: even worse, because it returns &lt;code&gt;{ error: "upstream" }&lt;/code&gt; as if it were a user record. &lt;code&gt;getCalls()&lt;/code&gt; is still 1.&lt;/p&gt;

&lt;p&gt;The actual one-line repair is not a retry library. Delete the entry when the promise rejects, then let the next caller start a new in-flight promise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contract 3, after the delete: first call still throws 503. Second call returns &lt;code&gt;{ id: 7 }&lt;/code&gt;. &lt;code&gt;getCalls()&lt;/code&gt; is 2.&lt;/p&gt;

&lt;p&gt;Contract 4, still required: two parallel first calls coalesce. &lt;code&gt;getCalls()&lt;/code&gt; stays 1 on a 200. You are not allowed to "fix" the 503 by removing in-flight dedupe. Interviewers notice when you swing from one bug to the stampede.&lt;/p&gt;

&lt;p&gt;I ran those four plus the model's sticky-body case as a local Node script, no dependencies, &lt;code&gt;node:assert/strict&lt;/code&gt;. Five contracts, all green on the delete-on-reject version.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should you say out loud while Gemini is typing?
&lt;/h2&gt;

&lt;p&gt;The round is 60 minutes of shared editing. Silence while you wait on a stream is the other way to fail. Narrate the contract before you accept the diff.&lt;/p&gt;

&lt;p&gt;I would say, roughly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I would cache the in-flight promise so two mounts don't double-fetch. I would not cache a rejection. If Gemini's patch drops &lt;code&gt;res.ok&lt;/code&gt; or wraps this in an empty &lt;code&gt;catch&lt;/code&gt;, I want a replay: 503 then 200. If the second call never hits the fetcher, we reject the diff.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That speech is the score. The code is evidence.&lt;/p&gt;

&lt;p&gt;A few other tells I would look for in whatever module they hand you, same 10-minute budget:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Smell in the model's patch&lt;/th&gt;
&lt;th&gt;What you replay&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Empty &lt;code&gt;catch&lt;/code&gt; / &lt;code&gt;return null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The error path now looks like success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache written before &lt;code&gt;res.ok&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;4xx/5xx bodies become canonical JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTL added, rejection kept&lt;/td&gt;
&lt;td&gt;You paid for expiry and still serve the 503 for the whole window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock around the wrong key&lt;/td&gt;
&lt;td&gt;Concurrent URLs share one poisoned slot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests only cover the 200&lt;/td&gt;
&lt;td&gt;Add the 503-then-200 case yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of those need a framework. They need you to keep a failing fixture in the pad and refuse to delete it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this format is not
&lt;/h2&gt;

&lt;p&gt;It is not "the interviewer wants to watch you prompt." Prompt quality is one of three bullets in Google's fluency rubric, sitting next to output validation and debugging. A candidate who pastes the whole file into Gemini and then reads the answer aloud is doing the opposite of "human-led."&lt;/p&gt;

&lt;p&gt;It is also not a license for every company. Meta's CoderPad pilot is opt-in and still rolling. Google's is a 2026 pilot on selected US teams. If the scheduler, the recruiter, or the pad header does not say an assistant is in the environment, assume it is not.&lt;/p&gt;

&lt;p&gt;The prep that transfers across both the Google comprehension round and Meta's AI-enabled pad is the same muscle: take an unfamiliar repo, keep a failing fixture, and treat the model as a junior who ships the first compile.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://www.aceround.app/blog/ai-coding-interview-assistant" rel="noopener noreferrer"&gt;aceround.app — AI interview assistant&lt;/a&gt; when the next round is verbal (system design, "walk me through this diff") and I need to rehearse the explanation under interruption. It does not replace the Node replay above. If you cannot show the 503-then-200 contract in the pad, the spoken version will not save you.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does Google let every candidate use Gemini in the coding round?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. BI describes a second-half 2026 pilot for junior and mid-level roles on selected US teams, with Gemini as the approved assistant. Do not assume your loop is in that cohort until someone says so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the tests pass after I accept Gemini's patch, am I done?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not if the tests never sent a 503. Add that case. A green suite that only covers 200 is how the sticky cache ships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this the same skill as "turn the model's answer into unit tests"?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Related, different fixture. That version starts from a blank solution. This version starts from a repo that already almost works. Google's comprehension round is the second one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I still practice LeetCode?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, for loops that still ban AI. For loops that allow it, spend equal time reading someone else's 200-line module and listing the contracts you would refuse to drop.&lt;/p&gt;




&lt;p&gt;What 503-then-200 fixture would you add to the last model patch you accepted at work?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance, then edited. The Node contracts were run locally before publishing. Google pilot details: &lt;a href="https://www.businessinsider.com/google-job-interview-software-engineers-ai-assistant-coding-2026-5" rel="noopener noreferrer"&gt;Business Insider, May 7, 2026&lt;/a&gt;. 75% figure: &lt;a href="https://blog.google/innovation-and-ai/infrastructure-and-cloud/google-cloud/cloud-next-2026-sundar-pichai/" rel="noopener noreferrer"&gt;Pichai, April 22, 2026&lt;/a&gt;. Meta pilot: &lt;a href="https://coderpad.io/blog/hiring-developers/ai-in-the-interview-is-not-cheating-it-is-the-job-according-to-meta/" rel="noopener noreferrer"&gt;CoderPad, October 30, 2025&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
