<?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: Nikola Roganovic</title>
    <description>The latest articles on DEV Community by Nikola Roganovic (@rogo032).</description>
    <link>https://dev.to/rogo032</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%2F3050435%2F64ed9127-e232-4e87-8cce-bc27253c3f4b.jpg</url>
      <title>DEV Community: Nikola Roganovic</title>
      <link>https://dev.to/rogo032</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rogo032"/>
    <language>en</language>
    <item>
      <title>Design a rate limiter, from fixed window to token bucket</title>
      <dc:creator>Nikola Roganovic</dc:creator>
      <pubDate>Wed, 07 Oct 2026 21:03:55 +0000</pubDate>
      <link>https://dev.to/rogo032/design-a-rate-limiter-from-fixed-window-to-token-bucket-3onf</link>
      <guid>https://dev.to/rogo032/design-a-rate-limiter-from-fixed-window-to-token-bucket-3onf</guid>
      <description>&lt;p&gt;&lt;em&gt;I first published this on my blog, where you can pause and play with every animation: &lt;a href="https://techinpencil.com/rate-limiter/" rel="noopener noreferrer"&gt;https://techinpencil.com/rate-limiter/&lt;/a&gt;&lt;/em&gt;&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%2Fzfwoqyf8zsdwszlvtj3d.jpg" 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%2Fzfwoqyf8zsdwszlvtj3d.jpg" alt="A fixed window limiter set to 100 requests per minute" width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A fixed window limiter set to 100 requests per minute&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The limiter in the picture above is set to 100 requests per minute, and it just let 200 through in under two seconds. It isn't broken. It's the simplest rate limiter there is, and a lot of real code works exactly like it: count requests per clock minute, wipe the count when the minute changes. A client that sends right before the change and right after it gets twice the limit through.&lt;/p&gt;

&lt;p&gt;This is also a classic interview question. You're asked to design a rate limiter, you sketch a counter, and the interviewer asks what happens at 0:59. I'll follow that conversation here. Each section starts with the next question.&lt;/p&gt;
&lt;h2&gt;
  
  
  A counter and a clock
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The interviewer asks:&lt;/strong&gt; “Design a rate limiter for our public API. 100 requests per minute per API key.”&lt;/p&gt;

&lt;p&gt;The first answer most people give is a good place to start. Keep one counter per API key per minute. When a request comes in, look up the counter for the current minute. If it's under the limit, add one and let the request through. If it's at the limit, answer with HTTP 429 Too Many Requests [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;function&lt;/span&gt; &lt;span class="nf"&gt;allow&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;now&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;minute&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;floor&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;counters&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="s2"&gt;`&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;minute&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="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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&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;return&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;counters&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;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;minute&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;count&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="kc"&gt;true&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;That's a fixed window. You need one integer per client, and old counters just expire. The picture below uses a limit of 10 per minute so you can count the slips.&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%2Fz561p6c557bzlzb6wwua.jpg" 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%2Fz561p6c557bzlzb6wwua.jpg" alt="Fixed window, limit 10 per minute" width="799" height="466"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Fixed window, limit 10 per minute&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Watch the yellow band. With steady traffic it never goes above 10. With traffic bunched around the boundary it shows 20: ten just before 1:00 and ten just after. Figma's engineering blog shows the same thing with a limit of 5 per minute [2]. Five requests at 11:00:59, five more at 11:01:00, and all ten go through.&lt;/p&gt;

&lt;p&gt;How bad that is depends on what the limit protects. A 2x spike for a second or two is fine if you just want to stop a script from hammering you all day. It's a real problem if the thing behind the limiter can't take more than 100 a minute.&lt;/p&gt;
&lt;h2&gt;
  
  
  Remember every request
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The interviewer asks:&lt;/strong&gt; “OK, the boundary leaks. Can you make it exact?”&lt;/p&gt;

&lt;p&gt;Stop thinking in clock minutes. Instead of “requests this minute”, count “requests in the last 60 seconds”, measured back from right now. For that you need the time of every request you let through. That list is the sliding log.&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;allow&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;now&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;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logs&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="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;log&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;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&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="nx"&gt;log&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;log&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="nx"&gt;LIMIT&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;log&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;now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;logs&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="nx"&gt;log&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;true&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 next picture gets the same bunched traffic. The bracket is the last 60 seconds, and a timestamp gets crossed off when it falls out of 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdmqkmpep8rya3ro9ovki.jpg" 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%2Fdmqkmpep8rya3ro9ovki.jpg" alt="Sliding log, limit 10 per minute" width="799" height="466"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Sliding log, limit 10 per minute&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It never lets more than 10 through in any 60 seconds. What it costs you is memory: each client now needs a timestamp for every allowed request instead of one integer. At 100 per minute that's up to 100 timestamps per client, 8 bytes each (a 64-bit timestamp in milliseconds), so a million active clients need 800 MB just for timestamps. Figma did this math for a limit of 500 requests a day and got 5 million stored values for 10,000 users [2].&lt;/p&gt;
&lt;h2&gt;
  
  
  Two counters and a guess
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The interviewer asks:&lt;/strong&gt; “Can you get close to that without storing every timestamp?”&lt;/p&gt;

&lt;p&gt;Keep the per-minute counters from the first version, but when a request arrives, look at two of them: this minute's and last minute's. Then pretend last minute's requests were spread evenly across it. If we're 15 seconds into this minute, the last 60 seconds still include the final 45 seconds of the previous one, so you count 75% of 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;*&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="o"&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;estimate&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;return&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;Cloudflare described this in 2017 with a worked example [3]: a limit of 50 per minute, 42 requests in the previous minute and 18 so far in the current one, 15 seconds in: 42 × 0.75 + 18 = 49.5. Figma wrote about something similar the same year [2]. For an hourly limit they keep 60 one-minute counters and add them up, which means more counters to store but less guessing.&lt;/p&gt;

&lt;p&gt;The dashed slips under the line are what the estimate assumes, and the solid ones are what happened.&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%2Ft5txfplb0fs76nlsd9xw.jpg" 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%2Ft5txfplb0fs76nlsd9xw.jpg" alt="Sliding window counter, limit 10 per minute" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Sliding window counter, limit 10 per minute&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If minute 1's traffic came early, the guess still counts requests that are already older than 60 seconds, so it's too strict. If it came late, the guess spreads them out, counts too few, and lets a few extra through. So you store two integers per client instead of a whole log, and the count is sometimes a bit off.&lt;/p&gt;

&lt;p&gt;Cloudflare measured how far off on real traffic: 400 million requests from 270,000 sources. The estimate missed the real rate by 6% on average, but that only changes the answer for clients sitting close to the limit, so just 0.003% of requests were wrongly allowed or wrongly limited [3]. Their counters lived in a Memcached fork with simple commands like GET, SET and INCR, and that's all this approach needs.&lt;/p&gt;
&lt;h2&gt;
  
  
  A bucket of tokens
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The interviewer asks:&lt;/strong&gt; “Some clients send bursts. A page load fires 20 API calls at once. Can you allow that without raising the average rate?”&lt;/p&gt;

&lt;p&gt;Every limiter so far counts the requests from the last minute. A token bucket doesn't count requests at all. Each client gets a bucket that holds up to N tokens, and tokens drip in at a fixed rate. A request takes one token, and if there's none left it gets a 429. A full bucket lets a burst of N through at once, and after that the client gets exactly the refill rate.&lt;/p&gt;

&lt;p&gt;The drip doesn't need a timer or a background job. You store two numbers per client, the token count and the time you last looked, and top the bucket up when the next request shows up:&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;allow&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;now&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;refill&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;RATE&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;tokens&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CAPACITY&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;tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;refill&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;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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="kc"&gt;false&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;tokens&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="kc"&gt;true&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;In the picture, the coin forming under the tap is that top-up.&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%2F7hcbvtkc209ukllhkuxb.jpg" 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%2F7hcbvtkc209ukllhkuxb.jpg" alt="Token bucket" width="799" height="522"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Token bucket&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So there are two settings instead of one: the bucket size decides how big a burst you accept, and the rate decides the average. Stripe wrote in 2017 that they use a token bucket for their API, with the limiters backed by Redis [4]. AWS API Gateway documents its throttling the same way: the rate is tokens added per second, and the burst is the size of the bucket [5].&lt;/p&gt;

&lt;p&gt;It doesn't fix 0:59 on its own, though. A bucket of 100 that refills at 100 per minute can still let about 200 through in 60 seconds: the 100 it saved up, plus 100 more dripping in. If a hard ceiling per minute matters, keep the bucket small compared to the rate.&lt;/p&gt;
&lt;h2&gt;
  
  
  A bucket with a hole
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The interviewer asks:&lt;/strong&gt; “The payment provider behind this API accepts 2 calls per second. Bursts or not. Now what?”&lt;/p&gt;

&lt;p&gt;The leaky bucket is the other famous bucket, and the name gets used for two different things. In the first one, the requests themselves go into the bucket and leak out of a hole at the bottom at a steady rate. The bucket is a queue. A burst doesn't get rejected, it waits its turn, and you only answer 429 when the queue is full.&lt;/p&gt;

&lt;p&gt;The picture below sends the same bursts through both, with the same size and rate: the token bucket on top, the queue below.&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%2Feac3nnw1cnk28st07qgg.jpg" 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%2Feac3nnw1cnk28st07qgg.jpg" alt="Token bucket and leaky bucket, same size and rate" width="799" height="666"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Token bucket and leaky bucket, same size and rate&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The token bucket passes the burst straight to the server. The queue turns it into an even drip, but requests have to wait: here the last one in each burst waits almost 3 seconds. That's what you want in front of something that really can't take a burst, like that payment provider. NGINX's &lt;code&gt;limit_req&lt;/code&gt; works this way when you set &lt;code&gt;burst&lt;/code&gt; and leave out &lt;code&gt;nodelay&lt;/code&gt;: extra requests are delayed and only rejected once they go past the burst size [6]. It answers with 503 by default, so if you want a 429 you have to set &lt;code&gt;limit_req_status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second meaning counts instead of queueing. Shopify's API docs describe it with marbles: every request drops a marble into the bucket, the bucket leaks at a fixed rate, and a full bucket means 429 [7]. Nothing waits in this one. Turn it upside down and you have a token bucket, because marbles in the bucket are just tokens missing from it. For the same size and rate, the two make the same decision on every request.&lt;/p&gt;
&lt;h2&gt;
  
  
  Many servers, one limit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The interviewer asks:&lt;/strong&gt; “You have 20 API servers behind a load balancer. Where does the counter live?”&lt;/p&gt;

&lt;p&gt;A counter in each server's memory doesn't work. A client whose requests get spread over 20 servers can get up to 20 times the limit, so 2,000 a minute instead of 100. The counter has to move to a shared store, often Redis, and that opens a new way to get it wrong. Here it is step by step:&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%2Fckuwq55wmdozw9zftiat.jpg" 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%2Fckuwq55wmdozw9zftiat.jpg" alt="Two servers, one counter" width="800" height="489"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Two servers, one counter&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Read, decide, write is three steps, and another server can slip in between any two of them. With thousands of requests per second on the same key, it will. The fix is to let the store read and write in one operation. For a counter that's &lt;code&gt;INCR&lt;/code&gt;, which adds one and returns the new value, and you decide based on what it returns [8]. For a token bucket, where the update is more than adding one, the usual answer is a small Lua script. Redis runs a script from start to finish without running anything else in between [9].&lt;/p&gt;

&lt;p&gt;Even the Redis docs point out a race in one of their own rate limiter examples: a client that crashes between &lt;code&gt;INCR&lt;/code&gt; and &lt;code&gt;EXPIRE&lt;/code&gt; leaves a counter with no expiry, which the docs call a leak. Their fix is a Lua script too [8].&lt;/p&gt;
&lt;h2&gt;
  
  
  Saying no politely
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The interviewer asks:&lt;/strong&gt; “What do you send back when you say no?”&lt;/p&gt;

&lt;p&gt;Status 429 Too Many Requests, from RFC 6585 [1]. The same section says the response may include a &lt;code&gt;Retry-After&lt;/code&gt; header, which tells the client how long to wait, and RFC 9110 defines it as a whole number of seconds or an HTTP date [10]. A client that respects it stops hammering you. RFC 6585 also says caches must not store a 429.&lt;/p&gt;

&lt;p&gt;GitHub goes further and tells you where you stand on every response, not only when you're blocked [11]. Authenticated users get 5,000 requests an hour, and the headers say how many are left and when the count resets, in UTC epoch seconds:&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="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;x-ratelimit-limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5000&lt;/span&gt;
&lt;span class="na"&gt;x-ratelimit-remaining&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4987&lt;/span&gt;
&lt;span class="na"&gt;x-ratelimit-used&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;13&lt;/span&gt;
&lt;span class="na"&gt;x-ratelimit-reset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1790001200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's an IETF draft to standardize this with two headers, &lt;code&gt;RateLimit&lt;/code&gt; and &lt;code&gt;RateLimit-Policy&lt;/code&gt; [12]. It's still a draft, version 11 as of May 2026. Plenty of blog posts show the older &lt;code&gt;RateLimit-Limit&lt;/code&gt; and &lt;code&gt;RateLimit-Remaining&lt;/code&gt; names, which were replaced in 2023.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Per client&lt;/th&gt;
&lt;th&gt;Bursts&lt;/th&gt;
&lt;th&gt;Exact?&lt;/th&gt;
&lt;th&gt;Who wrote about it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed window&lt;/td&gt;
&lt;td&gt;1 counter&lt;/td&gt;
&lt;td&gt;up to 2x the limit at the boundary&lt;/td&gt;
&lt;td&gt;per clock minute&lt;/td&gt;
&lt;td&gt;Redis docs, INCR pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sliding log&lt;/td&gt;
&lt;td&gt;1 timestamp per request&lt;/td&gt;
&lt;td&gt;never over the limit&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Figma (2017), considered it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sliding window counter&lt;/td&gt;
&lt;td&gt;2 counters&lt;/td&gt;
&lt;td&gt;close to the limit&lt;/td&gt;
&lt;td&gt;estimate&lt;/td&gt;
&lt;td&gt;Cloudflare (2017)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token bucket&lt;/td&gt;
&lt;td&gt;2 numbers&lt;/td&gt;
&lt;td&gt;up to the bucket size, on purpose&lt;/td&gt;
&lt;td&gt;the average, not every 60 s&lt;/td&gt;
&lt;td&gt;Stripe (2017), AWS API Gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leaky bucket, queue&lt;/td&gt;
&lt;td&gt;the waiting requests&lt;/td&gt;
&lt;td&gt;smoothed, requests wait&lt;/td&gt;
&lt;td&gt;yes, on the way out&lt;/td&gt;
&lt;td&gt;NGINX limit_req with burst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leaky bucket, meter&lt;/td&gt;
&lt;td&gt;2 numbers&lt;/td&gt;
&lt;td&gt;same as token bucket&lt;/td&gt;
&lt;td&gt;the average, not every 60 s&lt;/td&gt;
&lt;td&gt;Shopify&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If I had to pick one without knowing more, I'd take the token bucket with a small bucket. It stores two numbers per client, and you decide up front how big a burst is OK.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Nottingham, M. and R. Fielding, &lt;a href="https://www.rfc-editor.org/rfc/rfc6585#section-4" rel="noopener noreferrer"&gt;Additional HTTP Status Codes&lt;/a&gt;, RFC 6585, April 2012. Section 4.&lt;/li&gt;
&lt;li&gt;Mahdi, N., &lt;a href="https://www.figma.com/blog/an-alternative-approach-to-rate-limiting/" rel="noopener noreferrer"&gt;An alternative approach to rate limiting&lt;/a&gt;, Figma blog, April 2017.&lt;/li&gt;
&lt;li&gt;Desgats, J., &lt;a href="https://blog.cloudflare.com/counting-things-a-lot-of-different-things/" rel="noopener noreferrer"&gt;How we built rate limiting capable of scaling to millions of domains&lt;/a&gt;, Cloudflare blog, June 2017.&lt;/li&gt;
&lt;li&gt;Tarjan, P., &lt;a href="https://stripe.com/blog/rate-limiters" rel="noopener noreferrer"&gt;Scaling your API with rate limiters&lt;/a&gt;, Stripe blog, March 2017.&lt;/li&gt;
&lt;li&gt;Amazon Web Services, &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html" rel="noopener noreferrer"&gt;Throttle requests to your REST APIs&lt;/a&gt;, Amazon API Gateway Developer Guide.&lt;/li&gt;
&lt;li&gt;NGINX, &lt;a href="https://nginx.org/en/docs/http/ngx_http_limit_req_module.html" rel="noopener noreferrer"&gt;Module ngx_http_limit_req_module&lt;/a&gt;, nginx.org documentation.&lt;/li&gt;
&lt;li&gt;Shopify, &lt;a href="https://shopify.dev/docs/api/usage/limits" rel="noopener noreferrer"&gt;Shopify API limits&lt;/a&gt;, Shopify developer documentation.&lt;/li&gt;
&lt;li&gt;Redis, &lt;a href="https://redis.io/docs/latest/commands/incr/" rel="noopener noreferrer"&gt;INCR&lt;/a&gt;, command documentation. See the rate limiter patterns.&lt;/li&gt;
&lt;li&gt;Redis, &lt;a href="https://redis.io/docs/latest/develop/programmability/eval-intro/" rel="noopener noreferrer"&gt;Scripting with Lua&lt;/a&gt;, Redis documentation.&lt;/li&gt;
&lt;li&gt;Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., &lt;a href="https://www.rfc-editor.org/rfc/rfc9110#section-10.2.3" rel="noopener noreferrer"&gt;HTTP Semantics&lt;/a&gt;, STD 97, RFC 9110, June 2022. Section 10.2.3.&lt;/li&gt;
&lt;li&gt;GitHub, &lt;a href="https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api" rel="noopener noreferrer"&gt;Rate limits for the REST API&lt;/a&gt;, GitHub Docs.&lt;/li&gt;
&lt;li&gt;IETF HTTPAPI Working Group, &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/" rel="noopener noreferrer"&gt;RateLimit header fields for HTTP&lt;/a&gt;, Internet-Draft draft-ietf-httpapi-ratelimit-headers-11, May 2026.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Every animation, interactive: &lt;a href="https://techinpencil.com/rate-limiter/" rel="noopener noreferrer"&gt;https://techinpencil.com/rate-limiter/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>backend</category>
      <category>interview</category>
    </item>
    <item>
      <title>How to Reduce Your AWS Bill by 50%</title>
      <dc:creator>Nikola Roganovic</dc:creator>
      <pubDate>Tue, 27 Jan 2026 17:33:04 +0000</pubDate>
      <link>https://dev.to/rogo032/how-to-reduce-your-aws-bill-by-50-5714</link>
      <guid>https://dev.to/rogo032/how-to-reduce-your-aws-bill-by-50-5714</guid>
      <description>&lt;p&gt;Cutting your AWS bill by 50% sounds unrealistic. &lt;/p&gt;

&lt;p&gt;It isn’t.   &lt;/p&gt;

&lt;p&gt;In most growing AWS environments, a 30–50% reduction is achievable without rewriting applications, migrating providers, or slowing down production. These savings don’t come from a secret trick. They come from achieving visibility and correcting accumulated problems no one wanted to touch.&lt;/p&gt;

&lt;p&gt;Let’s break down those problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you know what is running ?
&lt;/h2&gt;

&lt;p&gt;Are you looking at your architecture diagram instead of your actual resources? &lt;/p&gt;

&lt;p&gt;Your diagram is not your infrastructure. The real cost lives in what is currently running across regions and accounts, including resources nobody remembers.&lt;/p&gt;

&lt;p&gt;The biggest savings usually appear in: Idle or underutilized EC2 instances, Overprovisioned RDS databases, Orphaned EBS volumes, Old snapshots with no retention policy, Unused load balancers, Forgotten Elastic IPs.&lt;/p&gt;

&lt;p&gt;These are not edge cases. They are standard in mature AWS accounts.&lt;/p&gt;

&lt;p&gt;Before you even start thinking about Savings Plans or Reserved Instances, stop and ask yourself one simple question: if you had to build this from scratch today, would you actually launch all of this again?&lt;/p&gt;

&lt;p&gt;If the answer is no, that’s where the 50% reduction begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overprovisioning
&lt;/h2&gt;

&lt;p&gt;Most teams provision infrastructure for peak traffic that happens a few times per month or even a year, and then pay maximum pricing 24/7.&lt;/p&gt;

&lt;p&gt;EC2 instances are larger than necessary. RDS instances are provisioned for theoretical growth. EKS clusters run with excess capacity “just in case.”&lt;/p&gt;

&lt;p&gt;Right-sizing alone usually reduces costs by 20–30%.&lt;/p&gt;

&lt;p&gt;And you think this is risky? No, it’s not. It’s simply measuring real utilization and provisioning your infrastructure with actual demand.&lt;/p&gt;

&lt;p&gt;Increasing capacity is easy. Reducing it takes effort.&lt;/p&gt;

&lt;p&gt;If you have not reviewed CPU, memory, and storage utilization in the last quarter, you are almost certainly overpaying for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Transfer Is Expensive
&lt;/h2&gt;

&lt;p&gt;Many teams focus only on compute while ignoring network costs.&lt;/p&gt;

&lt;p&gt;Cross-AZ. Cross-region. NAT Gateway. Public data transfer.&lt;/p&gt;

&lt;p&gt;As your system grows, these costs grow with it, and no one really questions them because they don’t belong to anyone.&lt;/p&gt;

&lt;p&gt;Sometimes a small change in how your services are connected, reducing cross-AZ traffic, cutting down on NAT usage, or cleaning up outbound traffic is enough to  lower your monthly bill without touching the application itself.&lt;/p&gt;

&lt;p&gt;If someone asks you about your data transfer costs and you can’t explain them, that’s a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage
&lt;/h2&gt;

&lt;p&gt;Storage feels inexpensive at first.&lt;/p&gt;

&lt;p&gt;Over time, it becomes a silent cost accumulator: Snapshots without lifecycle rules, S3 buckets without retention policies, Logs stored indefinitely, EBS volumes attached to terminated instances.&lt;/p&gt;

&lt;p&gt;Individually, these charges look harmless. Together, they often end up on my &lt;a href="https://costlyfy.com" rel="noopener noreferrer"&gt;CostlyFY&lt;/a&gt; audit list.&lt;/p&gt;

&lt;p&gt;Lifecycle policies and retention standards are not optional tasks. They are cost controls.&lt;/p&gt;

&lt;p&gt;If your organization doesn't enforce storage rulles, your bill will grow even if compute stays flat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discounts Without Discipline
&lt;/h2&gt;

&lt;p&gt;Savings Plans and Reserved Instances are powerful tools when applied correctly.&lt;/p&gt;

&lt;p&gt;They are not a solution to a large AWS bill.&lt;/p&gt;

&lt;p&gt;Locking in long term commitments on top of inefficient usage optimization simply guarantees long term inefficiency at a slightly discounted rate.&lt;/p&gt;

&lt;p&gt;The correct sequence is: Eliminate waste, Right-size infrastructure, Stabilize usage patterns, Then apply commitments strategically.&lt;/p&gt;

&lt;p&gt;If you do it in the wrong order, you’re locking in your problems for the long term.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lack of Ownership
&lt;/h2&gt;

&lt;p&gt;Big AWS bills are rarely caused by complex architecture. They’re usually caused by missing ownership.&lt;/p&gt;

&lt;p&gt;If no one is responsible for: Reviewing monthly bill, Investigating anomalies, Enforcing tagging, Shutting down unused resources.&lt;/p&gt;

&lt;p&gt;Then costs increase by default.&lt;/p&gt;

&lt;p&gt;Cloud pricing is usage based. Usage expands when nobody feels accountable for it.&lt;/p&gt;

&lt;p&gt;Reducing your AWS bill by 50% is not a technical miracle. It is an operational decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 50% Actually Means
&lt;/h2&gt;

&lt;p&gt;In most environments, the savings do not come from a single dramatic change.&lt;/p&gt;

&lt;p&gt;They come from: Removing unused infrastructure, Correcting oversizing, Cleaning up storage, Optimizing network, Applying commitments intelligently.&lt;/p&gt;

&lt;p&gt;Individually, each adjustment looks incremental. Together, they compound.&lt;/p&gt;

&lt;p&gt;That is how meaningful reductions happen without disrupting production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Reality
&lt;/h2&gt;

&lt;p&gt;All of this requires time, data analysis, and a willingness to challenge existing beliefs.&lt;/p&gt;

&lt;p&gt;Most teams know they’re spending more than they should. They just don’t have the time to dig into it properly.&lt;/p&gt;

&lt;p&gt;If you want a clear view of where your AWS bill can actually be reduced without making blind changes or taking unnecessary risks, a structured cost audit helps you see it quickly.&lt;/p&gt;

&lt;p&gt;That’s exactly what I do at &lt;a href="https://costlyfy.com/audit" rel="noopener noreferrer"&gt;CostlyFY&lt;/a&gt;: detailed AWS cost reviews focused on identifying real savings opportunities while preserving system stability.&lt;/p&gt;

&lt;p&gt;Because reducing your AWS bill by 50% is rarely about doing something extreme.&lt;/p&gt;

&lt;p&gt;It’s about finally looking at the numbers without avoiding what they show.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>aws</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>ChatGPT Ads Are Coming: What It Means for Users</title>
      <dc:creator>Nikola Roganovic</dc:creator>
      <pubDate>Tue, 20 Jan 2026 10:00:27 +0000</pubDate>
      <link>https://dev.to/rogo032/chatgpt-ads-are-coming-what-it-means-for-users-bfm</link>
      <guid>https://dev.to/rogo032/chatgpt-ads-are-coming-what-it-means-for-users-bfm</guid>
      <description>&lt;h2&gt;
  
  
  The illusion
&lt;/h2&gt;

&lt;p&gt;Since the popularization of AI up to today, every AI company has been trying to build a certain set of features and tools aimed at keeping people glued to that magical chat window for as long as possible. Not necessarily because of subscriptions and direct monthly revenue, but because of creating the illusion of a perfect personal assistant that is always there to help you.&lt;/p&gt;

&lt;p&gt;This assistant evolves (through new models), adapts, and over time becomes better and better at executing its tasks, and lately it has even become cheaper than ever. With the introduction of the new GO plan, for just $8 you get an unquestioning assistant that will always help you (until you hit the limit, of course, and then you pay a bit more for an even better one) in solving complex tasks, automating boring work, writing code, fixing bugs, and all the things you don’t want to actively deal with, creating a bigger and bigger habit and dependence on using it for less and less money, or even through “generous” free tiers.&lt;/p&gt;

&lt;p&gt;What makes this illusion so effective is how few obstacles exist between the user and the system. You open a chat window, you ask for help, and the help arrives. Over time, that simplicity reshapes expectations. Assistance starts to feel less like a product and more like something that simply exists in the space around you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good Intentions Don’t Pay for Infrastructure
&lt;/h2&gt;

&lt;p&gt;Running large-scale AI systems is not just a question of models and innovation, but of infrastrucutre, cost, and viability. Subscriptions help, but they were never designed to carry the full weight of a product that millions of people use daily, often intensively, and often without paying anything at all. A monthly fee captures commitment from a small group of   users, but it leaves the majority of usage unresolved. When a system becomes this central in everyday workflows, relying solely on subscriptions stops being a smart strategy and it starts being a losing battle. At that point, the question is no longer whether another revenue model will appear, but which one can realistically support the scale that has already been created. And the answer, is ads. &lt;/p&gt;

&lt;h2&gt;
  
  
  Shift in how people already use ChatGPT
&lt;/h2&gt;

&lt;p&gt;It is no secret that AI systems like ChatGPT are being used for an ever expanding range of tasks, and that no two people use them in exactly the same way. What has changed more noticeably, however, is not the technology itself, but the expectations we bring to it.&lt;/p&gt;

&lt;p&gt;With all the new capabilities and advantages, we have become lazier than ever before. We don’t want to wait 30 seconds for ChatGPT to display “thinking..”, we don’t want incorrect answers, we want a finished solution, business ideas, and we want it immediately, and delivered with the phrase “make no mistakes.”&lt;/p&gt;

&lt;p&gt;This shift is especially visible outside of purely technical work. We no longer want to think about where to go for dinner. We want the top five restaurants. We do not want to browse, compare, or explore. We want the best clothing, the best hotels, the best options, filtered and ranked without additional context.&lt;/p&gt;

&lt;p&gt;We are not opposed to influence. We are opposed to recognizing it. We want advertising, as long as it does not announce itself as such. We want it as a generic AI answer, helpful, and indistinguishable from everything else the AI already provides.&lt;/p&gt;

&lt;p&gt;That shift did not begin with ads.&lt;br&gt;
It began with how we chose to use the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ads Won’t Look Like Ads
&lt;/h2&gt;

&lt;p&gt;According to OpenAI’s own description, ads in ChatGPT aren't designed to merge into the model’s responses. Ads will not alter, shape, or influence what the AI say and the generated answer remains independent.&lt;/p&gt;

&lt;p&gt;Instead, ads are meant to appear as separated elements, displayed outside of the model’s response itself. They will be visually distinct, explicitly labeled, and positioned in a way that makes it clear what comes from the AI and what is sponsored. This means ads will appear below an answer, not inside it, and only when there is a relevant commercial context tied to the conversation.&lt;/p&gt;

&lt;p&gt;From a technical standpoint, this separation is critical. It preserves the integrity of the model’s output while allowing monetization to exist alongside it. OpenAI has been explicit that advertisers do not influence answers, do not gain access to conversations, and do not receive user prompts or responses.&lt;br&gt;
And we know that will never happen. For sure.&lt;/p&gt;

&lt;h2&gt;
  
  
  This isn’t about trust.
&lt;/h2&gt;

&lt;p&gt;It is tempting to frame the current state of AI around trust. We ask whether OpenAI will keep its promises, whether they respect user privacy, or whether the line between a helpful answer and a paid advertisement will remain intact.&lt;/p&gt;

&lt;p&gt;When a company’s way of survival depends on satisfying massive capital investments, optimization follows the money, not the truth. If the revenue model shifts toward advertising, the AI won't be your assistant. It will become a salesman designed to manipulate your decision.&lt;/p&gt;

&lt;p&gt;This isn't an accidental "drift." It is the inevitable result of a business model that views the user not as a customer to be served, but as a resource to be harvested. Every "reasonable" decision to prioritize sponsored content would be a dangerous path for a product.&lt;/p&gt;

&lt;p&gt;The mistake most users make is trusting the system as it exists today. They enjoy the current feature set while ignoring everyting else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changes
&lt;/h2&gt;

&lt;p&gt;When ads arrive, the interface may look almost the same, with only a subtle addition such as an ad banner at the bottom. What changes first is not the output, but the relationship. The system is no longer optimized solely around usefulness. It now operates within an additional constraint: revenue. Even when ads are technically separated from answers, their presence alters what the system exists to maximize. Attention becomes measurable. Time spent inside the interaction starts to matter in ways it did not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The introduction of ads into systems like ChatGPT is not a betrayal, nor is it an unexpected twist. It is a familiar stage in the lifecycle of products that need to sustain themselves financially as they grow.&lt;/p&gt;

&lt;p&gt;For users, the challenge is not to reject these systems, but to understand them more clearly. Advertising does not automatically erase value, nor does it immediately compromise usefulness. What it does is change the set of incentives the system operates under, and that change deserves attention.&lt;/p&gt;

&lt;p&gt;Follow &lt;a href="https://costlyfy.com" rel="noopener noreferrer"&gt;CostlyFY&lt;/a&gt; and stay up to date with the latest news from the tech world!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>news</category>
    </item>
    <item>
      <title>You’re Running EC2 Instances That Do Nothing</title>
      <dc:creator>Nikola Roganovic</dc:creator>
      <pubDate>Sun, 11 Jan 2026 14:24:58 +0000</pubDate>
      <link>https://dev.to/rogo032/youre-running-ec2-instances-that-do-nothing-112k</link>
      <guid>https://dev.to/rogo032/youre-running-ec2-instances-that-do-nothing-112k</guid>
      <description>&lt;p&gt;Somewhere in your account, EC2 instances are running with no traffic, no purpose, and no owner.&lt;br&gt;
They exist only because nobody had the audacity to delete them.&lt;/p&gt;

&lt;h2&gt;
  
  
  They Were Never Meant to Survive
&lt;/h2&gt;

&lt;p&gt;These instances were born from lies.&lt;/p&gt;

&lt;p&gt;They were launched for a “temporary test.”&lt;br&gt;
They survived three reorgs.&lt;br&gt;
They outlived the engineer who created them.&lt;/p&gt;

&lt;p&gt;Now they sit there.&lt;br&gt;
Running.&lt;br&gt;
Billing.&lt;br&gt;
Judging you silently.&lt;/p&gt;

&lt;p&gt;Who approved them?&lt;br&gt;
Who needs them?&lt;br&gt;
Who even knows they exist?&lt;/p&gt;

&lt;p&gt;AWS charges you for your ignorance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idle Does Not Mean Free
&lt;/h2&gt;

&lt;p&gt;You tell yourself idle instances aren’t that expensive.&lt;br&gt;
That is a lie you repeat to sleep better.&lt;/p&gt;

&lt;p&gt;CPU at 1%.&lt;br&gt;
Disk doing nothing.&lt;br&gt;
Network flatlined.&lt;/p&gt;

&lt;p&gt;You're still being charged.&lt;/p&gt;

&lt;p&gt;Compute.&lt;br&gt;
Storage.&lt;br&gt;
Attached volumes.&lt;br&gt;
Elastic IPs you forgot about.&lt;/p&gt;

&lt;p&gt;Idle infrastructure resources are not free.&lt;br&gt;
They are active waste.&lt;/p&gt;

&lt;p&gt;You don’t get a discount for ignorance.&lt;br&gt;
AWS bills uptime, not intention.&lt;/p&gt;

&lt;p&gt;How many instances would vanish if “no traffic” was a deletion rule?  &lt;/p&gt;

&lt;p&gt;If the answer scares you, good.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Fear Is Cheaper Than Discipline
&lt;/h2&gt;

&lt;p&gt;Nobody logs into them.&lt;br&gt;
Nobody updates them.&lt;br&gt;
Nobody monitors them.&lt;/p&gt;

&lt;p&gt;But nobody deletes them either.  &lt;/p&gt;

&lt;p&gt;Because deleting the wrong instance is dangerous.&lt;br&gt;
Because breaking something is problematic.&lt;br&gt;
Because wasting money is quiet.&lt;/p&gt;

&lt;p&gt;So you choose fear over discipline.&lt;br&gt;
You choose silence over ownership.&lt;/p&gt;

&lt;p&gt;If you can’t explain what breaks when it’s terminated, you already lost control.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is Not Technical Debt. It’s Financial Carelessness.
&lt;/h2&gt;

&lt;p&gt;You wouldn’t keep routing traffic to unhealthy instances.&lt;br&gt;
You wouldn’t run production with broken readiness checks.&lt;br&gt;
You wouldn’t ignore failing health checks.&lt;/p&gt;

&lt;p&gt;But you ignore idle EC2 because the damage arrives monthly, not instantly.  &lt;/p&gt;

&lt;p&gt;That doesn’t make it harmless.&lt;br&gt;
It makes it dangerous.&lt;/p&gt;

&lt;p&gt;You are not “scaling responsibly.”&lt;br&gt;
You are leaking money because nobody owns cleanup.&lt;/p&gt;

&lt;p&gt;So here’s the uncomfortable truth:&lt;/p&gt;

&lt;p&gt;If you’re afraid to delete EC2 instances, you don’t understand your own system.&lt;br&gt;
If you don’t review EC2 costs monthly, you are guessing.&lt;br&gt;
If you accept idle infrastructure, you are choosing waste.&lt;/p&gt;

&lt;p&gt;Now decide.&lt;/p&gt;

&lt;p&gt;Keep paying for servers nobody needs.&lt;br&gt;
Or admit the mess and clean it up.  &lt;/p&gt;

&lt;p&gt;For more stories like this, check out &lt;a href="https://costlyfy.com/blog" rel="noopener noreferrer"&gt;CostlyFY&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sre</category>
    </item>
    <item>
      <title>10 Proven Ways to Cut Your AWS Bill</title>
      <dc:creator>Nikola Roganovic</dc:creator>
      <pubDate>Sat, 10 Jan 2026 17:50:17 +0000</pubDate>
      <link>https://dev.to/rogo032/10-proven-ways-to-cut-your-aws-bill-5157</link>
      <guid>https://dev.to/rogo032/10-proven-ways-to-cut-your-aws-bill-5157</guid>
      <description>&lt;h2&gt;
  
  
  1. Right-size your EC2 instances
&lt;/h2&gt;

&lt;p&gt;One of the most common reasons for high AWS bills is overprovisioned EC2 instances. Many systems don’t use a fraction of their capacity and are usually just sitting there doing nothing. By regularly monitoring CPU, memory, and other metrics, you can right-size instances to match real usage. This single change often results in savings of 20 to 40 percent without affecting performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Use Auto Scaling instead of static resources
&lt;/h2&gt;

&lt;p&gt;Static servers cost money even when no one is using them. Auto Scaling allows your infrastructure to grow and shrink based on actual demand. This is especially useful for applications with daily traffic spikes or seasonal usage patterns. You only pay for what you need at any given moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use Reserved Instances or Savings Plans for stable workloads
&lt;/h2&gt;

&lt;p&gt;If a service runs continuously and has predictable usage, on-demand pricing is usually the most expensive option. Reserved Instances and Savings Plans offer significant discounts in exchange for long-term commitment. They work best for databases, core backend services, and internal systems. A small amount of planning can lead to substantial monthly savings. But be careful, if you buy them ahead of time and don’t use them, you’re still going to pay for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Use Spot Instances for batch and non-critical workloads
&lt;/h2&gt;

&lt;p&gt;Spot Instances take advantage of unused AWS capacity and are therefore much cheaper than standard instances. They are ideal for batch jobs, CI pipelines, and data processing tasks. While interruptions are possible, most of these workloads can handle restarts. When designed correctly, the cost savings can be dramatic.&lt;/p&gt;

&lt;p&gt;Don't use them for stable production workloads&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Shut down idle resources
&lt;/h2&gt;

&lt;p&gt;This one seems obvious, but many, many people forget about idle instances, or sometimes they don’t even know about them until it’s too late.&lt;/p&gt;

&lt;p&gt;Idle resources are silent budget killers. EC2 instances, RDS databases, and load balancers often remain running without serving any real purpose. Automating shutdowns outside of working hours is simple and highly effective. This is often the fastest way to see immediate cost reductions.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Move cold data to S3 Intelligent-Tiering or Glacier
&lt;/h2&gt;

&lt;p&gt;Not all data needs to be instantly accessible. Data that is rarely accessed should not live in expensive storage tiers. S3 Intelligent-Tiering automatically optimizes storage costs without manual intervention. Glacier is a great option for archives and long-term backups.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Minimize data transfer costs
&lt;/h2&gt;

&lt;p&gt;Data transfer is one of the most underestimated AWS expenses. Cross AZ traffic and outbound data can add up quickly. Keeping services within the same availability zone where possible can significantly reduce costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Use serverless where it makes sense
&lt;/h2&gt;

&lt;p&gt;Serverless pricing is based on execution time rather than uptime. For event driven systems and low or unpredictable traffic workloads, this model is often far more cost effective. It also reduces operational overhead. Fewer servers mean less maintenance and fewer hidden costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Set up AWS Budgets and Cost Anomaly Detection
&lt;/h2&gt;

&lt;p&gt;You cannot control what you cannot see. AWS Budgets let you define spending limits and receive alerts before costs become a problem. Cost Anomaly Detection automatically identifies unusual spikes in usage. These tools are essential for teams running production workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Delete unused snapshots, AMIs, and EBS volumes
&lt;/h2&gt;

&lt;p&gt;Storage resources tend to accumulate over time. Old snapshots, AMIs, and unused EBS volumes often provide no real value but continue to generate costs. Regular cleanup and automation can lead to consistent long-term savings. This is a small habit with a big financial impact.&lt;/p&gt;

&lt;p&gt;Once, while doing an audit report for a company, I found around ten 2 TB snapshots lying around, scattered across random regions that no one knew about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Optimizing AWS costs is not a one time task but an ongoing process. Most savings come from discipline, visibility, and smart architectural decisions. When these cost hacks are applied consistently, cloud spending becomes predictable and significantly lower, without sacrificing performance or reliability.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, feel free to check out &lt;a href="//costlyfy.com/blog"&gt;costlyfy&lt;/a&gt; for more practical posts about AWS, cloud infrastructure, and cost optimization.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cloud</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
