<?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: Saya Mori</title>
    <description>The latest articles on DEV Community by Saya Mori (@sayajmori).</description>
    <link>https://dev.to/sayajmori</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%2F4002585%2F0f7eacaf-ed2a-483b-936a-2acd6712859c.jpg</url>
      <title>DEV Community: Saya Mori</title>
      <link>https://dev.to/sayajmori</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sayajmori"/>
    <language>en</language>
    <item>
      <title>Deterministic Daily Draws: Same Card All Day, No Database</title>
      <dc:creator>Saya Mori</dc:creator>
      <pubDate>Fri, 24 Jul 2026 19:04:26 +0000</pubDate>
      <link>https://dev.to/sayajmori/deterministic-daily-draws-same-card-all-day-no-database-1ndc</link>
      <guid>https://dev.to/sayajmori/deterministic-daily-draws-same-card-all-day-no-database-1ndc</guid>
      <description>&lt;p&gt;A "card of the day" feature has one requirement that sounds trivial and is not: the same user must get the same card all day, and a different one tomorrow. The obvious implementations are all worse than they look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why storing it is the wrong default
&lt;/h2&gt;

&lt;p&gt;Store &lt;code&gt;(user_id, date) -&amp;gt; card&lt;/code&gt; and you have solved it. You have also acquired a write on every first-visit-of-day, a table that grows unboundedly, a timezone column, and a cleanup job.&lt;/p&gt;

&lt;p&gt;For a feature whose entire output is one card, that is a lot of infrastructure. The alternative is to make the draw a &lt;strong&gt;pure function&lt;/strong&gt; of inputs you already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeded hashing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;dailyCard&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;Card&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;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hash32&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;userId&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;date&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;deck&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;deck&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same user, same date, same deck -&amp;gt; same card, every time, on any server, with zero storage. Different date -&amp;gt; different hash -&amp;gt; different card.&lt;/p&gt;

&lt;p&gt;Two things that matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a real hash.&lt;/strong&gt; &lt;code&gt;Math.random()&lt;/code&gt; seeded by string concatenation is not reproducible across engines. FNV-1a or xxhash are small, fast, and stable — stability across runtimes is the whole point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modulo bias is negligible here but real.&lt;/strong&gt; With a 32-bit hash and a 78-card deck, the bias is far below perceptibility. Worth knowing it exists before someone asks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timezone is where this actually gets hard
&lt;/h2&gt;

&lt;p&gt;"Today" is not a global fact. A user in Tokyo and one in Los Angeles disagree about the current date for several hours daily.&lt;/p&gt;

&lt;p&gt;Options, none free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UTC for everyone&lt;/strong&gt; — simple, but the card flips mid-afternoon for some users, which feels broken&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-reported local date&lt;/strong&gt; — correct-feeling, but the client can lie and re-roll by changing its clock&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side timezone per user&lt;/strong&gt; — correct, but now you are storing user state, which was the thing you avoided&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I settled on: client-reported date, server-validated to be within ±1 day of UTC. Someone determined can still re-roll — and honestly, if a user wants to change their system clock to get a different tarot card, letting them is fine. Match the strictness to the stakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deck versioning
&lt;/h2&gt;

&lt;p&gt;If you add cards, &lt;code&gt;seed % deck.length&lt;/code&gt; shifts and everyone's card changes retroactively. Anyone who screenshotted today's card now sees a mismatch.&lt;/p&gt;

&lt;p&gt;Fix: version the deck and include the version in the seed. Old versions stay stable, new draws use the new deck.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hash32&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;userId&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;date&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;deckVersion&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cheap to add up front, painful to retrofit.&lt;/p&gt;

&lt;p&gt;I use this pattern in &lt;a href="https://oraclely.com" rel="noopener noreferrer"&gt;Oraclely&lt;/a&gt; — one card each morning across tarot, four-pillar, and Western astrology, no per-user draw storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it does not apply
&lt;/h2&gt;

&lt;p&gt;If you need draw &lt;em&gt;history&lt;/em&gt; — "show me my last 30 cards" — you need storage anyway, and the pure-function approach only saves you the write path. It shines when the draw is genuinely ephemeral.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
