<?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: Eduardo Zepeda</title>
    <description>The latest articles on DEV Community by Eduardo Zepeda (@zeedu_dev).</description>
    <link>https://dev.to/zeedu_dev</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg</url>
      <title>DEV Community: Eduardo Zepeda</title>
      <link>https://dev.to/zeedu_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zeedu_dev"/>
    <language>en</language>
    <item>
      <title>MCP servers are everywhere now!</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Wed, 04 Mar 2026 21:42:04 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/mcp-servers-are-everywhere-now-40aj</link>
      <guid>https://dev.to/zeedu_dev/mcp-servers-are-everywhere-now-40aj</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/zeedu_dev/how-to-create-a-mcp-server-and-mcp-tools-from-scratch-fea" class="crayons-story__hidden-navigation-link"&gt;How to create a MCP server and MCP tools from scratch?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/zeedu_dev" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" alt="zeedu_dev profile" class="crayons-avatar__image" width="800" height="1066"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/zeedu_dev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eduardo Zepeda
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eduardo Zepeda
                
              
              &lt;div id="story-author-preview-content-2718510" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/zeedu_dev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" class="crayons-avatar__image" alt="" width="800" height="1066"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eduardo Zepeda&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/zeedu_dev/how-to-create-a-mcp-server-and-mcp-tools-from-scratch-fea" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 11 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/zeedu_dev/how-to-create-a-mcp-server-and-mcp-tools-from-scratch-fea" id="article-link-2718510"&gt;
          How to create a MCP server and MCP tools from scratch?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/backend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;backend&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/zeedu_dev/how-to-create-a-mcp-server-and-mcp-tools-from-scratch-fea" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/zeedu_dev/how-to-create-a-mcp-server-and-mcp-tools-from-scratch-fea#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>javascript</category>
      <category>backend</category>
    </item>
    <item>
      <title>My Django Rapid Architecture short overview</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Fri, 27 Feb 2026 04:15:52 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/my-django-rapid-architecture-short-overview-45em</link>
      <guid>https://dev.to/zeedu_dev/my-django-rapid-architecture-short-overview-45em</guid>
      <description>&lt;p&gt;The other day, I was browsing Reddit, and found an Architecture proposal for Django projects called “&lt;a href="https://www.reddit.com/r/django/comments/1pko7q6/django_rapid_architecture_a_guide_to_structuring/" rel="noopener noreferrer"&gt;Django Rapid Architecture&lt;/a&gt;”. It’s a small document with a few guidelines or principles. I’m fond of Django, and I think &lt;a href="https://coffeebytes.dev/en/django/why-should-you-use-django-framework/" rel="noopener noreferrer"&gt;Django is of the best tools out there that you should use&lt;/a&gt;, so I read it and summarized it for you.&lt;/p&gt;

&lt;p&gt;Django Rapid Architecture is a collection of curated patterns and idioms. It aims to create maintainable Django codebases. The author claims it derives from 15+ years of experience and 100+ production projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s wrong with Django’s default architecture?
&lt;/h2&gt;

&lt;p&gt;Well, according to the author, Django’s “apps” are designed for reusable components, not project-specific business logic. Forcing all code into apps creates inflexibility: &lt;a href="https://coffeebytes.dev/en/go/go-migration-tutorial-with-migrate/" rel="noopener noreferrer"&gt;Migrations&lt;/a&gt; make early boundary decisions irrevocable, which inhibits the flexible refactoring necessary for dynamic projects.&lt;/p&gt;

&lt;p&gt;Additionally, apps prefer “vertical encapsulation,” which groups views and models according to features. For real-world systems with interconnected business domains and interfaces, this is not ideal.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to structure projects according to Django Rapid Architecture?
&lt;/h3&gt;

&lt;p&gt;Instead of using Django’s default paradigm, structure by layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep data (models/migrations)&lt;/li&gt;
&lt;li&gt;interfaces (HTTP views/management commands)&lt;/li&gt;
&lt;li&gt;Business logic (readers/actions) is separate.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fip5bwwd0vxydg3pcrqzc.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.amazonaws.com%2Fuploads%2Farticles%2Fip5bwwd0vxydg3pcrqzc.png" alt="Django rapid architecture overview" width="747" height="747"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This “horizontal encapsulation” aligns with Django’s natural layering, avoids early architectural lock-in, and better models complex domains.&lt;/p&gt;

&lt;h4&gt;
  
  
  Remember that Django is a monolith.
&lt;/h4&gt;

&lt;h3&gt;
  
  
  How does Django Rapid Architecture file structure looks?
&lt;/h3&gt;

&lt;p&gt;And how does that look in practice? Well, something like this. The author talks about how we should embrace monoliths at first, since they’re less complex and more maintainable than microservices, and I agree totally.&lt;/p&gt;

&lt;p&gt;Introducing unnecessary complexity just for the sake of it won’t allow you to iterate fast enough, which is crucial in this rapidly changing environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Django Rapid Architecture file structure looks?
&lt;/h3&gt;

&lt;p&gt;And how does that look in practice? Well, something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
├── actions
│ ├── some_domain.py
├── data
│ ├── migrations
│ │ ├── 0001_initial.py
│ └── models
│ └── some_model.py
├── interfaces
│ ├── management_commands
│ │ └── management
│ │ └── commands
│ │ └── some_management_command.py
│ └── http
│ ├── api
│ │ ├── urls.py
│ │ └── views.py
│ └── urls.py
├── readers
│ └── some_domain.py
├── settings.py
└── wsgi.py

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The thing here is just to remember that code is divided into actions, data, interfaces, and readers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layers in Django Rapid Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How to handle Data?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What to do with Models?
&lt;/h4&gt;

&lt;p&gt;Put all Models, yes, all models, inside a single app called data.&lt;/p&gt;

&lt;p&gt;Avoid ultra-large complex models with many methods; complex logic should be independent of models.&lt;/p&gt;

&lt;p&gt;Avoid inheritance other than Django’s Model, so every developer can just look at the model and understand what it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about Business logic?
&lt;/h3&gt;

&lt;p&gt;Business logic code should live in plain functions with well-understood interfaces that operate on model instances, querysets, or plain values. Avoid complex inheritance, mixins, decorators, &lt;a href="https://coffeebytes.dev/en/django/how-to-create-a-custom-manager-django/" rel="noopener noreferrer"&gt;complex custom managers&lt;/a&gt; unless it’s absolutely necessary.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to deal with Readers?
&lt;/h4&gt;

&lt;p&gt;Serving a Django response involves three key parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Query: Built in the view using a queryset. It defines which DB rows/columns to fetch, applying filters, joins, and optimizations. Some logic may be in custom querysets.&lt;/li&gt;
&lt;li&gt;The Values: The data to be sent. Basic values come from model fields. Complex business logic often lives here, in model methods (e.g., get_absolute_url), transforming raw data into usable values for the response.&lt;/li&gt;
&lt;li&gt;The Projection: The final shaping of data for the client. For a JSON API, this is serialization into a dict/list. For HTML, it’s template rendering. Both use the values from step 2. This is where you decide the exact output format.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Types of functions in readers
&lt;/h4&gt;

&lt;p&gt;I’m oversimplifying this; there are tons of examples in the original source, which I encourage you to read thoroughly. But the main types of functions to extract and transform data from a model are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queryset functions encapsulate queryset constructions. Use composition and Higher Order Functions.&lt;/li&gt;
&lt;li&gt;Producer functions produce values from model instances, functions that receive an instance and return something.&lt;/li&gt;
&lt;li&gt;Projector functions, built on top of producers, return a dictionary that maps one or more names onto one or more values.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Actions
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://coffeebytes.dev/en/software-architecture/basic-characteristics-of-an-api-rest-api/" rel="noopener noreferrer"&gt;REST APIs&lt;/a&gt; are complex and non-uniform. Therefore, we should forget about all secondary HTTP verbs and stick with POST and GET.&lt;/p&gt;

&lt;p&gt;Furthermore, we should make sure that a single URL maps to a single view, which responds only to GET or POST, not both. This is something similar to th &lt;a href="https://coffeebytes.dev/en/software-architecture/fast-and-performant-apis-using-go-lang-grpc-and-protobuffers/" rel="noopener noreferrer"&gt;RPC and gRPC paradigm&lt;/a&gt;, which I already wrote about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interfaces
&lt;/h3&gt;

&lt;h4&gt;
  
  
  SSR using Django templates is top-notch
&lt;/h4&gt;

&lt;p&gt;Generating HTML on the server instead of using an API with react can increase productivity. &lt;a href="https://coffeebytes.dev/en/django/django-and-htmx-modern-web-apps-without-writing-js/" rel="noopener noreferrer"&gt;HTMX combined with Django&lt;/a&gt; is recommended, the document also considers this approach superior to using React.&lt;/p&gt;

&lt;h4&gt;
  
  
  Nest interfaces to avoid complexity
&lt;/h4&gt;

&lt;p&gt;You can organize your code to mimick your hierarchy of url segments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/interfaces/http/urls.py
project/interfaces/http/api/urls.py
project/interfaces/http/api/admin/urls.py
project/interfaces/http/api/admin/widgets/urls.py
project/interfaces/http/api/admin/widgets/views.py

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Management commands
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://coffeebytes.dev/en/django/how-to-create-a-command-in-django/" rel="noopener noreferrer"&gt;Django Management commands&lt;/a&gt; are also an interface and should be treated similar to views.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where can I learn more about Django Rapid Architecture?
&lt;/h2&gt;

&lt;p&gt;Where can I learn more about Django Rapid Architecture? Remember that this text is only an overview of the main ideas. If you want to dive into this architecture proposal called &lt;a href="https://www.django-rapid-architecture.org/" rel="noopener noreferrer"&gt;Django Rapid Architecture&lt;/a&gt;, read the original source. I promise it’s short, only a few pages long, with a few more examples and the justification of some decisions.&lt;/p&gt;

</description>
      <category>django</category>
      <category>architecture</category>
      <category>python</category>
    </item>
    <item>
      <title>I've built a Swiss Tables interactive simulator so you can understand how they work internally and how they offer superior performance compared to Buckets</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Tue, 23 Sep 2025 21:38:18 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/ive-built-a-swiss-tables-interactive-simulator-so-you-can-understand-how-they-work-internally-and-3mk9</link>
      <guid>https://dev.to/zeedu_dev/ive-built-a-swiss-tables-interactive-simulator-so-you-can-understand-how-they-work-internally-and-3mk9</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/zeedu_dev/swiss-tables-the-superior-performance-hashmap-1gp6" class="crayons-story__hidden-navigation-link"&gt;Swiss Tables the superior performance hashmap&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/zeedu_dev" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" alt="zeedu_dev profile" class="crayons-avatar__image" width="800" height="1066"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/zeedu_dev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eduardo Zepeda
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eduardo Zepeda
                
              
              &lt;div id="story-author-preview-content-2858850" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/zeedu_dev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" class="crayons-avatar__image" alt="" width="800" height="1066"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eduardo Zepeda&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/zeedu_dev/swiss-tables-the-superior-performance-hashmap-1gp6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 22 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/zeedu_dev/swiss-tables-the-superior-performance-hashmap-1gp6" id="article-link-2858850"&gt;
          Swiss Tables the superior performance hashmap
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/zeedu_dev/swiss-tables-the-superior-performance-hashmap-1gp6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/zeedu_dev/swiss-tables-the-superior-performance-hashmap-1gp6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>architecture</category>
      <category>database</category>
      <category>go</category>
    </item>
    <item>
      <title>Swiss Tables the superior performance hashmap</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Mon, 22 Sep 2025 02:00:00 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/swiss-tables-the-superior-performance-hashmap-1gp6</link>
      <guid>https://dev.to/zeedu_dev/swiss-tables-the-superior-performance-hashmap-1gp6</guid>
      <description>&lt;h2&gt;
  
  
  The Hash Map Got a Swiss Army Knife Upgrade
&lt;/h2&gt;

&lt;p&gt;You’ve probably used hashmaps in the past, but the thing here is, you use it and you forget about the internals you limit your knowledge of hashmaps to getting and setting keys, you iterate over them probably, but that’s 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="nx"&gt;hashmap&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;hashmap&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;"&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="c1"&gt;// or&lt;/span&gt;
&lt;span class="nx"&gt;hashmap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every worth-learning language has its own implementation, you know, the way it works under the hood, and most devs just don’t give a damn about it, which is fine, I support high level abstractions.&lt;/p&gt;

&lt;p&gt;The thing here is that, recently,&lt;a href="https://coffeebytes.dev/en/go/golang-maps-or-dictionaries/" rel="noopener noreferrer"&gt;Go decided to change its default hashmap implementation from Buckets to Swiss tables&lt;/a&gt; looking for better performance, &lt;del&gt;trying to mimick Rust’s performance&lt;/del&gt;. Which already &lt;a href="https://www.datadoghq.com/blog/engineering/go-swiss-tables/" rel="noopener noreferrer"&gt;paid off for some companies saving them hundreds of gigabytes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By the way it was Google who created Swiss tables (well one of its engineers), and also &lt;a href="https://coffeebytes.dev/en/software-architecture/fast-and-performant-apis-using-go-lang-grpc-and-protobuffers/" rel="noopener noreferrer"&gt;protobuffers and GRPC&lt;/a&gt;, they’re always improving the performance of what already exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swiss Tables simulator in real time
&lt;/h2&gt;

&lt;p&gt;I coded a Swiss Tables simulator on my blog, however I can't inject Javascript here. So you have to go to my &lt;a href="https://coffeebytes.dev/en/software-architecture/swiss-tables-the-superior-performance-hashmap/" rel="noopener noreferrer"&gt;Swiss Tables entry&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2F9allijtd3sdf4a14yy9e.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.amazonaws.com%2Fuploads%2Farticles%2F9allijtd3sdf4a14yy9e.png" alt=" " width="800" height="842"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So, What’s the Big Idea behind Swiss Tables? It’s All About Metadata.
&lt;/h2&gt;

&lt;p&gt;Traditional open-addressing hash maps store your key-value pairs in a big array. When you insert an element, you hash the key to find a “home” slot. If that seat is already taken, you probe for the next available spot and so on.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Slot 1&lt;/th&gt;
&lt;th&gt;Slot 2&lt;/th&gt;
&lt;th&gt;Slot 3&lt;/th&gt;
&lt;th&gt;Slot 4&lt;/th&gt;
&lt;th&gt;Slot 5&lt;/th&gt;
&lt;th&gt;Slot 6&lt;/th&gt;
&lt;th&gt;slot 7&lt;/th&gt;
&lt;th&gt;slot 8&lt;/th&gt;
&lt;th&gt;slot 9&lt;/th&gt;
&lt;th&gt;slot n&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;↓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So… what’s wrong with this? Well, nothing really, just that in certain scenarios it can become messy. To find an element, or to confirm it’s &lt;em&gt;not&lt;/em&gt; there, you might have to traverse half the array. That’s slow.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Slot 1&lt;/th&gt;
&lt;th&gt;Slot 2&lt;/th&gt;
&lt;th&gt;Slot 3&lt;/th&gt;
&lt;th&gt;Slot 4&lt;/th&gt;
&lt;th&gt;Slot 5&lt;/th&gt;
&lt;th&gt;Slot 6&lt;/th&gt;
&lt;th&gt;slot 7&lt;/th&gt;
&lt;th&gt;slot 8&lt;/th&gt;
&lt;th&gt;slot 9&lt;/th&gt;
&lt;th&gt;slot n&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Swiss Tables attack this cumbersome problem with a brilliant idea: a separate metadata array. For every slot in the main data array, there’s a corresponding byte in the metadata array.&lt;/p&gt;

&lt;p&gt;This byte isn’t just a tombstone or an empty flag; it’s a packed suite of useful information. The most crucial part is the &lt;strong&gt;7 bits from the hash of the key&lt;/strong&gt; stored in that slot.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Meaning Control bit&lt;/th&gt;
&lt;th&gt;Control bit&lt;/th&gt;
&lt;th&gt;Bit 1&lt;/th&gt;
&lt;th&gt;Bit 2&lt;/th&gt;
&lt;th&gt;Bit 3&lt;/th&gt;
&lt;th&gt;Bit 4&lt;/th&gt;
&lt;th&gt;Bit 5&lt;/th&gt;
&lt;th&gt;Bit 6&lt;/th&gt;
&lt;th&gt;Bit 7&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Empty&lt;/td&gt;
&lt;td&gt;1&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;td&gt;0&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;tr&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0x3A&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deleted&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&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;This is a game changer. Why? Because to check if a slot might contain our key, you don’t need to touch the main data array at all. You can first check the metadata. This is a huge win for performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Step-by-Step Walk Through the Swiss table data structure
&lt;/h2&gt;

&lt;p&gt;Alright, so we’ve talked about the metadata esoteric magic. But how does it actually works, step-by-step, when you insert &lt;em&gt;my_app[“value”]&lt;/em&gt; = value and when you ask for &lt;em&gt;my_map[“apple”]&lt;/em&gt;?&lt;/p&gt;

&lt;h3&gt;
  
  
  Inserting a key and its value in a Swiss table
&lt;/h3&gt;

&lt;p&gt;When you look up a key the overall goes this way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The key is hashed.&lt;/li&gt;
&lt;li&gt;The group or bucket is decided using h1.&lt;/li&gt;
&lt;li&gt;The target slot or block is located using h2.&lt;/li&gt;
&lt;li&gt;If the slot contains our key already you just update it&lt;/li&gt;
&lt;li&gt;If no slot contains our key, you look for an empty slot.&lt;/li&gt;
&lt;li&gt;If all slots are taken you jump to the next group&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Hash the key
&lt;/h4&gt;

&lt;p&gt;First, the key “apple” is run through a robust hash function. This produces a full 64-bit hash value. Let’s say it’s something like 0x5A3F9C42B1D08E3A (a beautiful, random-looking number). Now, Swiss Tables perform a neat trick: they split this hash into two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;57 bits (Called h1)&lt;/li&gt;
&lt;li&gt;7 bits (Called h2)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;H1&lt;/th&gt;
&lt;th&gt;…&lt;/th&gt;
&lt;th&gt;H1&lt;/th&gt;
&lt;th&gt;H2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10101110&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;11100010&lt;/td&gt;
&lt;td&gt;0x3A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Decide the initial group using h1
&lt;/h4&gt;

&lt;p&gt;The first 57 bits (0x5A3F9C42B1D08E…): This part of the hash determines which initial group or “bucket” the key belongs to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x5A3F9C42B1D08E % 2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Locate target block
&lt;/h4&gt;

&lt;p&gt;The last 7 low bits (0x3A): This is the “probe index.” It tells the map which of the 8 or 16-slots (or “blocks”) to start looking in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving the value of a key
&lt;/h3&gt;

&lt;p&gt;When you look up a key the overall goes this way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The key is hashed.&lt;/li&gt;
&lt;li&gt;The group or bucket is decided using h1.&lt;/li&gt;
&lt;li&gt;The target slot or block is located using h2.&lt;/li&gt;
&lt;li&gt;The CPU takes the 7-bit (h2) hash snippet from our key and loads it into a special register.&lt;/li&gt;
&lt;li&gt;It then compares this &lt;em&gt;single&lt;/em&gt; value against &lt;em&gt;all 8 or 16&lt;/em&gt; metadata bytes in the target block— &lt;strong&gt;simultaneously&lt;/strong&gt;. This is done using SIMD (Single Instruction, Multiple Data) instructions, basically parallelism.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let me explain you the steps.&lt;/p&gt;

&lt;h4&gt;
  
  
  Finding the block using the 7-bit hash
&lt;/h4&gt;

&lt;p&gt;The map takes the 7-bit (h2) probe index (0x3A) and uses it to locate the specific 16-slot block where “apple” should be. This calculation is incredibly fast.&lt;/p&gt;

&lt;h4&gt;
  
  
  Use SIMD to compare the slots
&lt;/h4&gt;

&lt;p&gt;This is the SIMD (Single Instruction, Multiple Data) step we love. The CPU loads the 16 metadata bytes from that block. For each byte, it checks two things:&lt;/p&gt;

&lt;p&gt;Is the slot occupied? (A special bit in the metadata byte indicates this).&lt;/p&gt;

&lt;p&gt;Does the 7-bit (h2) fingerprint in the metadata match our fingerprint (0x3A)?&lt;/p&gt;

&lt;p&gt;It does this for all 16 slots AT ONCE. And this is where the magic happens, I’ll elaborate in a moment. The result is a bitmask of potential candidates.&lt;/p&gt;

&lt;h4&gt;
  
  
  Handle collisions if they exist
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;A matching 7-bit fingerprint doesn’t mean the keys are equal&lt;/strong&gt; ; it just means they might be equal. It’s a pre-filter. It’s designed to be fast, not perfect. The full 57-bit hash (h1) (and eventually the actual key comparison) is the final arbiter.&lt;/p&gt;

&lt;p&gt;If there are any matches (say, slots 2 and 9 had the same 7-bit (h2) fingerprint and are occupied, a collision occurred), the map finally goes to the main data array. But it’s not guessing anymore. It goes directly to slots 2 and 9 and performs a full key comparison: stored_key == “apple”? This is the only expensive operation, and you’ve minimized it to just one or two checks, lukcily you won’t have more than two collisions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Retrieve the key’s value
&lt;/h4&gt;

&lt;p&gt;Finally, if a full key matches, it returns the value. If not, or if the SIMD step found no candidates, &lt;strong&gt;it can confidently say the key isn’t in the map&lt;/strong&gt;. This last part—the negative lookup—is where Swiss Tables absolutely dominate traditional maps that have to trudge through long probe sequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why are Swiss tables so fast? SIMD and the 16-Slot Block
&lt;/h2&gt;

&lt;p&gt;Here’s where the real genius kicks in. Modern CPUs don’t need to check things one byte at a time. They are surprisingly good doing operations in parallel. Swiss Tables are designed to exploit this by grouping slots into blocks (typically of 16).&lt;/p&gt;

&lt;p&gt;In just one &lt;del&gt;blazingly fast&lt;/del&gt; operation, the CPU creates a bitmask. A &lt;em&gt;1&lt;/em&gt; means “the hash snippet matches,” a &lt;em&gt;0&lt;/em&gt; means it doesn’t. Only &lt;em&gt;then&lt;/em&gt;, for the slots that might be a match, does the code actually dereference the pointer to the main data array to do a full key comparison.&lt;/p&gt;

&lt;p&gt;This is the killer feature of Swiss Tables. It minimizes expensive memory accesses and leverages the CPU’s parallel processing capabilities.&lt;/p&gt;

&lt;p&gt;It makes lookups, especially for missing keys, super fast. You’re not traversing a chain or a long probe sequence. Which, as you may know, impacts &lt;a href="https://coffeebytes.dev/en/linux/the-big-o-notation-for-algorithm-analysis/" rel="noopener noreferrer"&gt;Big O performance&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Should You Care? The Swiss tables Advantages
&lt;/h2&gt;

&lt;p&gt;This architecture isn’t just a neat bloring and hypothetical academic exercise. It translates into real tangible benefits that nerdy devs will see reflected in their applications, &lt;del&gt;transforming a 0.0004s into a 0.00002s execution&lt;/del&gt;. According to Go’s official page, &lt;a href="https://go.dev/blog/swisstable" rel="noopener noreferrer"&gt;performance went up by about 63% compared&lt;/a&gt; to Buckets implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Blazing Fast Lookups:&lt;/strong&gt; The combination of the metadata filter and SIMD makes &lt;em&gt;find()&lt;/em&gt; and &lt;em&gt;contains()&lt;/em&gt; operations significantly faster than in most traditional maps. It’s not a small margin; we’re talking multiples in many benchmarks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Super Efficient Memory Use:&lt;/strong&gt; Swiss Tables are typically implemented as “flat” structures. This means they store keys and values directly in the array, not as separate allocated nodes. This greatly improves cache locality—the data you need is probably already in the CPU’s fast cache(You should know the drill: L1, L2, L3 caches)—and it avoids the memory overhead of pointers used in chained implementations. This makes them superior to other hashmaps implementations in terms of memory usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Smarter Resizing:&lt;/strong&gt; The metadata array makes the internal control logic much smarter. The map can make better decisions about when to rehash and how to distribute elements, keeping performance more consistent as the load factor increases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok but, what about Swiss tables disadvantages
&lt;/h2&gt;

&lt;p&gt;However, it’s not all sunshine and rainbows, of course, everyhing in tech is trade-off. The separate metadata array does consume extra memory (about 1/16th to 1/8th of the main array), which is usually a great trade-off because memory is one of the cheapest resources.&lt;/p&gt;

&lt;p&gt;Also the implementation is complex—thankfully, but that doesn’t involve you, because you’re going to use it the same way you have always used it.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>go</category>
    </item>
    <item>
      <title>How to create a MCP server and MCP tools from scratch?</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Wed, 17 Sep 2025 00:00:32 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/how-to-create-a-mcp-server-and-mcp-tools-from-scratch-fea</link>
      <guid>https://dev.to/zeedu_dev/how-to-create-a-mcp-server-and-mcp-tools-from-scratch-fea</guid>
      <description>&lt;h2&gt;
  
  
  Why Would We Want to Create a MCP Server?
&lt;/h2&gt;

&lt;p&gt;Creating a MCP server allows us to connect an LLM or AI to real-time data, personal data, or other data sources.&lt;/p&gt;

&lt;p&gt;I previously wrote a post where I explain &lt;a href="https://coffeebytes.dev/en/artificial-intelligence/understand-the-model-context-protocol-or-mcp-once-and-for-all/" rel="noopener noreferrer"&gt;how the Model Context Protocol (MCP) works internally&lt;/a&gt;; you can check it out if you’re not satisfied with just a recipe and want to learn more.&lt;/p&gt;

&lt;p&gt;In this post, I’ll detail how to create a MCP server. We’re going to make one that solves &lt;a href="https://community.openai.com/t/incorrect-count-of-r-characters-in-the-word-strawberry/829618" rel="noopener noreferrer"&gt;one of the most embarrassing LLM errors: not being able to count the number of r’s&lt;/a&gt; in variations of the word strawberry. For example: &lt;em&gt;strawberrrry&lt;/em&gt; or &lt;em&gt;strawberrrrrrry&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you’re not a bot, you already know that counting letters is a pretty trivial task for a human.&lt;/p&gt;

&lt;p&gt;On the other hand, for an LLM it’s almost impossible due to how it works—based on tokens. &lt;a href="https://coffeebytes.dev/en/artificial-intelligence/ai-generated-art-and-ai-generated-code-are-treated-differently/" rel="noopener noreferrer"&gt;AI can create art and code&lt;/a&gt;, even though it &lt;a href="https://coffeebytes.dev/en/artificial-intelligence/chat-gpt-searles-chinese-room-and-consciousness/" rel="noopener noreferrer"&gt;isn’t a consciou entity&lt;/a&gt;, but it can’t count letters. Contradictory, isn’t it?&lt;/p&gt;

&lt;p&gt;For now, we’ll turn this into an educational exercise on how to deploy a MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements to Create a MCP Server in JavaScript
&lt;/h2&gt;

&lt;p&gt;We’ll start with a Node installation—you know, the result of running: &lt;em&gt;npm init -y&lt;/em&gt;, and we’ll create the index file using &lt;a href="https://coffeebytes.dev/en/linux/linux-basic-commands-grep-ls-cd-cat-cp-rm-scp/" rel="noopener noreferrer"&gt;the Linux touch command&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── index.js
├── package.json
└── package-lock.json

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set package.json as a Module
&lt;/h3&gt;

&lt;p&gt;Inside the &lt;em&gt;package.json&lt;/em&gt; file, change or create the &lt;em&gt;type&lt;/em&gt; property and assign it the value &lt;em&gt;module&lt;/em&gt;, so npm treats it as a module.&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&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;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install the Library
&lt;/h3&gt;

&lt;p&gt;A MCP server requires &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;the official MCP server SDK&lt;/a&gt;, just install it via npm. We also need &lt;em&gt;zod&lt;/em&gt;, which, &lt;del&gt;if you’re unlucky enough to use JavaScript&lt;/del&gt; , is used to validate data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @modelcontextprotocol/sdk@1.16.0 zod@3.25.76

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a MCP Server
&lt;/h2&gt;

&lt;p&gt;Afterward let’s create a MCP server just initiliazing a new McPServer object&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;McpServer&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;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&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;McpServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Strawberry Count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&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;h2&gt;
  
  
  Create MCP Tools in a MCP Server
&lt;/h2&gt;

&lt;p&gt;Next, we’ll define the tools, or &lt;em&gt;mcp tools&lt;/em&gt;, that allow us to receive a parameter from the LLM so we can do whatever we want with it—in this case, count the r’s. I already talked about &lt;em&gt;mcp tools&lt;/em&gt; in my introduction to the Model Context Protocol.&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="c1"&gt;// server.tool("&amp;lt;Name&amp;gt;", "&amp;lt;Description&amp;gt;", {&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Strawberry Count&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;Count the number of r's present in an strawberry variant&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;param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The strawberry variant, it can have a variable number of r's&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="c1"&gt;// callback with the param&lt;/span&gt;
    &lt;span class="c1"&gt;// AI will detect this param&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;param&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;content&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`The word has: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&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;letter&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;letter&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;r&lt;/span&gt;&lt;span class="dl"&gt;"&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="s2"&gt; r's`&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;One thing to notice here, we’re counting the r’s manually—no tricks whatsoever. We process the string we receive and return the number of r’s.&lt;/p&gt;

&lt;p&gt;Also, I want to highlight how awesome is the dark magic that the LLM uses to get our custom param. Our LLM detects the param that we want to receive from the user’s input using as context the description information that we passed to the describe method.&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.amazonaws.com%2Fuploads%2Farticles%2F4bbyzg2ij4cu9nff1m2i.webp" 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.amazonaws.com%2Fuploads%2Farticles%2F4bbyzg2ij4cu9nff1m2i.webp" alt="MCP meme" width="500" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously, your &lt;em&gt;mcp tool&lt;/em&gt; won’t do something as useless as counting r’s—you could fetch data from an API, process it, get info from the file system, or even from another LLM—whatever you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Does the LLM Get Its Context From?
&lt;/h2&gt;

&lt;p&gt;For this example, we’ll use STDIO (Standard Input Output). But in the MCP introduction I talked you a little bit about its alternatives: SSE and HTTP Stream.&lt;/p&gt;

&lt;p&gt;In this case, it’s basically the user’s text input.&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;transport&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;StdioServerTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run the MCP Server to Read from STDIO
&lt;/h2&gt;

&lt;p&gt;Now we’ll connect this transport to the MCP server so it can read directly from STDIO.&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;await&lt;/span&gt; &lt;span class="nx"&gt;server&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="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting the MCP to Other Applications, like Claude Desktop
&lt;/h3&gt;

&lt;p&gt;We can connect this MCP server to any program that supports MCP so we can use it.&lt;/p&gt;

&lt;p&gt;For example, for Claude Desktop we would fill out the &lt;em&gt;claude_desktop_config.json&lt;/em&gt; file.&lt;/p&gt;

&lt;p&gt;Notice how we declare that the MCP server should run with the &lt;code&gt;npx&lt;/code&gt; command followed by the list of arguments in order.&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mcpservers&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;command&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;npx&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;args&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-y&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;node&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;&amp;lt;full_path_to_js_file&amp;gt;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you wanted to use TypeScript instead of JavaScript, you could do something 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="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mcpservers&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;command&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;npx&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;args&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-y&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;tsx&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;&amp;lt;full_path_to_ts_file&amp;gt;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Analyzing the MCP Server with the Inspector
&lt;/h2&gt;

&lt;p&gt;How we test that our MCP works? Claude’s inspector lets you test and debug MCP servers. You can view the tools you’ve created and simulate their function—however, these don’t use an LLM. It’s a plain server, functioning as if we were the LLM.&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.amazonaws.com%2Fuploads%2Farticles%2F9tdyggcib3iroc4yer3s.webp" 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.amazonaws.com%2Fuploads%2Farticles%2F9tdyggcib3iroc4yer3s.webp" alt="MCP Inspector GUI" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To run it, you can use &lt;em&gt;npx&lt;/em&gt; and pass the server command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @modelcontextprotocol/inspector &amp;lt;mcp_server_command&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wasn’t able to run the command using substitution. I tried using the relative and absolute directory, but I couldn’t connect to the server—I’m not sure why.&lt;/p&gt;

&lt;p&gt;However it did work by manually filling in the data in the Inspector’s graphical interface.&lt;/p&gt;

&lt;p&gt;After running the inspector, we’ll have a server running on port 6274 (by default, though this can be changed).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @modelcontextprotocol/inspector npm index.js
npx &lt;span class="nt"&gt;-y&lt;/span&gt; @modelcontextprotocol/inspector npm /home/dir/mcp-server/index.js

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, fill in the &lt;em&gt;transport type&lt;/em&gt; and commands in case you had the same issue I did.&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.amazonaws.com%2Fuploads%2Farticles%2F3rbdozw2klyglg0pszgo.webp" 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.amazonaws.com%2Fuploads%2Farticles%2F3rbdozw2klyglg0pszgo.webp" alt="MCP inspector configuration panel" width="286" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we click on “list tools,” it’ll detect the tool we just created.&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.amazonaws.com%2Fuploads%2Farticles%2Fnmjmrzbxicm5a8wocp1q.webp" 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.amazonaws.com%2Fuploads%2Farticles%2Fnmjmrzbxicm5a8wocp1q.webp" alt="MCP inspector tools panel" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By clicking on &lt;em&gt;List tools&lt;/em&gt;, and then on the name of the tool, will activate and we’ll be able to pass it our parameter: a &lt;em&gt;strawberry&lt;/em&gt; variant with as many r’s as we want.&lt;/p&gt;

&lt;p&gt;Just remember, this parameter is what we’d receive directly from our LLM—it would be the one responsible for parsing the user’s input and returning the parameter accordingly, so don’t mess with the input.&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.amazonaws.com%2Fuploads%2Farticles%2Ftyjnridlygipzaiw75xc.webp" 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.amazonaws.com%2Fuploads%2Farticles%2Ftyjnridlygipzaiw75xc.webp" alt="MCP inspector input box" width="768" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the code works. The MCP server returns our message. In a real-world setting, this would be received by the LLM and would tell the user the correct number of r’s in their &lt;em&gt;strawberry&lt;/em&gt; variant—without making a fool of itself, as it would normally do.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>backend</category>
    </item>
    <item>
      <title>Zero Downtime Migrations: Shadow Table Strategy Explained</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Tue, 16 Sep 2025 17:37:13 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-25j7</link>
      <guid>https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-25j7</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-hp9" class="crayons-story__hidden-navigation-link"&gt;Zero Downtime Migrations: Shadow Table Strategy Explained&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/zeedu_dev" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" alt="zeedu_dev profile" class="crayons-avatar__image" width="800" height="1066"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/zeedu_dev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eduardo Zepeda
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eduardo Zepeda
                
              
              &lt;div id="story-author-preview-content-2634874" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/zeedu_dev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" class="crayons-avatar__image" alt="" width="800" height="1066"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eduardo Zepeda&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-hp9" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 11 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-hp9" id="article-link-2634874"&gt;
          Zero Downtime Migrations: Shadow Table Strategy Explained
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/backend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;backend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/sql"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;sql&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-hp9" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-hp9#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>database</category>
      <category>backend</category>
      <category>sql</category>
    </item>
    <item>
      <title>Zero Downtime Migrations: Shadow Table Strategy Explained</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Mon, 15 Sep 2025 22:14:41 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-hp9</link>
      <guid>https://dev.to/zeedu_dev/zero-downtime-migrations-shadow-table-strategy-explained-hp9</guid>
      <description>&lt;p&gt;Picture this: It’s 2 AM, you’re deploying a “simple” column type change to production, and suddenly your entire application is down because the table lock is taking forever. Your phone starts buzzing with angry Slack notifications, and you’re frantically trying to explain to your team why the “5-minute migration” has been running for 30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s the Shadow Table Strategy?
&lt;/h2&gt;

&lt;p&gt;The shadow table strategy is like having a stunt double for your database table. Instead of modifying your original table directly (and potentially bringing your application to its knees), you create a &lt;del&gt;shadow clone&lt;/del&gt; new table with the desired structure, gradually copy data over, and then perform a lightning-fast switcheroo.&lt;/p&gt;

&lt;p&gt;Here’s the basic flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dwrscezd2/image/upload/v1751079950/coffee-bytes/shadow-table-explanation_gbdhc0.png" rel="noopener noreferrer"&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.amazonaws.com%2Fuploads%2Farticles%2Fpntoymr2rqtljhjmx0d2.png" alt="Shadow table diagram" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Is This Solving Shadow Table Strategy?
&lt;/h2&gt;

&lt;p&gt;Traditional &lt;em&gt;ALTER TABLE&lt;/em&gt; operations can be absolute nightmares in production if you’re dealing with websites that have billions of records. When you run something like &lt;em&gt;ALTER TABLE users MODIFY COLUMN id BIGINT&lt;/em&gt;, most database engines will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lock the entire table&lt;/strong&gt; for the duration of the operation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block all reads and writes&lt;/strong&gt; while restructuring the table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take forever&lt;/strong&gt; on large tables (we’re talking hours for billions of rows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk timeouts&lt;/strong&gt; that leave your database in an inconsistent state&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The shadow table strategy solves these problems by breaking the &lt;a href="https://coffeebytes.dev/en/go-migration-tutorial-with-migrate/" rel="noopener noreferrer"&gt;database migration&lt;/a&gt; into smaller, more manageable chunks that don’t require long-running locks. Your application stays online, users stay happy, and you don’t get notifications at 3 AM.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about smaller tables
&lt;/h3&gt;

&lt;p&gt;If your table doesn’t have millions of rows and it’s not critical that the database keeps running, you can always just send an email notifying your users that your app will be under maintenance for a short period.&lt;/p&gt;

&lt;p&gt;I mean nobody is going to hate you if your furry images site is down for a couple of hours, put a “touch some grass” page and perform the migration, &lt;a href="https://coffeebytes.dev/en/dont-obsess-about-your-web-application-performance/" rel="noopener noreferrer"&gt;don’t obsess unnecessarily about your app’s performance.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, if that’s not the case and the business is losing money for every second that the database is locked…&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to Perform a Table Modification Using Shadow Table Strategy
&lt;/h2&gt;

&lt;p&gt;Let’s walk through a real-world example where we need to change a user ID from INT to BIGINT because we’re approaching the 2.1 billion limit.&lt;/p&gt;

&lt;p&gt;Stonks. Congratulations if you’re the one running the show.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Shadow Table
&lt;/h3&gt;

&lt;p&gt;First, create your new table with the desired structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dwrscezd2/image/upload/v1751080620/coffee-bytes/shadow-table-copy_jlbtgz.png" rel="noopener noreferrer"&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.amazonaws.com%2Fuploads%2Farticles%2Fxx4nzptpt52zcgemtthl.png" alt="Create the Shadow Table" width="800" height="467"&gt;&lt;/a&gt;&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="c1"&gt;-- Create the shadow table with the new structure&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Copy indexes from the original table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_created_at&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Set Up Data Synchronization
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dwrscezd2/image/upload/v1751080652/coffee-bytes/shadow-table-sync_v7mgtq.png" rel="noopener noreferrer"&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.amazonaws.com%2Fuploads%2Farticles%2Farfsjxihc1djf3xlddhr.png" alt="Sync both tables" width="800" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where things get interesting. You need to keep the shadow table in sync with the original while your application continues to operate like if nothing were happening.&lt;/p&gt;

&lt;p&gt;Yes, you’re duplicating writes and running two tables instead of one. For this there are two approaches:&lt;/p&gt;

&lt;h4&gt;
  
  
  Use database triggers
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create triggers to keep shadow table in sync&lt;/span&gt;
&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;users_insert_sync&lt;/span&gt;
    &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="err"&gt;$$&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;users_update_sync&lt;/span&gt;
    &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt; 
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updated_at&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="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="err"&gt;$$&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;users_delete_sync&lt;/span&gt;
    &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users_new&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="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="err"&gt;$$&lt;/span&gt;

&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Synchronize data at Application-Level
&lt;/h4&gt;

&lt;p&gt;If you prefer, you can sync the data through your application’s logic instead.&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;update_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Original write to main table or database
&lt;/span&gt;    &lt;span class="n"&gt;original_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE users SET ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Shadow write (with transformation)
&lt;/span&gt;    &lt;span class="n"&gt;shadow_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE users_new SET name=? ...&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&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;h3&gt;
  
  
  Step 3: Copy Existing Data in Batches
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dwrscezd2/image/upload/v1751082296/coffee-bytes/shadow-table-copy-data_1_m2qwh7.png" rel="noopener noreferrer"&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.amazonaws.com%2Fuploads%2Farticles%2Fu7w3cekbr494tn9jw3id.png" alt="Copy data in batches" width="800" height="1231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now comes the bulk data migration. &lt;strong&gt;Never try to copy everything at once&lt;/strong&gt; , that’s a recipe for a digital hecatombe. Remember the &lt;a href="https://coffeebytes.dev/en/worker-pool-design-pattern-explanation/" rel="noopener noreferrer"&gt;worker pool pattern’s&lt;/a&gt; reason of existance.&lt;/p&gt;

&lt;p&gt;Make sure you have enough resources and consider doing this during low-traffic periods:&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="c1"&gt;-- Copy data in manageable batches to avoid long locks&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;min_id&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;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;max_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Loop through batches (you'd typically script this)&lt;/span&gt;
&lt;span class="n"&gt;WHILE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;min_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;max_id&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&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;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;min_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;min_id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;DUPLICATE&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;min_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;min_id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Give the database a breather&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;SLEEP&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;WHILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Verify Data Consistency
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dwrscezd2/image/upload/v1751081576/coffee-bytes/shadow-table-copy-compare_2_lzbmok.png" rel="noopener noreferrer"&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.amazonaws.com%2Fuploads%2Farticles%2Fzpk3gwgz3k6reqax5flz.png" alt="Compare data is the same in shadow table" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before you make the switch, you better be damn sure everything copied correctly:&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="c1"&gt;-- Check row counts match&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;original_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;shadow_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Check data integrity with checksums&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CRC32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;original_checksum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CRC32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;shadow_checksum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Spot check some random records&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&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;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&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;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: The Great Switcheroo
&lt;/h3&gt;

&lt;p&gt;Here’s where your heart rate spikes. The actual table swap should be lightning fast, like an &lt;del&gt;unnecessary&lt;/del&gt; Rust new shiny library:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dwrscezd2/image/upload/v1751081923/coffee-bytes/shadow-table-copy-switch_fck15s.png" rel="noopener noreferrer"&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.amazonaws.com%2Fuploads%2Farticles%2Fovs5tcsl6yl1b0ie979w.png" alt="Switch from old table to shadow table" width="800" height="826"&gt;&lt;/a&gt;&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="c1"&gt;-- This should take milliseconds, not minutes&lt;/span&gt;
&lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Drop the triggers first (no more syncing needed)&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;users_insert_sync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;users_update_sync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;users_delete_sync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Rename tables atomically&lt;/span&gt;
&lt;span class="k"&gt;RENAME&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;users_old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&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;h3&gt;
  
  
  Step 6: Clean Up and Verify
&lt;/h3&gt;

&lt;p&gt;Clean up and &lt;del&gt;realize everything went wrong&lt;/del&gt; celebrate.&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="c1"&gt;-- Verify everything is working&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- After you're confident everything works (give it a day or two, nevermind, maybe a couple of months)&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users_old&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling Tables with millions of QPS
&lt;/h2&gt;

&lt;p&gt;For tables with millions of QPS, or &lt;a href="https://coffeebytes.dev/en/how-to-scale-a-django-app-to-serve-one-million-users/" rel="noopener noreferrer"&gt;million of concurrent users&lt;/a&gt;, you can opt for using a queue instead of directly writes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use a write buffer&lt;/strong&gt; : Queue shadow writes in Redis/Kafka if database just can’t handle dual writes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dwrscezd2/image/upload/v1751083242/coffee-bytes/shadow-table-queue_fln9xj.png" rel="noopener noreferrer"&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.amazonaws.com%2Fuploads%2Farticles%2Fdszgrv4mflc4nmz9qrfr.png" alt="Write buffer for shadow table" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Column mapping hell?&lt;/strong&gt; Use views to abstract renames:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;users_combined&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;unified_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users_new&lt;/span&gt;
  &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Errors While Implementing Shadow Tables
&lt;/h2&gt;

&lt;p&gt;Let me save you some pain by sharing some common mistakes I’ve seen and read about &lt;del&gt;and made&lt;/del&gt; :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting to Handle Foreign Key Constraints&lt;/strong&gt; : If other tables reference your table, you’ll need to temporarily disable foreign key checks or handle the references carefully. Don’t just ignore this, your data integrity depends on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not Testing the Triggers Thoroughly&lt;/strong&gt; : Triggers can fail silently or behave in strange ways under load. Test them with realistic data volumes and concurrent operations before going to production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Underestimating the Sync Lag&lt;/strong&gt; : During heavy write periods, your triggers can let you down. Monitor the sync status and be prepared to throttle writes if necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inadequate Monitoring&lt;/strong&gt; : You need to be aware during the migration progress, sync lag, and any errors, not after things go wrong. Implement monitoring before you start, not while everything is falling apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poor Rollback Planning&lt;/strong&gt; : Always have plan B. If something goes sideways during the switchover, you need to be able to revert quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Shadow Tables in Distributed Systems
&lt;/h2&gt;

&lt;p&gt;Consider service discovery, connection pooling, and cache invalidation. When you rename your tables, remember that every service instance needs to know about the change simultaneously.&lt;/p&gt;

&lt;p&gt;This often means implementing a coordinated deployment strategy where you temporarily stop traffic, perform the switch, and afterwards restart services.&lt;/p&gt;

&lt;p&gt;Consider using feature flags, like the one I told you about in my &lt;a href="https://coffeebytes.dev/en/common-and-useful-deployment-patterns/" rel="noopener noreferrer"&gt;post about deployment patterns&lt;/a&gt;, to control which table your application reads from. This gives you fine-grained control over the migration and allows for gradual rollouts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Databases with read replicas
&lt;/h2&gt;

&lt;p&gt;Ensure your shadow table has been fully replicated before performing the switch. Monitor replication lag carefully, as the switch needs to happen consistently across all replicas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Performance During the Transition
&lt;/h2&gt;

&lt;p&gt;When executing the migration you’re essentially running two tables in parallel, with its corresponding I/O usage, check disk usage and monitor it.&lt;/p&gt;

&lt;p&gt;Triggers add overhead to every single INSERT, UPDATE, and DELETE operation. Set up alerts for unusually long trigger execution times.&lt;/p&gt;

&lt;p&gt;Compare row counts between the original and shadow tables during the process, if the difference between both grows, there is something fishy going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Unique Constraints and Foreign Keys
&lt;/h2&gt;

&lt;p&gt;For unique constraints, you need to ensure the shadow table maintains exactly the same uniqueness guarantees as the original table.&lt;/p&gt;

&lt;p&gt;When copying data in batches, use INSERT … ON DUPLICATE KEY UPDATE or equivalent upsert logic to handle potential duplicates, especially if your application continues to write data during the migration.&lt;/p&gt;

&lt;p&gt;For Foreign Key constraints, you can disable checks during the migration, however you’re risking data integrity, it’s always a trade off. Use deferred checks (If you’re using PostgreSQL) or disable FK checks (MySQL SET FOREIGN_KEY_CHECKS=0)&lt;/p&gt;

&lt;p&gt;Alternatively, you can create the foreign key constraints on the shadow table and update referencing tables after the main migration finishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dealing with Triggers and Stored Procedures
&lt;/h2&gt;

&lt;p&gt;Existing triggers and stored procedures can be a pain in the…&lt;/p&gt;

&lt;p&gt;Firstly, list all existing triggers on your table. You’ll need to recreate these triggers on the shadow table, just remember that execution order is paramount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your sync triggers should generally be executed last&lt;/strong&gt; , after all business logic triggers have run.&lt;/p&gt;

&lt;p&gt;Stored procedures that point to your table, should be renamed after the migration. Use synonyms or views to minimize the number of procedures requiring updates.&lt;/p&gt;

&lt;p&gt;Test trigger interactions thoroughly in a staging environment to prevent unwanted surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying Data Consistency Before Cutover
&lt;/h2&gt;

&lt;p&gt;Never skip this step, no matter what.&lt;/p&gt;

&lt;p&gt;Compare aggregate values like sums, averages, and counts. &lt;strong&gt;Use checksums or hash your data to double-check that row data matches exactly between tables&lt;/strong&gt;. Statistics is your friend, get the correct number of records to sample to get that 95% confidence level.&lt;/p&gt;

&lt;p&gt;Create automated consistency check scripts that you can run repeatedly during the migration that let you know if everything is going well.&lt;/p&gt;

&lt;p&gt;Checksum tools (like pg_checksums or custom COUNT(*) + hash_agg() queries) are your friends.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rollback Plan (When Everything Goes to Hell)
&lt;/h2&gt;

&lt;p&gt;Are you familiar with Murphy’s Law? Well you know the drill then.&lt;/p&gt;

&lt;p&gt;The simplest rollback is to reverse the renaming operation. &lt;strong&gt;Keep your original table as &lt;em&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;_old, or whatever you want, during the cutover period , so you can quickly rename it back if Murphy shows up. This should be blazingly fast.

&lt;p&gt;For more complex rollbacks, you might need to reverse-sync data from the shadow table back to the original. Consider maintaining reverse triggers during the cutover period, it adds complexity but you’ll have more options available&lt;/p&gt;

&lt;p&gt;When production is on fire, you don’t want to be figuring out rollback commands on the fly. &lt;strong&gt;Always document your rollback procedures clearly&lt;/strong&gt; , practice in stagging environments.&lt;/p&gt;

&lt;p&gt;Consider implementing application-level rollback mechanisms using feature flags or configuration changes. I talked a little bit about them in my post about deployment patterns. Sometimes it’s faster to point your application back to the old table than to perform database-level changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools to help you
&lt;/h2&gt;

&lt;p&gt;There are some tables that can help you to ease the process of migrating a table:&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Killer Feature&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gh-ost&lt;/td&gt;
&lt;td&gt;MySQL&lt;/td&gt;
&lt;td&gt;Triggerless replication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pg_repack&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Online table reorganization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Liquibase&lt;/td&gt;
&lt;td&gt;Cross-DB&lt;/td&gt;
&lt;td&gt;Change tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debezium&lt;/td&gt;
&lt;td&gt;CDC streaming&lt;/td&gt;
&lt;td&gt;Kafka integration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The shadow table strategy is far away from perfect and many things can occur, you can f'up your boss' database so take things calmly, don't let them pressure you, and don't work alone.&lt;/p&gt;

&lt;p&gt;Remember that every database and application is different. What works for a read-heavy tables might not work for a write-heavy tables. Always test in an environment that mimics production as closely as possible.&lt;/p&gt;

&lt;p&gt;Don’t be afraid to abort the migration during the last minute if things aren’t going according to plan, it’s all about keeping things working not about playing the hero. Yes! you're not Saitama equivalent of programming.&lt;/p&gt;


&lt;/table&gt;&lt;/div&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>backend</category>
      <category>sql</category>
    </item>
    <item>
      <title>AI-generated Art and AI-generated code are treated differently</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Sat, 13 Sep 2025 20:29:58 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/ai-generated-art-and-ai-generated-code-are-treated-differently-agh</link>
      <guid>https://dev.to/zeedu_dev/ai-generated-art-and-ai-generated-code-are-treated-differently-agh</guid>
      <description>&lt;p&gt;Today, while &lt;em&gt;doom scrolling&lt;/em&gt; on Zuckerberg’s social network, the algorithm recommended me an image of &lt;strong&gt;Sakura Card Captors&lt;/strong&gt; in the style of the Spanish painter &lt;strong&gt;Remedios Varo&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2Fxgly24vcjhdzd2nr6pmu.webp" 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.amazonaws.com%2Fuploads%2Farticles%2Fxgly24vcjhdzd2nr6pmu.webp" alt="Sakura Card Captors image in the style of Remedios Varo created with AI" width="700" height="1050"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I checked the comments—I don’t even know what I gain from doing that—I noticed that the most &lt;em&gt;liked&lt;/em&gt; ones expressed strong disdain for artificial intelligence. A normal behavior if you’re in the &lt;a href="https://coffeebytes.dev/en/artificial-intelligence/ai-is-overhyped-when-will-the-bubble-burst/" rel="noopener noreferrer"&gt;middle of AI overhype&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Artists Hate AI, Devs Are Forced to Accept It
&lt;/h2&gt;

&lt;p&gt;There’s a strong consensus among artists regarding AI.&lt;/p&gt;

&lt;p&gt;Movements like #NOAI or &lt;a href="https://www.forbes.com/sites/lesliekatz/2024/07/17/human-intelligence-art-movement-takes-defiant-stand-against-ai/" rel="noopener noreferrer"&gt;Made with human intelligence&lt;/a&gt; have made &lt;a href="https://notbyai.fyi/es/" rel="noopener noreferrer"&gt;their rejection of this technology very clear&lt;/a&gt; . The contempt seems to stem from how it was trained—using artists’ work without their permission, accused of plagiarizing something as personal as a style—and the fear of being replaced by this technology, which affects them economically.&lt;/p&gt;

&lt;p&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.amazonaws.com%2Fuploads%2Farticles%2Feuqmxaiqsjuh4c1r62se.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.amazonaws.com%2Fuploads%2Farticles%2Feuqmxaiqsjuh4c1r62se.jpg" alt="NO AI movement" width="600" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the other hand, there’s been a strong trend (whether organic or orchestrated, I don’t know) in the software world, to adopt AI as just another tool to increase the quality and quantity of code produced. Even when there’s resistance, it’s more focused on technical aspects and potential flaws rather than the ethical concerns of its training process.&lt;/p&gt;

&lt;p&gt;This contrast fascinates me.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Thoughts on Why AI-Generated Code and AI-Generated Art Are Treated Differently
&lt;/h2&gt;

&lt;p&gt;It’s not about one being right or wrong—it just really catches my attention.&lt;/p&gt;

&lt;p&gt;Perhaps it’s because art is a direct form of visual and emotional communication. The fact that it’s generated by a machine might be perceived as an &lt;em&gt;inferior&lt;/em&gt; form of expression or a threat to something considered purely human.&lt;/p&gt;

&lt;p&gt;In contrast, code, by its functional and abstract nature, lacks that emotional weight, and the fact that a computer program generates it goes completely unnoticed.&lt;/p&gt;

&lt;p&gt;Humans evolved using sight as a survival tool, so anything perceived visually will have a more significant impact. Code, as an abstraction of logic, is very recent and wasn’t part of our species’ evolutionary pressures or material reality. Plus, given its nature, it’s far less visual. Remember, code isn’t the final product—it’s an abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Is Pragmatic, Art Isn’t
&lt;/h3&gt;

&lt;p&gt;Code was created to abstract, in a language closer to human speech, a series of instructions for a computer, with a well-defined and deterministic purpose.&lt;/p&gt;

&lt;p&gt;But art tells a different story.&lt;/p&gt;

&lt;p&gt;Oscar Wilde would say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“All art is quite useless.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;His statement doesn’t mean art is useless but that it exists for beauty and expression, not survival or moral instruction.&lt;/p&gt;

&lt;p&gt;If there’s any truth to that, art is far more flexible than code and logical-mathematical systems.&lt;/p&gt;

&lt;p&gt;I believe it’s precisely this flexibility that allows AI, combined with randomness, to function so differently for art and code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of Randomness in Generative Models and LLMs
&lt;/h2&gt;

&lt;p&gt;AI can abstract patterns from its training dataset. Once it identifies those patterns or archetypes, it uses randomness to generate results that “fit” within them.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-Generated Art
&lt;/h3&gt;

&lt;p&gt;An artist gathers stimuli from other artists, personal experiences, books, music… and “mixes” them to create something new. The result is something original that &lt;strong&gt;retains certain references or characteristics of the works that inspired it, no matter how subtle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All of this happens consciously, whereas &lt;a href="https://coffeebytes.dev/en/artificial-intelligence/chat-gpt-searles-chinese-room-and-consciousness/" rel="noopener noreferrer"&gt;AI does it unconsciously—or at least that’s what Searle would suggest&lt;/a&gt; .&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-Generated Code
&lt;/h3&gt;

&lt;p&gt;In the case of AI-generated code, the process is practically the same. The AI detects patterns in numerous code samples and then generates new code, different but based on the patterns the LLMs have “learned.”&lt;/p&gt;

&lt;p&gt;In both cases, randomness allows for something new—not an exact copy of the originals it was trained on (as long as it doesn’t overfit).&lt;/p&gt;

&lt;h2&gt;
  
  
  Randomness Impacts the Differences Between AI-Generated Art and Code
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, art is far more subjective and variable than coding. There are millions (or more) ways to present a concept or image, which aligns perfectly with how LLMs or generative models work.&lt;/p&gt;

&lt;p&gt;When it comes to writing code, however, the number of ways to achieve the same thing is much more limited. Writing a loop that counts from 0 to 100 in the same programming language might have a dozen solutions.&lt;/p&gt;

&lt;p&gt;This number starkly contrasts with the potential ways to paint a concept, which could be practically limitless.&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.amazonaws.com%2Fuploads%2Farticles%2Ffcs2py3qt0e5cy2ikt61.webp" 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.amazonaws.com%2Fuploads%2Farticles%2Ffcs2py3qt0e5cy2ikt61.webp" alt="An emotional photograph in the style of a Fujifilm disposable camera (like 'Utsurundesu') showing a medium shot of a Japanese woman in her 20s, crying at a wedding ceremony. She stands among guests, wearing a soft pastel-colored dress. Her makeup is slightly smudged by tears. The scene is imbued with deep nostalgia and melancholy. Soft, dim lighting casts a gentle glow on her dark hair. The focus is on her expressive face, and in the reflection of her eyes, a bride and groom can be seen standing together. The background shows a beautiful wedding venue with warm lighting. The image features pronounced film grain, slightly muted tones, and cinematic blur, amplifying the emotional intensity of the moment from a wider perspective. The composition evokes longing and fleeting memories, characteristic of heartfelt disposable camera photography. The background is softly blurred." width="600" height="797"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With code, it’s slightly different. Obviously, as the codebase grows, the number of possible solutions increases too. But here’s a crucial difference: unlike art, not all solutions are equally valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Differences Between AI-Generated Art and Code
&lt;/h2&gt;

&lt;p&gt;Let me expand on this. The subjective nature of art means all representations of the same concept are valid—a Picasso-style apple is just as valid as one painted by Velázquez or Carrington.&lt;/p&gt;

&lt;p&gt;This isn’t the case with software. A variation of any complex program (a web crawler, a database, etc.) will behave differently in different scenarios, and there will be a superior or inferior version based on the code’s requirements.&lt;/p&gt;

&lt;p&gt;These differences can be measured quantitatively: execution time, memory usage, scalability, &lt;a href="https://coffeebytes.dev/en/python/how-to-measure-requests-per-second-with-locust-in-python/" rel="noopener noreferrer"&gt;requests per second&lt;/a&gt; etc., leaving us with some solutions better than others.&lt;/p&gt;

&lt;p&gt;Given this, AI will “get it right” less often and require more iterations when generating code. However, when creating art, the user will likely consider the result valid after just a few iterations.&lt;/p&gt;

&lt;p&gt;This makes me think that, from my perspective, the non-deterministic nature of AI works better for creating art than for creating code.&lt;/p&gt;

&lt;p&gt;Now, there’s another aspect I want to touch on regarding the differences between AI-generated code and AI-generated art.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contrast in Perception Between AI-Generated Code and AI-Generated Images
&lt;/h2&gt;

&lt;p&gt;The final product of AI-generated art is the same product the user perceives. In contrast, the code AI creates doesn’t interact with the end user.&lt;/p&gt;

&lt;p&gt;There’s no public backlash or protests over AI-generated code because people don’t see it. Code, in itself, doesn’t evoke passion or anger—unlike &lt;a href="https://coffeebytes.dev/en/artificial-intelligence/devin-ai-will-this-ai-replace-programmers/" rel="noopener noreferrer"&gt;when Devin AI promised to replace programmers&lt;/a&gt; . Maybe it’s because the visual stimulus of art is far more intense than a wall of text expressing relationships between abstract entities.&lt;/p&gt;

&lt;p&gt;For proof, just look at the response to tools like &lt;a href="https://coffeebytes.dev/en/artificial-intelligence/my-bolt-vs-lovable-vs-v0-vercel-comparison/" rel="noopener noreferrer"&gt;Bolt, Lovable, or V0&lt;/a&gt; , which were trained using developers’ code—also without their permission.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-Generated Websites
&lt;/h3&gt;

&lt;p&gt;But what about websites? I’d argue that even if we observe the crystallization of code into pixels on a webpage, the reaction is entirely different from what we’d experience with art—despite both being perceived visually.&lt;/p&gt;

&lt;p&gt;An AI-generated website (and perhaps the code itself) is completely indistinguishable from one made by a human. Plus, a website isn’t usually perceived as the result of human expression (especially in today’s internet).&lt;/p&gt;

&lt;p&gt;But with art, it’s much easier to tell—hands with too many fingers, logical inconsistencies, even that default, generic style that’s become so characteristic. Or the now-popular Ghibli style that’s turned into a commodity.&lt;/p&gt;

&lt;p&gt;AI-generated art stands out whereas AI-generated code is invisible.&lt;/p&gt;

</description>
      <category>opinion</category>
      <category>ai</category>
      <category>art</category>
    </item>
    <item>
      <title>I Built a Bloom Filter Data Structure Simulator</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Fri, 12 Sep 2025 15:49:12 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-5460</link>
      <guid>https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-5460</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-2o3l" class="crayons-story__hidden-navigation-link"&gt;I Built a Bloom Filter Data Structure Simulator&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/zeedu_dev" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" alt="zeedu_dev profile" class="crayons-avatar__image" width="800" height="1066"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/zeedu_dev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eduardo Zepeda
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eduardo Zepeda
                
              
              &lt;div id="story-author-preview-content-2822853" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/zeedu_dev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" class="crayons-avatar__image" alt="" width="800" height="1066"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eduardo Zepeda&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-2o3l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 11 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-2o3l" id="article-link-2822853"&gt;
          I Built a Bloom Filter Data Structure Simulator
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/backend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;backend&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-2o3l" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-2o3l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>database</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>I Built a Bloom Filter Data Structure Simulator</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Fri, 12 Sep 2025 15:47:15 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-2o3l</link>
      <guid>https://dev.to/zeedu_dev/i-built-a-bloom-filter-data-structure-simulator-2o3l</guid>
      <description>&lt;p&gt;Suppose you want to check whether a piece of data belongs to a larger set or not. Let’s say you’re Google and you want to check if certain url has been marked as spam, the dumb approach would be to iterate over every url marked as spam to see if you can find it. Sure, maybe you think, “I’ll save the spammy sites in a hashmap”, but then, a hashmap of the gazillion sites that exists in the internet? There must be a way that uses less space.&lt;/p&gt;

&lt;p&gt;Even if you index those urls you still will have a not so good &lt;a href="https://coffeebytes.dev/en/linux/the-big-o-notation-for-algorithm-analysis/" rel="noopener noreferrer"&gt;Big O&lt;/a&gt; performance, maybe O(log n) or O.&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.amazonaws.com%2Fuploads%2Farticles%2Fniyrtu3xy0wa31e4xp5j.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.amazonaws.com%2Fuploads%2Farticles%2Fniyrtu3xy0wa31e4xp5j.png" alt="Bloom Filter" width="733" height="840"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sorry, this is an image, for the real simulator you need to visit my blog since I can't inject Javascript here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://coffeebytes.dev/en/databases/i-built-a-bloom-filter-data-structure-simulator/" rel="noopener noreferrer"&gt;Bloom filter simulator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is an interesting data structure that’s probabilistic, it won’t have a 100% chance of returning what you wanna know, but the tradeoffs are interesting enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok but what’s a Bloom Filter?
&lt;/h2&gt;

&lt;p&gt;A Bloom filter is a data structure that helps you check if an item might be in a set. It gives &lt;del&gt;blazingly&lt;/del&gt; fast answers with very little memory. The trade-off is that sometimes it can say an item is present when it is not.&lt;/p&gt;

&lt;p&gt;This data structure can produce &lt;em&gt;false positives&lt;/em&gt;. But the good news are that there are not &lt;em&gt;false negatives&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Because of this, Bloom filters shine when you care more about speed and space than absolute accuracy.&lt;/p&gt;

&lt;p&gt;Check how they work, for simplicity we’re only using one word as the full set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where can I use bloom filters?
&lt;/h3&gt;

&lt;p&gt;Bloom filters show up in those systems where quick lookups matter (No, your Tinder for pets probably doesn’t apply):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Databases&lt;/strong&gt; : To check if a key might be in a table before doing a costly disk read. Of course we’re taking about millions of records, not tiny databases. (Cassandra, HBase, and Redis)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web caches&lt;/strong&gt; : To test if a page or object might be cached.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed systems&lt;/strong&gt; : To cut down on network calls when asking if a node has certain data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; : To quickly filter known bad URLs or email addresses, like in the first example I gave you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content recommendation systems&lt;/strong&gt; : Where you want to avoid recommending content already consumed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dwrscezd2/image/upload/v1757107240/coffee-bytes/bloom-filter-usage_fvyq87.png" rel="noopener noreferrer"&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.amazonaws.com%2Fuploads%2Farticles%2F7jsped9c9lju5ppiz5qx.png" alt="Bloom filter usage by reddit user" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bloom filters help reduce time and resource usage at scale, if you’re ok with some false positives.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does a Bloom Filter Work Internally?
&lt;/h2&gt;

&lt;p&gt;A Bloom filter uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bit array (all values start at 0).&lt;/li&gt;
&lt;li&gt;A set of hash functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you add an item, the filter runs it through each hash function. Each function gives you an index in the array. You set the bits at those positions to 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  There are no deletions in classic bloom filters
&lt;/h3&gt;

&lt;p&gt;Since you are not storing real data but the “pattern” produced by the hash functions, you have no way to tell which combination of data and hash function produced it, hence you cannot “delete” the item.&lt;/p&gt;

&lt;h3&gt;
  
  
  You cannot retrieve members from the set
&lt;/h3&gt;

&lt;p&gt;Once you construct the bloom filter, you cannot know which items produced that patter from the filter itself, you can only know if something might belong or definitely doesn’t belong to the set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bloom filter collisions
&lt;/h3&gt;

&lt;p&gt;However when you start adding more and more elements the chances of getting a collision (false positive) increase.&lt;/p&gt;

&lt;p&gt;You can always add more hash functions to diminish the collitions but then the complexity and memory required increase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking if an item exists
&lt;/h3&gt;

&lt;p&gt;When you check an item, the filter does the same. If all the positions are set to 1, the item &lt;em&gt;might&lt;/em&gt; be in the set (this is why it’s a probabilistic data structure). If any position is 0, the item is &lt;em&gt;definitely&lt;/em&gt; not in the set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Bloom filter Flow
&lt;/h2&gt;

&lt;p&gt;Here’s a simple outline that you can blatanly use to inspire you in the dark arts of bloom filters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Decide the size of the bit array and initialize all elements to 0.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pick a few independent hash functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To add an item:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To check an item:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Bloom filter pseudocode
&lt;/h3&gt;

&lt;p&gt;Using pseudocode (I know, I should have used Javascript instead), it would look like this.&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="n"&gt;initialize&lt;/span&gt; &lt;span class="n"&gt;bit_array&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;all&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;choose&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="n"&gt;functions&lt;/span&gt;

&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;to&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;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hash_i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
        &lt;span class="n"&gt;bit_array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&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;function&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;to&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;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hash_i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bit_array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&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;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not present&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;might be present&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bloom filter flow chart
&lt;/h3&gt;

&lt;p&gt;And if you are a fan of flow charts it would look like this&lt;/p&gt;

&lt;p&gt;flowchart TD A[Start] --&amp;gt; B[Hash item with k functions] B --&amp;gt; C[Get indexes] C --&amp;gt; D{Add or Check?} D --&amp;gt;|Add| E[Set bits at indexes to 1] D --&amp;gt;|Check| F[Are all bits = 1?] F --&amp;gt;|Yes| G[Might be present] F --&amp;gt;|No| H[Not present]&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not use hashmaps instead?
&lt;/h2&gt;

&lt;p&gt;Well you could use some hashmaps approaches like buckets or &lt;a href="https://coffeebytes.dev/en/go/golang-maps-or-dictionaries/" rel="noopener noreferrer"&gt;Swiss tables&lt;/a&gt; but consider that you have to deal with collisions, also you can use many less intensive hash functions in a bloom filter whereas in hashmaps you need a strong hash function to avoid collisions which can consume some resources.&lt;/p&gt;

&lt;p&gt;Also in a hashmap you store all the information as key-value pairs, whereas in a bloom filter you only store the results of the hash function, which can save a lot of space in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you shouldn’t use them?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If you need to delete items, standard bloom filters only support inserting new data.&lt;/li&gt;
&lt;li&gt;If data is small, there is no sense in using a probabilistic approach with false positives, use a hashmap instead.&lt;/li&gt;
&lt;li&gt;As I told you before, the more data you add, the chances of getting false positives increases, so don’t use it for data that grows a lot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that’s all, I wrote this entry because I read about this data structure in the &lt;a href="https://amzn.to/41rodp3" rel="noopener noreferrer"&gt;System Design Interview Book&lt;/a&gt;, found interesting the fact that there is a probabilistic data structure out there, when must of the times you don’t deal with probability when you deal with data structures, quite the contrary, you look for determinism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some bloom filter libraries
&lt;/h2&gt;

&lt;p&gt;Chances are there are already community-maintained libraries, so don’t worry about reinventing the wheel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bits-and-blooms/bloom" rel="noopener noreferrer"&gt;bits and blooms (go)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KenanHanke/rbloom" rel="noopener noreferrer"&gt;rbloom(python)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/bloom-filters" rel="noopener noreferrer"&gt;Bloom filters(javascript)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>I wrote a guide about all the basic aspects of technical SEO that are often overlooked by web developers.</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Tue, 15 Apr 2025 19:38:31 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/i-wrote-a-guide-about-all-the-basic-aspects-of-technical-seo-that-are-often-overlooked-by-web-5fcp</link>
      <guid>https://dev.to/zeedu_dev/i-wrote-a-guide-about-all-the-basic-aspects-of-technical-seo-that-are-often-overlooked-by-web-5fcp</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/zeedu_dev/a-technical-seo-basics-checklist-made-for-web-developers-1a70" class="crayons-story__hidden-navigation-link"&gt;A Technical SEO Basics Checklist Made For Web Developers&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/zeedu_dev" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" alt="zeedu_dev profile" class="crayons-avatar__image" width="800" height="1066"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/zeedu_dev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eduardo Zepeda
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eduardo Zepeda
                
              
              &lt;div id="story-author-preview-content-2407982" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/zeedu_dev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F503443%2Fc2684892-d4bd-4a72-9bd2-29adb854e9c3.jpg" class="crayons-avatar__image" alt="" width="800" height="1066"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eduardo Zepeda&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/zeedu_dev/a-technical-seo-basics-checklist-made-for-web-developers-1a70" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 15 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/zeedu_dev/a-technical-seo-basics-checklist-made-for-web-developers-1a70" id="article-link-2407982"&gt;
          A Technical SEO Basics Checklist Made For Web Developers
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/seo"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;seo&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opinions"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opinions&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/zeedu_dev/a-technical-seo-basics-checklist-made-for-web-developers-1a70" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/zeedu_dev/a-technical-seo-basics-checklist-made-for-web-developers-1a70#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>seo</category>
      <category>opinions</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A Technical SEO Basics Checklist Made For Web Developers</title>
      <dc:creator>Eduardo Zepeda</dc:creator>
      <pubDate>Mon, 14 Apr 2025 06:02:21 +0000</pubDate>
      <link>https://dev.to/zeedu_dev/a-technical-seo-basics-checklist-made-for-web-developers-1a70</link>
      <guid>https://dev.to/zeedu_dev/a-technical-seo-basics-checklist-made-for-web-developers-1a70</guid>
      <description>&lt;p&gt;Previously I told you how &lt;a href="https://coffeebytes.dev/en/my-technical-seo-mistakes-when-i-migrated-my-site-from-wordpress/" rel="noopener noreferrer"&gt;I made many mistakes in SEO&lt;/a&gt; when I migrated my website from Wordpress to Hugo, after that I started watching a lot of videos about SEO, especially Romuald’s videos (He is one of the biggest SEO influencers in Spanish speaking countries), I’ve also read &lt;a href="https://amzn.to/42uNZrv" rel="noopener noreferrer"&gt;The art of SEO&lt;/a&gt; and I tried to summarize everything I learned from him and the book in a short post, just for you, before &lt;a href="https://coffeebytes.dev/en/the-rise-and-fall-of-the-ai-bubble/" rel="noopener noreferrer"&gt;AI bubble&lt;/a&gt; explodes, taking SEO with 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.amazonaws.com%2Fuploads%2Farticles%2F53lscpn9od0y3s43z0sk.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.amazonaws.com%2Fuploads%2Farticles%2F53lscpn9od0y3s43z0sk.jpg" alt="Romuald Fons Video thumbnail" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;My totally reliable source: Romuald Fons&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I decided to focus on technical SEO because it’s the one that I understand, it’s less subjective and I’m more familiar with it.&lt;/p&gt;

&lt;p&gt;I have examined many websites and I have noticed that many professional web developers leave Technical SEO basics out from their developments, it is certainly true that it is quite an extensive topic and it is far from what their expertise area, in my opinion, doing an average SEO is better than ignoring the topic entirely.&lt;/p&gt;

&lt;p&gt;Simplifying SEO criminally, it can be divided in two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Technical SEO&lt;/strong&gt; : all those objective, measurable aspects, like the presence of a sitemap, meta tags, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content SEO&lt;/strong&gt; : it’s a bit more subjective, it’s about what words to use, what sites to connect with&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  SEO is a black box and an art rather than science
&lt;/h2&gt;

&lt;p&gt;Nobody knows exactly how the search engine algorithm works, so getting to the number one position in Google, or other engines, for a certain search keyword, is more of an art than a science.&lt;/p&gt;

&lt;p&gt;Even if you manage to vaguely understand how the algorithm works enough to manipulate it, you will have to face the fact that &lt;strong&gt;the algorithm is a shape-shifting entity&lt;/strong&gt; and what worked perfectly yesterday may not work today.&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.amazonaws.com%2Fuploads%2Farticles%2F65o90gz62z6zb4ptw9s7.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.amazonaws.com%2Fuploads%2Farticles%2F65o90gz62z6zb4ptw9s7.jpg" alt="google’s algorithm update meme" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite the changes in the SEO world, the current SEO approach is still heavily tied to keywords; short phrases that you include in your website to tell search engines what your website content is about, so it can show it to the right users.&lt;/p&gt;

&lt;p&gt;However, lately, Google has said that its algorithm is so sophisticated that it manages to evaluate how well a website responds to &lt;em&gt;user intent&lt;/em&gt; and invites its users &lt;a href="https://about.google/company-info/philosophy/" rel="noopener noreferrer"&gt;to focus on resolving user intent&lt;/a&gt; rather than keywords. Although, paradoxically, their advertising services are still strongly keyword oriented.&lt;/p&gt;
&lt;h2&gt;
  
  
  Technical SEO in web development
&lt;/h2&gt;

&lt;p&gt;Technical SEO consists of a series of requirements that a website must meet to appear friendly to search engines, so that they index you in high positions and when a user searches in serach engines, you appear in the top positions.&lt;/p&gt;

&lt;p&gt;This topic is very broad, but I hope to summarize in a way that you have a general idea and do not get lost in the sea of information that exists.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sitemap.xml is the most important one of technical SEO basics
&lt;/h3&gt;

&lt;p&gt;This is probably the most important element of technical SEO basics. A sitemap will tell search engines what pages your application has.&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.amazonaws.com%2Fuploads%2Farticles%2Ft0y61nsb5q6awdgectzs.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.amazonaws.com%2Fuploads%2Farticles%2Ft0y61nsb5q6awdgectzs.png" alt="Sitemap diagram showing available urls" width="736" height="316"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Sitemap diagram showing available urls&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In many cases, a sitemap can be generated dynamically, some frameworks even have tools that allow you to do it in a few lines, such as &lt;a href="https://coffeebytes.dev/en/dynamic-sitemap-with-django/" rel="noopener noreferrer"&gt;Django (using its sitemap class)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once created, you must tell the search engines where the url of your sitemap is located directly from your administration panel. If you don’t explicitly indicate it, they will search in the most common urls or, luckily in the &lt;em&gt;robots.txt&lt;/em&gt; file.&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.amazonaws.com%2Fuploads%2Farticles%2Ftcugwc4z8um9ixx5qufo.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.amazonaws.com%2Fuploads%2Farticles%2Ftcugwc4z8um9ixx5qufo.png" alt="Screenshot of Google’s search console sitemap section" width="800" height="362"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot of Google's search console sitemap section&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Robots.txt
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;robots.txt&lt;/em&gt; file will guide search engines and crawlers on which URLs to ignore and which to inspect. In addition this file should include the location of the sitemap.&lt;/p&gt;

&lt;p&gt;The URL for this file is always expected to be &lt;em&gt;/robots.txt&lt;/em&gt;, so stick to this convention.&lt;/p&gt;

&lt;p&gt;Here is an example of a &lt;em&gt;robots.txt&lt;/em&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-agent: *
Disallow: /*/tags/
Disallow: /*/categories/
Disallow: /*/search/

Sitemap: https://example.org/sitemap.xml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  A robots.txt file is not for blocking crawlers or bots.
&lt;/h4&gt;

&lt;p&gt;There is some confusion about this, and it’s pretty obvious but I’ll mention it anyway: &lt;strong&gt;crawlers can completely ignore the instructions in your robots.txt file&lt;/strong&gt; , so you should not see it as a protection mechanism for your website, if a crawler wants to, it will ignore the instructions contained there.&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.amazonaws.com%2Fuploads%2Farticles%2Fek1tbfhfl5eoeym7deji.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.amazonaws.com%2Fuploads%2Farticles%2Fek1tbfhfl5eoeym7deji.jpg" alt="Robots.txt meme" width="750" height="737"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Robots.txt won't protect you from crawlers&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If any influencer tells you otherwise, they’re lying through his teeth. Furthermore, I dare them to stop a crawler just by using a &lt;em&gt;robots.txt&lt;/em&gt; file.&lt;/p&gt;
&lt;h4&gt;
  
  
  Robots disallow everything
&lt;/h4&gt;

&lt;p&gt;If you want to tell crawlers to ignore absolutely everything on your site, you could use something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-agent: *
Disallow: /

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The counterpart of robots.txt: humans.txt
&lt;/h4&gt;

&lt;p&gt;As a curious fact, there is an initiative to popularize the idea of adding the counterpart of the &lt;em&gt;robots.txt&lt;/em&gt; file, a &lt;a href="https://humanstxt.org" rel="noopener noreferrer"&gt;&lt;em&gt;humans.txt&lt;/em&gt; file&lt;/a&gt; and show the human part behind a web site, the soul in the machine or in the console (the “Ghost in the shell”?), I think it is a very nice idea to know how are the humans behind a web site. Unfortunately this project has little diffusion to date.&lt;/p&gt;

&lt;h3&gt;
  
  
  The often ignored Schema markup
&lt;/h3&gt;

&lt;p&gt;Often present as a &lt;em&gt;ld+json&lt;/em&gt; type &lt;em&gt;script&lt;/em&gt; tag (although there are other valid formats), which can be found anywhere in your HTML document, it tells search engines what elements your web site has and how they relate to each other.&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.amazonaws.com%2Fuploads%2Farticles%2Fdej7zydt2uhayibrmauz.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.amazonaws.com%2Fuploads%2Farticles%2Fdej7zydt2uhayibrmauz.png" alt="Schema Markup diagram" width="800" height="264"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A Schema will provide information about the contents of a webpage&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The different types of Schema are very complex and it is a whole topic to discuss, since they vary greatly according to the type of website.&lt;/p&gt;

&lt;p&gt;A website that sells electronics will have a completely different set of Schema properties than a restaurant website, or a web application.&lt;/p&gt;

&lt;p&gt;If you are completely lost, here is a prompt you can use for guidance, just write it to ChatGPT, DeepSeek or any other competent LLM:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are an SEO expert, create the content of a ld+json tag, based on schema.org features, for a web page whose main topic is “x”, the content of the page consists of “y”, you can use placeholders for the variables using the following format: “z”, please.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you get the result corroborate it with the official documentation or with your &lt;a href="https://validator.schema.org/" rel="noopener noreferrer"&gt;schema markup validation tool&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Presence of Meta tags
&lt;/h3&gt;

&lt;p&gt;The metatags that go in the &lt;em&gt;head&lt;/em&gt; tag of your HTML are metadata about the content that can be used to help search engines understand your site better.&lt;/p&gt;

&lt;p&gt;Within the metatags are especially important the Open Graph, as they are the standard for social networks to obtain information from your web pages, these meta tags are the ones that make one of your links look like this when you share it on social networks.&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.amazonaws.com%2Fuploads%2Farticles%2F09zqnikosfcmbvpbx9zc.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.amazonaws.com%2Fuploads%2Farticles%2F09zqnikosfcmbvpbx9zc.png" alt="Social networks use metatags to display relevant information on a page." width="527" height="354"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Social networks use metatags to display relevant information on a page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are some &lt;a href="https://www.seoptimer.com/meta-tag-generator" rel="noopener noreferrer"&gt;meta tag generators&lt;/a&gt; that you can use to generate all the boilerplate, or simple ask chatGPT.&lt;/p&gt;
&lt;h3&gt;
  
  
  Good performance is generally overrated in technical SEO basics
&lt;/h3&gt;

&lt;p&gt;A website must provide a good user experience and be responsive, it matters, yes, but not as much as you think.&lt;/p&gt;

&lt;p&gt;Content plays a much more important role than the speed of your website, it is common to see websites that are poorly optimized but with a good ranking in search engines, but don’t blindly trust me, double-check it, check google’s first page results using the following tool.&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.amazonaws.com%2Fuploads%2Farticles%2Fs4a1s8kt1nxwyx9ci1v3.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.amazonaws.com%2Fuploads%2Farticles%2Fs4a1s8kt1nxwyx9ci1v3.png" alt="Lighthouse score for my portfolio page" width="800" height="619"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Lighthouse score for my portfolio page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://chromewebstore.google.com/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; are very useful to measure the performance of a website and also tell you how to improve it.&lt;/p&gt;

&lt;p&gt;IMO this is one of the most overrated technical SEO basics’ aspect.&lt;/p&gt;
&lt;h3&gt;
  
  
  The most overlooked aspect of a technical SEO checklist: Well-designed website architecture
&lt;/h3&gt;

&lt;p&gt;Make sure the website has a structure that allows search engines to “understand” it. What do I mean? I mean that the website is organized in a logical and hierarchical way that is understandable.&lt;/p&gt;

&lt;p&gt;For example something similar to this:&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.amazonaws.com%2Fuploads%2Farticles%2Fk9fwpao2e4vquk57nkux.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.amazonaws.com%2Fuploads%2Farticles%2Fk9fwpao2e4vquk57nkux.png" alt="Semantic structure of a website" width="800" height="367"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Semantic structure of a website&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Does the URL structure matter in SEO?
&lt;/h4&gt;

&lt;p&gt;Yes, and a lot, you can use the urls to give your website the structure you think is correct so that it is coherent with the architecture you plan, this makes it easier for search engines to “understand” your website.&lt;/p&gt;

&lt;p&gt;I’ve talked about this on my post &lt;a href="https://coffeebytes.dev/en/rest-api-best-practices-and-design/" rel="noopener noreferrer"&gt;REST API: Best practices and design&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the following URLs, notice how there is no way to know if Nawapol is a movie, or a director or a documentary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/session-9/
/50-first-dates/
/nawapol-thamrongrattanarit/
/salt-of-the-earth/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would be best to provide them with a consistent and more explicit structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/movies/psychological-horror/session-9/
/movies/comedy/one-hundred-first-dates/
/documentaries/photography/salt-of-the-earth/
/directors/nawapol-thamrongrattanarit/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  You’re probably not using the correct HTML tags
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Headings and SEO
&lt;/h4&gt;

&lt;p&gt;Headings are the most important tags of your content, as they tell search engines how it is organized.&lt;/p&gt;

&lt;p&gt;Make sure you use only one h1 tag, and hierarchical h2 tags up to h6, using them to give a hierarchical structure to your website.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML allows you to be very expressive in SEO.
&lt;/h4&gt;

&lt;p&gt;Make sure you use only one h1 tag, and hierarchical h2 tags up to h6, using them to give a hierarchical structure to your website. There is much more to it than divs, anchors, img tags and video tags. HTML provides tags to help search engines and devices to better understand the content of a web page. Don’t just stick with those elements and research the rest of the HTML tags, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;article&lt;/em&gt;: Self-contained content that could be distributed independently.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;section&lt;/em&gt;: Thematic grouping of content, typically with a heading.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;nav&lt;/em&gt;: Navigation links for the document or site.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;footer&lt;/em&gt;: Footer for a section or page, often containing metadata.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;datetime&lt;/em&gt;: Machine-readable date/time (used within &lt;em&gt;&amp;lt;!-- raw HTML omitted --&amp;gt;&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;aside&lt;/em&gt;: Content tangentially related to the surrounding content.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;header&lt;/em&gt;: Introductory content or navigational aids for a section/page.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;progress&lt;/em&gt;: Displays the completion progress of a task.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;meter&lt;/em&gt;: Represents a scalar measurement within a known range.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;cite&lt;/em&gt;: Citation or reference to a creative work (e.g., book, article).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;q&lt;/em&gt;: Short inline quotation (browser usually adds quotes).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;pre&lt;/em&gt;: Preformatted text, preserving whitespace and line breaks.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;kbd&lt;/em&gt;: Keyboard input, indicating user-entered keys.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;samp&lt;/em&gt;: Sample output from a program or computing system.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;dfn&lt;/em&gt;: Defining instance of a term (often italicized).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;output&lt;/em&gt;: Represents the result of a calculation or user action.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;abbr&lt;/em&gt;: Abbreviation or acronym, optionally with a title for expansion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Presence of internal links
&lt;/h3&gt;

&lt;p&gt;They help search engines understand how your pages are related to each other. Make sure your links are valid and do not return 404 errors, and that the text you link to is related to the content of the linked page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ &amp;lt;a href="/how-to-code-clean-code"&amp;gt;How to code clean code&amp;lt;/a&amp;gt;
✅ &amp;lt;a href="/how-to-code-clean-code"&amp;gt;Learn how to code clean code&amp;lt;/a&amp;gt;
❌ &amp;lt;a href="/how-to-code-clean-code"&amp;gt;Click here to read my new entry&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Presence of external links
&lt;/h3&gt;

&lt;p&gt;External links, consider it as a vote in favor towards other websites to indicate to search engines that the content you link to is important, research thoroughly about the attributes &lt;em&gt;nofollow&lt;/em&gt;, &lt;em&gt;follow&lt;/em&gt;, &lt;em&gt;ugc&lt;/em&gt; and &lt;em&gt;sponsored&lt;/em&gt; in the anchor tags and use them appropriately according to your intentions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;follow&lt;/em&gt;, the default value, tells search engines that the site they link to should be positively valued.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;nofollow&lt;/em&gt;, search engines should not follow this link, ideal for advertising links, affiliate links or websites you want to link to but do not want them to be related to your content.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;ugc&lt;/em&gt;, acronym for “User generated content”, ideal for social networks.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;sponsored&lt;/em&gt;, sponsored link, usually advertising or affiliate links.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="https://example.org/" rel="nofollow"&amp;gt;The example website&amp;lt;/a&amp;gt;
&amp;lt;a href="https://user-website.com" rel="ugc"&amp;gt;Just check my website&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Flesch Kincaid test
&lt;/h3&gt;

&lt;p&gt;is a test based on the level of education required by a hypothetical reader to understand the content of your website, &lt;del&gt;unlike what your inflated ego thinks&lt;/del&gt; , the higher the score the easier it will be to understand, which is better because it will be available to more readers.&lt;/p&gt;

&lt;p&gt;Having said that, I would dare to say that the exact value does not matter so much, but that your text is easy to read and the reader flows through it.&lt;/p&gt;

&lt;p&gt;Keep in mind that there are some plugins, like Yoast SEO if you use Wordpress, that take care of measuring it or you can program it yourself.&lt;/p&gt;

&lt;p&gt;I have left some aspects that I do not consider so important, but that maybe I will add later, the important thing here is that you understand that it is not just to set up a website and that’s it, but you have to polish it so that users can find it and use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTPS instead of HTTP
&lt;/h3&gt;

&lt;p&gt;It is quite rare to find modern websites that do not use HTTPS, especially when it is the hosting platforms that are responsible for configuring the servers automatically. In any case, make sure that your website uses HTTPS instead of just HTTP, as search engines favor secure connections.&lt;/p&gt;

&lt;p&gt;And that’s all for now, I will be adding more information to the article if I consider it pertinent as the days go by.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>opinions</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
