<?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: Fu'ad Husnan</title>
    <description>The latest articles on DEV Community by Fu'ad Husnan (@fuadhusnan_f44f3e13).</description>
    <link>https://dev.to/fuadhusnan_f44f3e13</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3914266%2F094c796a-3db7-45ac-abff-19a1b85ff8a7.png</url>
      <title>DEV Community: Fu'ad Husnan</title>
      <link>https://dev.to/fuadhusnan_f44f3e13</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fuadhusnan_f44f3e13"/>
    <language>en</language>
    <item>
      <title>A Smarter Workflow for Debugging and Problem-Solving</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Sun, 26 Jul 2026 04:54:23 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/a-smarter-workflow-for-debugging-and-problem-solving-l3m</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/a-smarter-workflow-for-debugging-and-problem-solving-l3m</guid>
      <description>&lt;p&gt;There is a moment every developer knows well: you stare at a function that worked perfectly yesterday, and now it doesn't. The test suite is red, the logs say nothing useful, and you've been looking at the same twenty lines for forty minutes. The problem isn't that &lt;a href="https://dte.telkomuniversity.ac.id/debugging-dan-penanganan-error-dalam-kode-program/" rel="noopener noreferrer"&gt;debugging&lt;/a&gt; is hard. The problem is that most developers never build a real debugging workflow — they improvise, every single time.&lt;/p&gt;

&lt;p&gt;A structured debugging workflow changes that. It turns a frustrating, open-ended process into a repeatable sequence of decisions, and it gets you to the root cause faster than instinct alone ever will.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop Guessing, Start Observing
&lt;/h2&gt;

&lt;p&gt;The most common debugging mistake is jumping straight to a fix before you understand the problem. You see an unexpected &lt;code&gt;null&lt;/code&gt; and immediately start adding guard clauses. You get a &lt;code&gt;500&lt;/code&gt; error and start commenting out code. This produces a cycle of blind changes that can mask the real issue — or introduce new ones.&lt;/p&gt;

&lt;p&gt;Before touching a single line of code, your first move should be to describe the problem in plain language. Write it down if you have to. What did you expect to happen? What actually happened? Where is the boundary between the two? This sounds trivially simple, but forcing yourself to articulate the discrepancy shifts your brain from reaction mode into observation mode. You stop guessing and start reading.&lt;/p&gt;

&lt;p&gt;A useful exercise here is rubber duck debugging, named after the idea of explaining your code to an inanimate object. The act of narrating your logic out loud — or even in a comment block — has a remarkable tendency to surface the flaw. The moment you say "and then this function returns the updated value, which gets passed to..." you often catch the exact step where your mental model diverges from reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce the Bug Reliably
&lt;/h2&gt;

&lt;p&gt;You cannot fix what you cannot reproduce. If the bug only appears sometimes, or only on your colleague's machine, your first task is to identify the exact conditions that trigger it. Environment, data shape, call order, timing — any of these can be a hidden variable.&lt;/p&gt;

&lt;p&gt;The goal is a minimum reproducible example: the smallest possible piece of code that demonstrates the failure. Strip out every dependency that isn't directly involved. Replace real API calls with stub data. Reduce the input to the simplest case that still breaks.&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="c1"&gt;# Before: hard to isolate because of real dependencies
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_order_total&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_user_from_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_active_cart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;apply_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;promo_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SAVE10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;90.0&lt;/span&gt;

&lt;span class="c1"&gt;# After: minimum reproducible example
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_order_total_isolated&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;
    &lt;span class="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&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="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;90.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have a reproducible case, you've already done half the work. You've proven the bug exists independently of external systems, and you have a clear target for your fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the Error Message — Actually Read It
&lt;/h2&gt;

&lt;p&gt;This sounds obvious, but error messages are consistently underread. Developers scan the first line, recognize a familiar exception type, and jump to conclusions. The stack trace contains the actual story, and it's usually worth reading from bottom to top.&lt;/p&gt;

&lt;p&gt;Python tracebacks, for instance, show the outermost call at the top and the point of failure at the bottom. The line that matters is almost always near the end — the specific file, line number, and expression where execution stopped. Similarly, JavaScript console errors frequently include the call stack, which tells you exactly how you arrived at the failing state.&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;// Misread: "TypeError: Cannot read properties of undefined"&lt;/span&gt;
&lt;span class="c1"&gt;// → Developer thinks: "something is undefined, add a check"&lt;/span&gt;

&lt;span class="c1"&gt;// Actually read: line 47, `user.profile.avatar`&lt;/span&gt;
&lt;span class="c1"&gt;// → user exists, but user.profile is undefined&lt;/span&gt;
&lt;span class="c1"&gt;// → the real question is: why was the profile not populated?&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserAvatar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// user arrives without profile key&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { id: 1, name: "Alex" } — profile missing entirely&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// crashes here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you take the error message seriously, you're often led directly to the root cause rather than a symptom. The TypeError above isn't about adding a null check — it's about a missing field from the data source, which is a fundamentally different problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolate the Failing Layer
&lt;/h2&gt;

&lt;p&gt;Complex applications involve multiple systems talking to each other: a frontend, an API layer, a database, external services. When something breaks, your debugging workflow needs a way to narrow down which layer owns the problem without testing all of them at once.&lt;/p&gt;

&lt;p&gt;The most reliable technique is bisection. If you suspect a pipeline of five steps, test the midpoint. If the midpoint is correct, the problem is downstream. If it's wrong, the problem is upstream. Keep bisecting until you've pinpointed the single step that produces unexpected output.&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;process_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;step1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;step2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;step3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;step4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;enrich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;step5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;format_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;step5&lt;/span&gt;

&lt;span class="c1"&gt;# Debugging via bisection:
&lt;/span&gt;&lt;span class="nb"&gt;raw_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_test_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;step1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;After parse:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# looks correct
&lt;/span&gt;
&lt;span class="n"&gt;step3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;After transform:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# wrong value here
&lt;/span&gt;
&lt;span class="c1"&gt;# Now we know: the bug is in validate() or transform()
&lt;/span&gt;&lt;span class="n"&gt;step2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;After validate:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# also wrong — problem is in validate()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach avoids the trap of testing everything from scratch every time. You divide the search space, confirm the safe zones, and close in on the problem systematically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Logging as a First-Class Tool
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;print()&lt;/code&gt; debugging has a bad reputation, but the underlying idea — inserting observability into your code — is sound. The issue isn't logging; it's logging poorly. Printing a variable name without context, or logging after the fact when you don't know what you needed to capture, wastes time.&lt;/p&gt;

&lt;p&gt;The smarter approach is to set up structured logging before problems arise, so that when things break, you already have the information you need. In Python, the &lt;code&gt;logging&lt;/code&gt; module gives you control over levels, formats, and output destinations. In production systems, structured JSON logs are often the difference between a fifteen-minute fix and a three-hour hunt.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(asctime)s [%(levelname)s] %(name)s: %(message)s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&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;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;promo_code&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Calculating discount | price=%.2f promo=%s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;promo_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lookup_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promo_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Discount rate resolved | rate=%.2f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Discount applied | original=%.2f final=%.2f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you log inputs, outputs, and key intermediate values with consistent labels, you can reconstruct the execution path without a debugger. More importantly, you can grep your logs for patterns across many requests — which is something &lt;code&gt;print()&lt;/code&gt; alone can't give you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know When to Walk Away
&lt;/h2&gt;

&lt;p&gt;There is a productivity illusion in debugging where the longer you sit with a problem, the closer you feel to solving it. Most of the time, the opposite is true. After a certain point, continued staring degrades your judgment rather than improving it.&lt;/p&gt;

&lt;p&gt;The best thing you can do after thirty to forty-five minutes of making no progress is to step away from the screen entirely. A short walk, a different task, or even a night's sleep causes what researchers call diffuse thinking — the brain continues working on the problem in the background, making connections that focused attention misses. The "shower insight" phenomenon is real, and it's not a luxury; it's a legitimate part of the problem-solving process.&lt;/p&gt;

&lt;p&gt;When you return, start fresh. Re-read your notes on the problem. Ask yourself whether your original assumption about what broke is still justified, or whether new evidence has shifted things. Often, the first hypothesis was wrong, and you've been chasing a ghost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workflow in Practice
&lt;/h2&gt;

&lt;p&gt;A reliable debugging workflow isn't about following rigid steps on every small bug. It's about having a default sequence you reach for when you're stuck, rather than spending that time thrashing. Reproduce reliably, observe before acting, read the error in full, bisect the failing layer, log with intention, and rest when you're spinning. The best engineers aren't the ones who never get stuck — they're the ones who get unstuck fastest.&lt;/p&gt;

&lt;p&gt;If you take only one thing from this, let it be the reproducible example. Everything else in a good debugging workflow depends on having a clean, isolated case to work with. Build that first, and the path forward becomes much shorter.&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>development</category>
      <category>python</category>
    </item>
    <item>
      <title>AI-Assisted Debugging: Speed Boost or Chaos?</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Sun, 26 Jul 2026 04:45:34 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/ai-assisted-debugging-speed-boost-or-chaos-aih</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/ai-assisted-debugging-speed-boost-or-chaos-aih</guid>
      <description>&lt;p&gt;AI-assisted debugging has gone from novelty to daily habit for a meaningful share of working developers. You paste a stack trace into ChatGPT, describe unexpected behavior to Copilot Chat, or let Cursor highlight a suspicious function — and within seconds you have a hypothesis, sometimes even a working fix. It sounds like exactly what developers have always needed. But the closer you look at how these tools actually perform under real conditions, the more complicated the picture becomes.&lt;/p&gt;

&lt;p&gt;This isn't a verdict on whether &lt;a href="https://fandhip.staff.telkomuniversity.ac.id/2026/07/14/panduan-lengkap-belajar-pemrograman-di-era-ai-2026/" rel="noopener noreferrer"&gt;AI debugging&lt;/a&gt; tools are "good" or "bad." It's a closer look at where they genuinely save time, where they introduce new categories of risk, and how to structure your workflow so you get the speed without the chaos.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI Debugging Tools Are Actually Doing
&lt;/h2&gt;

&lt;p&gt;When you hand a bug to an AI assistant, it isn't running your code or analyzing runtime behavior the way a traditional debugger does. It's doing pattern matching — drawing on enormous amounts of training data to recognize symptoms that resemble patterns it's seen before. That's both the source of its power and its most significant limitation.&lt;/p&gt;

&lt;p&gt;A function that throws &lt;code&gt;ZeroDivisionError&lt;/code&gt; on an empty list is a pattern the model has encountered thousands of times. Consider 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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass an empty list, and you get a crash. An AI assistant will catch this immediately and suggest a guard clause:&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;calculate_average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# or 0, depending on your domain logic
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For problems like this — surface-level, well-documented, with clear error messages — AI debugging is genuinely fast. The model recognizes the pattern, generates the fix, and you move on. There's no reason to dispute the speed benefit here. It's real.&lt;/p&gt;

&lt;p&gt;The pattern-matching engine also works well across language ecosystems. A developer unfamiliar with how JavaScript handles async errors can paste a confusing &lt;code&gt;UnhandledPromiseRejectionWarning&lt;/code&gt; and get a clear explanation of what went wrong and why. A backend engineer debugging a Rust borrow checker error gets a readable walkthrough of the ownership model involved. For cross-language or cross-framework situations — places where a developer's knowledge has gaps — AI tools can compress learning curves dramatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Chaos Comes In
&lt;/h2&gt;

&lt;p&gt;The problem starts when the bug doesn't match a familiar pattern. AI models don't know what they don't know, and they don't express uncertainty the way a senior colleague would. A colleague might say, "I'm not sure — this could be a race condition or a caching issue; let's add some logging and find out." An AI assistant tends to give you a confident answer either way.&lt;/p&gt;

&lt;p&gt;Here's a concrete example of what that looks like in practice. Suppose you're working with a Sequelize query and you get a &lt;code&gt;TypeError&lt;/code&gt; on a result set:&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;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sortByCreatedAt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// TypeError: users.sortByCreatedAt is not a function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some AI responses will invent a method — suggesting &lt;code&gt;sortByCreatedAt()&lt;/code&gt; exists as a Sequelize utility or proposing a library method that doesn't exist in the version you're running. The fix sounds plausible. The code looks reasonable. If you paste it in without checking, you've just introduced a second bug while trying to fix the first one.&lt;/p&gt;

&lt;p&gt;The correct approach is straightforward:&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;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;order&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="s1"&gt;createdAt&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="s1"&gt;DESC&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But getting there requires knowing that Sequelize handles sorting at the query level, not after the fact on the result array. An AI assistant with stale training data or insufficient context about your environment may not get there reliably.&lt;/p&gt;

&lt;p&gt;This pattern — confident, plausible, wrong — is the defining failure mode of AI debugging. And it's more dangerous than an obvious failure, because it passes the eye test. Junior developers in particular may not have the domain knowledge to catch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context Problem
&lt;/h2&gt;

&lt;p&gt;Most debugging problems don't exist in isolation. A production bug is usually the intersection of a specific environment, a particular data state, an architectural decision made eighteen months ago, and a library version that introduced a subtle behavior change in its last minor release. AI tools only know what you tell them in the prompt.&lt;/p&gt;

&lt;p&gt;The quality of your AI debugging interaction is almost entirely determined by the quality of your context. A weak prompt produces a generic answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// What not to do
"Why is my API returning 500?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A specific, contextualized prompt produces something genuinely useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// What actually works
"Node.js 18, Express 4.18, Prisma 5.x. POST /users returns 500 only
when the email field contains a plus sign (+). The request reaches the
controller — I've confirmed with a log — but Prisma throws before the
INSERT. Here's the exact error and the schema field definition: [...]"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference in output quality between these two prompts is substantial. Developers who get consistently good results from AI debugging have internalized this. They treat the AI like a collaborator who needs onboarding, not an oracle who already knows your codebase.&lt;/p&gt;

&lt;p&gt;This also means AI debugging compounds skill rather than replacing it. An experienced engineer who knows exactly what context to surface will use these tools effectively. A developer who doesn't yet have the mental model to articulate what's relevant will get noise back, or worse, a confident wrong answer they can't evaluate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bugs That AI Misses Systematically
&lt;/h2&gt;

&lt;p&gt;Some categories of bugs are effectively invisible to AI assistants because they don't manifest in the code itself. Race conditions in concurrent systems, memory leaks that only surface under load, heisenbugs that disappear when you add logging, performance regressions tied to database query plans — these are problems that require runtime observation, profiling tools, and time. An AI assistant given a code snippet and asked whether it has a race condition will often say no, because the snippet looks fine in isolation.&lt;/p&gt;

&lt;p&gt;Distributed systems failures are particularly resistant to AI diagnosis. When a microservice fails intermittently because a downstream dependency is returning inconsistent data under high concurrency, there's no stack trace to paste. The debugging work happens in observability tooling, and the AI's contribution is limited to helping you reason about what you're seeing — not finding the bug for you.&lt;/p&gt;

&lt;p&gt;This isn't a criticism so much as a scope clarification. AI-assisted debugging is genuinely excellent for a certain class of problem. It's ineffective for another class. Understanding where the boundary is prevents you from wasting time prompting an AI about a problem it structurally cannot see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the Speed Without the Surprises
&lt;/h2&gt;

&lt;p&gt;The developers who use AI debugging most effectively treat AI output as a starting hypothesis, not a final answer. They ask the AI to explain its reasoning, not just produce a fix. They look up the suggested API calls before using them. They test the fix against the specific conditions that triggered the original bug, not just the happy path.&lt;/p&gt;

&lt;p&gt;A few practices that consistently improve results: provide the full error message and stack trace, not just the function you suspect; include your runtime version and relevant dependency versions; tell the AI what you've already tried so it doesn't repeat suggestions; and ask explicitly for alternative explanations when the first answer doesn't sit right.&lt;/p&gt;

&lt;p&gt;It's also worth maintaining healthy skepticism about fixes that involve methods, configuration options, or library features you haven't encountered before. A quick documentation check takes thirty seconds and prevents the scenario where you've applied a confidently stated fix that references an API that doesn't exist in your version.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Trade-Off
&lt;/h2&gt;

&lt;p&gt;AI-assisted debugging makes certain developers faster in certain situations. That's not hype — it's a measurable change in how quickly routine bugs get resolved, and it meaningfully reduces the time junior developers spend stuck on well-documented problems. The tools are worth using.&lt;/p&gt;

&lt;p&gt;But they don't replace the need to understand your system. They don't catch the bugs that require observability. They produce wrong answers confidently and often, and the wrong answers tend to be sophisticated enough that catching them requires the same domain knowledge you would have needed to find the bug yourself.&lt;/p&gt;

&lt;p&gt;Use AI debugging as acceleration, not as a substitute for understanding. Verify what it tells you. Provide real context. Treat the output like a code review comment from a smart colleague who hasn't read your codebase — worth considering, not worth following blindly. Done that way, the speed gains are real, and the chaos stays manageable.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>development</category>
      <category>ai</category>
    </item>
    <item>
      <title>Modern Debugging: The Art of Finding a Needle in a Haystack</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Sun, 26 Jul 2026 04:38:10 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/modern-debugging-the-art-of-finding-a-needle-in-a-haystack-e3h</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/modern-debugging-the-art-of-finding-a-needle-in-a-haystack-e3h</guid>
      <description>&lt;p&gt;Most developers spend nearly half their working hours &lt;a href="https://jakarta.telkomuniversity.ac.id/en/tips-debugging-cara-mengatasi-error-dengan-efektif/" rel="noopener noreferrer"&gt;debugging&lt;/a&gt;. That's not a productivity failure — it's the nature of software. But &lt;strong&gt;modern debugging&lt;/strong&gt; has evolved far beyond adding &lt;code&gt;console.log&lt;/code&gt; statements and hoping for the best. Today's systems are distributed, asynchronous, and layered in ways that make traditional debugging habits feel painfully inadequate against the complexity they were never designed to handle.&lt;/p&gt;

&lt;p&gt;This article walks through practical, proven techniques for tracking down bugs in both development and production environments — from structured logging and binary search strategies to distributed tracing and using debuggers the way they were actually designed to be used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Bugs Hide Better in Modern Systems
&lt;/h2&gt;

&lt;p&gt;Bugs don't just hide in your code anymore. They hide in network latency, race conditions, configuration drift, and the emergent behavior that surfaces when three microservices communicate under load. A defect that's perfectly reproducible on your laptop might vanish entirely in a staging environment and reappear two weeks later in production under a specific, hard-to-replicate sequence of user actions.&lt;/p&gt;

&lt;p&gt;This isn't a bug in your system — it's the nature of complexity. The more moving parts a system has, the more surface area bugs have to hide in. State becomes harder to reason about, causality becomes harder to trace, and the mental model you hold in your head inevitably diverges from what's actually running. That gap is where bugs live.&lt;/p&gt;

&lt;p&gt;The first shift you need to make is conceptual. Debugging isn't guessing. It's a scientific process of forming hypotheses and eliminating them systematically, one by one, until only one explanation remains. Every minute you spend randomly changing things and checking if the bug disappears is a minute not spent actually understanding your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce First, Fix Second
&lt;/h2&gt;

&lt;p&gt;The single biggest mistake developers make when debugging is jumping straight to a fix. Before you touch a single line of code, your primary job is to make the bug happen reliably and on demand. A bug you can reproduce consistently is already 80% solved.&lt;/p&gt;

&lt;p&gt;Start by capturing the exact conditions that trigger the failure: the input, the environment, the sequence of events, and the state of the system at the moment the failure occurred. If you can't reproduce it in isolation, you'll never be confident that your fix actually worked — you'll just be hoping it did, which is a different thing entirely.&lt;/p&gt;

&lt;p&gt;In many cases, writing a failing test before you write any fix is the most reliable way to codify the reproduction step. The test serves simultaneously as proof that the bug exists and proof that your eventual fix actually resolves it.&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="c1"&gt;# Write the failing test before touching the implementation
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_user_balance_never_goes_negative&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InsufficientFundsError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Should raise, not silently set balance to -5.0
&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;  &lt;span class="c1"&gt;# Balance should be unchanged after a failed deduction
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the test fails, the bug is real and documented. When it passes after your change, you're done — and you've added a permanent regression guard in the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Binary Search Method
&lt;/h2&gt;

&lt;p&gt;When you're staring at a large codebase and genuinely don't know where a bug originates, binary search is your most reliable compass. The idea is borrowed directly from computer science fundamentals: divide the problem space in half with each diagnostic step.&lt;/p&gt;

&lt;p&gt;If a bug exists somewhere in a 2,000-line data pipeline, don't start reading from line one. Insert a checkpoint in the middle. If the data looks correct at that checkpoint, the bug lives in the second half. If the data is already wrong, it's in the first half. Repeat the process until the scope is small enough to reason about without overwhelming your working memory.&lt;/p&gt;

&lt;p&gt;Version control makes this approach even more powerful. Git's &lt;code&gt;bisect&lt;/code&gt; command was designed specifically for this kind of search. It performs an automated binary search through your commit history to identify the exact commit that introduced a regression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect start
git bisect bad                  &lt;span class="c"&gt;# Mark the current (broken) commit&lt;/span&gt;
git bisect good v1.4.0          &lt;span class="c"&gt;# Mark a known-good version&lt;/span&gt;

&lt;span class="c"&gt;# Git checks out the midpoint commit automatically.&lt;/span&gt;
&lt;span class="c"&gt;# Run your tests, then report the result:&lt;/span&gt;
git bisect good   &lt;span class="c"&gt;# This commit is fine — bug is in the later half&lt;/span&gt;
&lt;span class="c"&gt;# or:&lt;/span&gt;
git bisect bad    &lt;span class="c"&gt;# This commit is broken — bug is in the earlier half&lt;/span&gt;

&lt;span class="c"&gt;# Git keeps narrowing until it identifies the first bad commit.&lt;/span&gt;
git bisect reset  &lt;span class="c"&gt;# Restore HEAD when you're done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;git bisect&lt;/code&gt; can search through hundreds of commits in under ten iterations. When you land on the first bad commit, reading the diff usually tells you everything you need to know about why the bug exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured Logging: Your Debugging Time Machine
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;console.log("here")&lt;/code&gt; is not logging — it's noise. Structured logging treats each log entry as a data record rather than a free-form string, making it filterable, searchable, and machine-parseable in ways that plain text logs never can be.&lt;/p&gt;

&lt;p&gt;That distinction matters enormously when something goes wrong in production at 2 a.m., and you need to reconstruct exactly what the system was doing when a request failed. A string like &lt;code&gt;"Payment failed for user"&lt;/code&gt; tells you almost nothing. A structured event with user ID, amount, error code, and timestamp tells you everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Good Logging Looks Like
&lt;/h3&gt;

&lt;p&gt;Well-structured logs capture context — not just what happened, but who triggered it, what data was in play, and how long the operation took. Libraries like Python's &lt;code&gt;structlog&lt;/code&gt; or Node's &lt;code&gt;pino&lt;/code&gt; make this the path of least resistance.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_payment&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_initiated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&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;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&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;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_succeeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&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;transaction_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration_ms&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;PaymentError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&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;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;error_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every log entry here carries enough context to stand alone. If you aggregate these entries and filter by &lt;code&gt;user_id&lt;/code&gt;, you get a complete timeline of everything that happened during that user's payment flow — no extra debugging code required, no reproduction needed. The logs are the reproduction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a Debugger the Right Way
&lt;/h2&gt;

&lt;p&gt;Most developers know their IDE includes a debugger. Far fewer use it effectively. The most common misuse is stepping through code line by line, looking for something that looks wrong. It's the slowest possible way to use the tool — and it puts the burden of verification entirely on your memory rather than on the machine.&lt;/p&gt;

&lt;p&gt;The right approach is to set breakpoints at decision points, not at every line. A decision point is anywhere the program branches: an &lt;code&gt;if&lt;/code&gt; statement, a loop condition, a function that could return multiple distinct values. When execution pauses, your job is to verify or falsify a specific hypothesis about the data — not to read the code as if you've never seen it before.&lt;/p&gt;

&lt;p&gt;Watch expressions change this completely. Instead of manually inspecting multiple variables each time execution pauses, you define expressions that evaluate automatically at every breakpoint:&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;// Set in browser DevTools or your IDE debugger as a watch expression:&lt;/span&gt;
&lt;span class="c1"&gt;// user.permissions.includes("admin") &amp;amp;&amp;amp; user.session.isActive&lt;/span&gt;

&lt;span class="c1"&gt;// The debugger evaluates this automatically every time execution pauses.&lt;/span&gt;
&lt;span class="c1"&gt;// You see immediately whether both conditions are true, without manually&lt;/span&gt;
&lt;span class="c1"&gt;// opening two nested objects every single time.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conditional breakpoints extend this further. If a bug only surfaces on the 50th iteration of a loop, you should never have to click "continue" 49 times. Right-click the breakpoint and set a condition — pause only when &lt;code&gt;i === 49&lt;/code&gt; or when &lt;code&gt;response.status === 500&lt;/code&gt;. The debugger does the tedious work; you think.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability in Distributed Systems
&lt;/h2&gt;

&lt;p&gt;When your application spans multiple services, traditional debugging stops working at the boundaries. You can't set a breakpoint in a production container. You can't reliably reproduce a multi-service interaction on your laptop. You can't read logs from five different services simultaneously and manually correlate events by timestamp. This is where observability becomes the only viable strategy.&lt;/p&gt;

&lt;p&gt;Observability rests on three pillars: logs, metrics, and distributed traces. Logs tell you what happened on a single service at a specific moment. Metrics tell you whether the system is behaving normally at a statistical level over time. Distributed traces stitch together the full lifecycle of a single request as it crosses service boundaries.&lt;/p&gt;

&lt;p&gt;OpenTelemetry has become the vendor-neutral standard for adding trace instrumentation to services. It propagates a shared trace context across every service hop, so you can reconstruct the full request path after the fact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;

&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tracer&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;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_user_profile&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fetch_user_profile&lt;/span&gt;&lt;span class="sh"&gt;"&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;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user.id&lt;/span&gt;&lt;span class="sh"&gt;"&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users WHERE id = ?&lt;/span&gt;&lt;span class="sh"&gt;"&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;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db.result.found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db.query.rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;else&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="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, every invocation of &lt;code&gt;fetch_user_profile&lt;/code&gt; is tracked across service calls, giving you a timeline of the entire request lifecycle. When a request fails, you pull its trace ID from the error log and see the exact sequence of service calls, their durations, and where the chain broke — which is information no local debugger can give you.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Stop and Rethink
&lt;/h2&gt;

&lt;p&gt;One debugging trap that rarely gets discussed is the sunk cost of a wrong hypothesis. You spend three hours convinced the bug is in the authentication layer, chasing signals that seem to confirm it, and finally realize the real issue is a misconfigured environment variable in a completely different service. Those three hours are gone.&lt;/p&gt;

&lt;p&gt;The discipline here is to set a time budget for each hypothesis. If you haven't found confirming evidence within 30 to 45 minutes of focused investigation, abandon the hypothesis and start fresh. It feels counterintuitive, but the willingness to throw away a working theory is one of the most underrated debugging skills there is.&lt;/p&gt;

&lt;p&gt;Write down your hypothesis explicitly before you start investigating it. That small act of externalizing forces you to be precise about what you believe, and it makes it much easier to recognize when the evidence is pointing somewhere else entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: From Noise to Signal
&lt;/h2&gt;

&lt;p&gt;The gap between an average debugger and a skilled one isn't about knowing more tools. It's about having a method. Reproduce first. Form a hypothesis. Narrow the search space systematically. Verify with evidence. Every productive debugging session is a compressed scientific experiment with a clear question, a testable prediction, and an outcome that either confirms or refutes the theory.&lt;/p&gt;

&lt;p&gt;Modern debugging demands that you treat your code as a system to observe, not just a text file to read. Instrument it deliberately, log with intent, and reach for the right tool at the right layer of the stack. When you build those habits consistently, the needle stops hiding — and the haystack starts to shrink.&lt;/p&gt;

&lt;p&gt;If you're ready to sharpen these skills in practice, pick one technique from this article and apply it to the next real bug you encounter. Don't wait for the perfect bug or the perfect system. Methodology compounds over time — every bug you debug deliberately makes the next one faster to find.&lt;/p&gt;

</description>
      <category>code</category>
      <category>development</category>
    </item>
    <item>
      <title>Deep Learning Libraries for Beginners: A Comparative Overview</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Tue, 21 Jul 2026 03:39:39 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/deep-learning-libraries-for-beginners-a-comparative-overview-48nj</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/deep-learning-libraries-for-beginners-a-comparative-overview-48nj</guid>
      <description>&lt;p&gt;Choosing among &lt;a href="https://it.telkomuniversity.ac.id/tag/deeplearning/" rel="noopener noreferrer"&gt;deep learning&lt;/a&gt; libraries is often the first real decision a new machine learning practitioner has to make, and it shapes how painful or pleasant the next few months of learning will be. Keras, PyTorch, and TensorFlow are the three names that dominate almost every tutorial, bootcamp, and university course, with JAX increasingly showing up as a fourth option for people who want to go deeper into the mechanics. Each of these libraries solves the same underlying problem, building and training neural networks, but they approach it with different philosophies about how much complexity a beginner should see on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Makes a Library Beginner-Friendly
&lt;/h2&gt;

&lt;p&gt;Before comparing specific tools, it helps to define what "beginner-friendly" even means in this context. A library is beginner-friendly when its syntax matches the way a newcomer already thinks about code, when error messages point clearly at the actual mistake, and when the path from "I have an idea" to "I have a working model" involves as few detours through boilerplate as possible. Speed of execution, GPU support, and production deployment options matter too, but those concerns tend to surface later, once someone has already built a handful of models and wants to ship one.&lt;/p&gt;

&lt;p&gt;It's also worth separating two different questions that beginners often conflate: which library is easiest to learn &lt;em&gt;concepts&lt;/em&gt; with, and which library is worth building &lt;em&gt;habits&lt;/em&gt; around for a longer career. Sometimes those answers point in the same direction. Sometimes they don't, and that mismatch is exactly why this comparison exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keras: The Gentlest On-Ramp
&lt;/h2&gt;

&lt;p&gt;Keras was built by François Chollet with a specific goal in mind: reducing the cognitive load required to define a neural network. It has been folded directly into TensorFlow as &lt;code&gt;tf.keras&lt;/code&gt; since TensorFlow 2.0, and the standalone Keras 3 release extended that same high-level API across TensorFlow, PyTorch, and JAX backends, so a Keras model can now run on whichever engine underneath it makes sense for the task.&lt;/p&gt;

&lt;p&gt;The appeal for beginners is immediate. A working image classifier can be assembled in a handful of lines using the &lt;code&gt;Sequential&lt;/code&gt; API, and the &lt;code&gt;model.fit()&lt;/code&gt; method quietly handles the training loop, batching, and metric tracking that would otherwise need to be written by hand.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Conv2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;relu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MaxPooling2D&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Flatten&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;softmax&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;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adam&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sparse_categorical_crossentropy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accuracy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epochs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validation_split&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That compactness is also Keras's main limitation. Once a project needs a custom training loop, an unusual loss function, or a research-style architecture with branching logic, the high-level API starts to feel restrictive, and learners often find themselves reaching for lower-level tools anyway. Keras remains an excellent place to learn what layers, activations, and optimizers actually do without getting distracted by plumbing, but it's rarely where advanced work ends up living.&lt;/p&gt;

&lt;h2&gt;
  
  
  PyTorch: Learning by Writing Regular Python
&lt;/h2&gt;

&lt;p&gt;PyTorch, released by Meta AI's research group, takes a different approach: rather than hiding the mechanics of a neural network, it exposes them using ordinary Python control flow. Because PyTorch builds its computation graph dynamically as code executes, a training loop looks almost exactly like any other Python loop, which makes debugging feel familiar rather than mysterious.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch.nn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Conv2d&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;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReLU&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MaxPool2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Flatten&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Adam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;loss_fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CrossEntropyLoss&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;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&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;images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;train_loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is more code than the Keras example, and that extra verbosity is precisely the point: writing the loop by hand forces a beginner to see the forward pass, the loss calculation, the backward pass, and the optimizer step as four distinct actions rather than one hidden method call. Surveys of newcomers consistently find that a majority now choose PyTorch as their first framework, and recent industry breakdowns put PyTorch's share of research publications and machine learning job postings well ahead of its main rival, a trend that has only strengthened as most new model releases and tutorials, including the Hugging Face ecosystem, default to it. The tradeoff is that beginners have to write and understand more of the underlying machinery before they see a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  TensorFlow: Built for Production From the Start
&lt;/h2&gt;

&lt;p&gt;TensorFlow, developed by the Google Brain team, was originally built around static computation graphs, an approach that offered performance and deployment advantages but made early versions notoriously difficult to debug. TensorFlow 2.x switched to eager execution by default and absorbed Keras as its official high-level API, closing much of the usability gap with PyTorch for everyday model building.&lt;/p&gt;

&lt;p&gt;Where TensorFlow still earns its keep is downstream of training. Its ecosystem includes TensorFlow Serving for scalable model deployment, TensorFlow Lite (now transitioning to the standalone LiteRT project) for mobile and embedded devices, and TensorFlow.js for running models directly in a browser. Recent TensorFlow releases have leaned further into this identity, focusing on stability, quantization support for edge hardware, and data-pipeline performance rather than expanding the modeling API, with the project's own release notes now pointing newcomers toward Keras 3, JAX, or PyTorch for cutting-edge generative AI work. For a beginner who already knows they want to deploy a model onto a phone or an industrial sensor, that ecosystem is a genuine advantage. For someone still learning what a convolution does, it's mostly irrelevant.&lt;/p&gt;

&lt;h2&gt;
  
  
  JAX: Worth Knowing About, Not Where to Start
&lt;/h2&gt;

&lt;p&gt;JAX, built by Google DeepMind, occupies a different niche entirely. It combines NumPy-style syntax with automatic differentiation and just-in-time compilation, which makes it extremely fast for research code and custom numerical work, and it now sits alongside TensorFlow and PyTorch as one of the backends Keras 3 can run on. JAX rewards a level of comfort with functional programming and array manipulation that most beginners haven't built yet, so it's better treated as a second or third framework, one worth learning once the basic concepts of backpropagation and optimization already feel intuitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Comparison Actually Plays Out
&lt;/h2&gt;

&lt;p&gt;In practice, the choice among these libraries rarely stays fixed for long. A common and sensible pattern among both students and professional teams is to prototype in PyTorch, where the dynamic graph makes experimentation fast, and then either stay there for deployment or convert the final model through a format like ONNX if TensorFlow's serving infrastructure is needed. This hybrid approach has become common enough that treating the decision as permanent is a bit of a false premise; the frameworks are increasingly interoperable rather than walled off from each other.&lt;/p&gt;

&lt;p&gt;Performance differences between PyTorch and TensorFlow on a single GPU are close enough in most benchmarks that they shouldn't be the deciding factor for a beginner, especially since both frameworks continue to trade the lead as new compiler optimizations ship. What matters more at the start is which mental model clicks. Someone who wants to see a working model in the shortest number of lines, and who is comfortable treating the internals as a black box for a while, will likely be happier starting with Keras. Someone who wants to understand every step of the training process, and who is willing to write more code to get that understanding, will probably get more out of starting directly with PyTorch.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Starting Point
&lt;/h2&gt;

&lt;p&gt;For most newcomers in 2026, the pragmatic path is to start with either Keras or PyTorch, not both at once. Learning two new APIs alongside the underlying math of neural networks at the same time tends to slow everyone down rather than build well-rounded skills faster. Once the fundamentals of layers, loss functions, and gradient descent feel solid, picking up the other framework is a matter of days, not months, because the concepts transfer even when the syntax doesn't.&lt;/p&gt;

&lt;p&gt;TensorFlow is worth learning specifically when a project's requirements point toward it- mobile deployment, browser-based inference, or an existing production pipeline that already depends on it- rather than as a default first choice. JAX is worth exploring once a learner is comfortable enough with the basics to appreciate what its speed and functional style actually buy them. None of these libraries is objectively "the best" in isolation; each was built to solve a slightly different problem, and the right one depends on what a beginner is actually trying to build next.&lt;/p&gt;

&lt;p&gt;The honest advice, then, isn't to find the single correct framework and commit to it forever. It's to pick one that matches how you learn right now, build something small and real with it, and let the next framework introduce itself once you have a reason to need it.&lt;/p&gt;

</description>
      <category>deeplearning</category>
      <category>ai</category>
    </item>
    <item>
      <title>Exploring the Deep Learning Library in Modern Computer Vision</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Tue, 21 Jul 2026 03:35:19 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/exploring-the-deep-learning-library-in-modern-computer-vision-4j9d</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/exploring-the-deep-learning-library-in-modern-computer-vision-4j9d</guid>
      <description>&lt;p&gt;Picking the right &lt;a href="https://openlibrary.telkomuniversity.ac.id/pustaka/165800/deep-learning-with-javascript-neural-networks-in-tensorflow-js.html" rel="noopener noreferrer"&gt;deep learning library&lt;/a&gt; shapes almost everything about a computer vision project, from how fast you can prototype a model to how painful it is to ship one into production. Two frameworks dominate this decision today: PyTorch and TensorFlow. Neither has definitively won, but the split between them has become clearer than it was five years ago, and understanding that split is the fastest way to stop guessing and start building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Choice of Framework Still Matters
&lt;/h2&gt;

&lt;p&gt;It's tempting to think framework choice is a solved problem — just pick whatever's popular and move on. But vision work has quirks that make the library underneath your code more than a technical footnote. Custom data augmentation pipelines, non-standard loss functions for tasks like instance segmentation, and the need to export models to mobile or edge devices all behave differently depending on the ecosystem you're in.&lt;/p&gt;

&lt;p&gt;Market data backs up the idea that this is still a genuinely contested space. TensorFlow holds a larger footprint in enterprise deployment, with roughly 37% market share and tens of thousands of companies using it in production, largely thanks to TensorFlow Serving, TensorFlow Extended, and TensorFlow Lite running across billions of devices. PyTorch, meanwhile, has become the default in research settings, with a majority of recent computer vision papers shipping PyTorch reference implementations first. Job postings mentioning PyTorch have also edged ahead of TensorFlow in recent hiring data, reflecting how much prototyping and applied research work now happens in that ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  PyTorch: The Researcher's Default
&lt;/h2&gt;

&lt;p&gt;PyTorch's dynamic computation graph is the feature people mention first, and for good reason. Because the graph is built as your code runs, you can set breakpoints, inspect tensors mid-forward-pass, and change model behavior conditionally without recompiling anything. For anyone iterating on a novel architecture — a new attention mechanism for object detection, say — that debugging loop is the difference between an afternoon of experimentation and a week of frustration.&lt;/p&gt;

&lt;p&gt;A simple image classification setup shows how little boilerplate PyTorch demands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch.nn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;torchvision&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transforms&lt;/span&gt;

&lt;span class="c1"&gt;# Load a pretrained ResNet-50 and adapt it for a new task
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resnet50&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IMAGENET1K_V2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;in_features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 10 output classes
&lt;/span&gt;
&lt;span class="n"&gt;preprocess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterCrop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToTensor&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.485&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.456&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.406&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                          &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.225&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;criterion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CrossEntropyLoss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AdamW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That block loads a pretrained backbone, swaps the final classification layer, and sets up standard preprocessing — all in code that reads close to plain Python. The &lt;code&gt;torchvision&lt;/code&gt; library, which ships alongside PyTorch, bundles common vision datasets, pretrained weights, and transform utilities, which removes a lot of the groundwork that used to eat up the first week of a vision project.&lt;/p&gt;

&lt;p&gt;Performance used to be PyTorch's weak spot compared to TensorFlow's graph-compiled execution, but that gap has narrowed considerably. The &lt;code&gt;torch.compile()&lt;/code&gt; feature, introduced in PyTorch 2.0, uses the Triton compiler to just-in-time optimize models with a single line of code, and benchmarks on architectures like ResNet-50 have shown speedups in the 20–25% range without any changes to the surrounding training loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  TensorFlow: Built for the Production Pipeline
&lt;/h2&gt;

&lt;p&gt;TensorFlow's strength has always been what happens after the model works. Its ecosystem is more centralized and opinionated than PyTorch's, with official first-party libraries for vision, text, and probabilistic modeling that are designed to interoperate cleanly. That consistency pays off when a model needs to move from a training script into a serving system that other engineers will maintain.&lt;/p&gt;

&lt;p&gt;TensorFlow Lite (now often referred to as LiteRT) is the clearest example. It compresses and quantizes models so they run efficiently on phones and embedded hardware, which matters enormously for vision tasks like on-device object detection or real-time image classification in a mobile app. A comparable image classification setup in TensorFlow looks 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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tensorflow&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt;

&lt;span class="n"&gt;base_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ResNet50&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imagenet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include_top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;base_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trainable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;base_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GlobalAveragePooling2D&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;softmax&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;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimizers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AdamW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sparse_categorical_crossentropy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accuracy&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;p&gt;The Keras API, now integrated directly into TensorFlow, trades some of PyTorch's low-level flexibility for a more declarative style that's easier to hand off between team members. Keras 3, released as a framework-agnostic layer, has taken this further by letting the same high-level code run on PyTorch, TensorFlow, or JAX backends interchangeably, which is a quiet acknowledgment that the industry no longer wants to be locked into one computational substrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Two Frameworks Actually Diverge for Vision Work
&lt;/h2&gt;

&lt;p&gt;Computer vision, more than NLP, tends to be where the choice between these two libraries feels closest to a coin flip, because both have mature tooling for image tasks. The practical differences show up in specific situations rather than in a blanket "one is better" verdict.&lt;/p&gt;

&lt;p&gt;If the project involves reproducing a recent paper on object detection or segmentation, PyTorch is almost always the path of least resistance, since that's the framework the original authors used. If the project is a mobile app that needs to classify images on-device with tight latency and battery constraints, TensorFlow Lite's optimization tooling is hard to match. Teams building internal MLOps pipelines in regulated industries — healthcare imaging or financial document analysis, for instance — often lean TensorFlow because of its longer track record with tools like TensorFlow Extended for pipeline orchestration and validation.&lt;/p&gt;

&lt;p&gt;Deployment has also become somewhat framework-agnostic at the inference layer. Increasingly, trained models exit their original framework entirely through formats like ONNX and get served through dedicated runtimes such as TensorRT or Triton Inference Server, which means the training framework matters less at the point where a model actually meets production traffic. That shift softens the stakes of the initial choice, but it doesn't eliminate the day-to-day experience of writing and debugging training code, which is where most vision engineers still spend the bulk of their time.&lt;/p&gt;

&lt;p&gt;Cost is another factor that rarely makes it into framework comparisons but shapes real hiring and staffing decisions. Salary data for AI engineering roles shows senior specialists in vision and NLP commanding a premium regardless of which framework they list, but PyTorch experience tends to open more doors into research-adjacent roles, while TensorFlow experience is still valued heavily in industries with established MLOps pipelines built around TFX. Teams evaluating which library to standardize on internally are effectively also deciding what kind of engineer they'll find it easiest to hire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond the Big Two: OpenCV and Specialized Libraries
&lt;/h2&gt;

&lt;p&gt;Deep learning frameworks handle the model, but a full computer vision pipeline usually needs more than that. OpenCV remains the standard toolkit for classical image processing tasks — resizing, color space conversion, edge detection, camera calibration — that sit upstream or downstream of a neural network. It's common to see OpenCV used for real-time video capture and preprocessing, with the actual inference handled by a PyTorch or TensorFlow model loaded into the same script.&lt;/p&gt;

&lt;p&gt;Higher-level libraries have also emerged to reduce repetitive setup work. The &lt;code&gt;timm&lt;/code&gt; library (PyTorch Image Models) provides hundreds of pretrained vision backbones with a consistent interface, which is useful when benchmarking multiple architectures against the same dataset without rewriting loading code each time. Detectron2 and MMDetection, both built on PyTorch, offer ready-made implementations of detection and segmentation architectures that would otherwise take weeks to reproduce from a research paper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Decision for Your Own Project
&lt;/h2&gt;

&lt;p&gt;The honest answer to "which library should I use" depends on where a project sits in its lifecycle. Early-stage research, architecture experimentation, or academic work benefits from PyTorch's debugging ergonomics and its head start on new techniques. Mature products heading toward mobile deployment, or teams that already have TensorFlow infrastructure and MLOps tooling in place, often get more value from staying inside that ecosystem rather than paying the switching cost.&lt;/p&gt;

&lt;p&gt;Learning both is increasingly the realistic expectation for anyone working professionally in computer vision. The underlying concepts — convolutional layers, backpropagation, optimizers, data augmentation — transfer directly between frameworks, so the second one is far easier to pick up than the first. Given how much the field still shifts year to year, that flexibility is worth more than loyalty to either library.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>tensorflow</category>
    </item>
    <item>
      <title>Build Your First Neural Network: A Deep Dive into the TensorFlow Library</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Tue, 21 Jul 2026 02:29:15 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/build-your-first-neural-network-a-deep-dive-into-the-tensorflow-library-3ecb</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/build-your-first-neural-network-a-deep-dive-into-the-tensorflow-library-3ecb</guid>
      <description>&lt;p&gt;Building your first neural network with TensorFlow is one of the most practical ways to understand how &lt;a href="https://it.telkomuniversity.ac.id/machine-learning-konsep/" rel="noopener noreferrer"&gt;machine learning&lt;/a&gt; actually works under the hood. Rather than treating deep learning as a black box, walking through the process of designing, training, and evaluating a network gives you a concrete mental model you can carry into more advanced projects. TensorFlow, Google's open-source library for numerical computation and machine learning, has become one of the two dominant frameworks in the field, and its high-level Keras API makes the barrier to entry lower than most newcomers expect.&lt;/p&gt;

&lt;p&gt;This guide walks through the full lifecycle of a simple neural network: setting up your environment, preparing data, defining the model architecture, training it, and evaluating the results. Along the way, we'll touch on the design decisions that matter and the tradeoffs you should be aware of before treating any of this as production-ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why TensorFlow Is a Reasonable Starting Point
&lt;/h2&gt;

&lt;p&gt;TensorFlow was released by Google in 2015 and has since matured into a comprehensive ecosystem that spans research, production deployment, and mobile or edge inference through TensorFlow Lite. Its main competitor, PyTorch, is often praised for a more Pythonic, dynamic-graph feel that some developers find easier to debug. TensorFlow's advantage lies elsewhere: its Keras API, tight integration with TensorFlow Serving and TensorFlow.js, and broad industry adoption mean that skills learned here transfer directly to deployment scenarios you're likely to encounter in a job or a shipped product.&lt;/p&gt;

&lt;p&gt;Neither framework is objectively superior for every use case. If your work leans heavily toward research prototyping, PyTorch's flexibility often wins. If you're building something you intend to deploy at scale, in a browser, or on a mobile device, TensorFlow's tooling tends to reduce friction later in the project. It's worth knowing both exist, and this article uses TensorFlow because its Keras interface is arguably the gentlest on-ramp for someone building a first network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Your Environment
&lt;/h2&gt;

&lt;p&gt;Before writing any model code, you need a working Python environment with TensorFlow installed. A virtual environment keeps this isolated from other projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv tf-env
&lt;span class="nb"&gt;source &lt;/span&gt;tf-env/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;tensorflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, confirm the setup works and check which version you're running, since TensorFlow's API has changed meaningfully across major versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tensorflow&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__version__&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 have a compatible NVIDIA GPU, TensorFlow can use it automatically, though a CPU is perfectly sufficient for the small example we'll build here. Training a full-scale model on CPU only becomes painful once your dataset and network grow substantially larger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Dataset
&lt;/h2&gt;

&lt;p&gt;For a first neural network, it makes sense to use a dataset that's small, clean, and well understood, so that any problems you hit are almost certainly in your code rather than in messy data. The MNIST dataset of handwritten digits is the classic choice for this reason. It contains 70,000 grayscale images of digits 0 through 9, each 28x28 pixels, split into training and test sets.&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;mnist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mnist&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mnist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;x_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x_train&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;255.0&lt;/span&gt;
&lt;span class="n"&gt;x_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x_test&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;255.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That normalization step, dividing pixel values by 255, scales them into a 0 to 1 range. Neural networks generally train faster and more reliably on normalized inputs, since large or wildly varying input magnitudes can destabilize gradient descent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Model Architecture
&lt;/h2&gt;

&lt;p&gt;With Keras, you define a network by stacking layers. For MNIST, a modest fully connected network is enough to get strong results without needing convolutional layers, though those would improve accuracy further.&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;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;relu&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dropout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each piece here does specific work. The &lt;code&gt;Flatten&lt;/code&gt; layer converts each 28x28 image into a single 784-element vector, since dense layers expect one-dimensional input. The &lt;code&gt;Dense&lt;/code&gt; layer with 128 units and ReLU activation is where most of the actual learning happens, combining inputs with weights and applying a nonlinearity so the network can model more than straight lines. &lt;code&gt;Dropout&lt;/code&gt; randomly disables 20 percent of neurons during training, which is a regularization technique that helps prevent the network from memorizing the training data instead of learning generalizable patterns. The final &lt;code&gt;Dense&lt;/code&gt; layer with 10 units corresponds to the 10 possible digit classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling and Training the Model
&lt;/h2&gt;

&lt;p&gt;Before training, the model needs an optimizer, a loss function, and a metric to track.&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;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;adam&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;losses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SparseCategoricalCrossentropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_logits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accuracy&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;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epochs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adam is a reasonable default optimizer for most beginner projects because it adapts its learning rate automatically and tends to converge reliably without much manual tuning. Sparse categorical crossentropy is the appropriate loss function here because the labels are integers (0 through 9) rather than one-hot encoded vectors. The &lt;code&gt;from_logits=True&lt;/code&gt; argument matters because our final layer has no activation function, meaning it outputs raw scores rather than probabilities; TensorFlow needs to know this to compute the loss correctly.&lt;/p&gt;

&lt;p&gt;Training for 5 epochs, meaning 5 full passes through the training data, is usually enough to see this simple network reach around 97 to 98 percent accuracy on MNIST. That's not a benchmark to be impressed by in 2026, since MNIST is a solved problem for even basic architectures, but it's a useful confirmation that your pipeline actually works end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluating the Model
&lt;/h2&gt;

&lt;p&gt;Accuracy on training data tells you almost nothing about how the model will perform on data it hasn't seen. The test set exists specifically to check 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;test_loss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_acc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test accuracy:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_acc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If test accuracy is noticeably lower than training accuracy, that gap is a signal of overfitting: the model has started memorizing quirks of the training set rather than learning generalizable patterns. On a simple MNIST model like this one, the gap should be small. On more complex projects with less data or more parameters, watching this gap closely becomes one of the more important diagnostic habits you can build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Predictions
&lt;/h2&gt;

&lt;p&gt;Once trained, the model can classify new images. Wrapping it with a softmax layer converts the raw logit outputs into interpretable probabilities.&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;probability_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Softmax&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;predictions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;probability_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_test&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row in &lt;code&gt;predictions&lt;/code&gt; is a probability distribution across the 10 digit classes, and the class with the highest value is the model's best guess. This distinction between logits and probabilities trips up a lot of newcomers, since it's easy to forget that raw model output and a genuine probability aren't automatically the same thing without an explicit softmax step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls Worth Knowing
&lt;/h2&gt;

&lt;p&gt;A few mistakes recur often enough among people building their first network that they're worth naming directly. Forgetting to normalize input data is common and can lead to slow or unstable training. Mismatching the loss function with the label format, using categorical crossentropy on integer labels instead of sparse categorical crossentropy, throws a shape error that confuses a lot of beginners. Training for too many epochs on a small dataset without any validation monitoring can quietly produce an overfit model that looks great on paper and performs poorly on anything new.&lt;/p&gt;

&lt;p&gt;It's also worth being honest that MNIST is not representative of most real-world problems. Real datasets are messier, class-imbalanced, and rarely arrive pre-cleaned. The habits built here- checking shapes, normalizing inputs, watching the train-test accuracy gap-matter more than the specific architecture, and they transfer directly once you move to harder problems like image classification on custom datasets or natural language tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Go From Here
&lt;/h2&gt;

&lt;p&gt;Once this basic pipeline feels comfortable, the natural next step is experimenting with convolutional neural networks for image tasks, which handle spatial structure far better than the flatten-and-dense approach used here. From there, TensorFlow's ecosystem offers a fairly direct path toward more advanced work: TensorFlow Datasets for handling larger and more varied data sources, TensorBoard for visualizing training in more depth than print statements allow, and TensorFlow Lite or TensorFlow.js if deployment to mobile or web is eventually the goal.&lt;/p&gt;

&lt;p&gt;Building a first neural network is less about the specific numbers you get and more about internalizing the shape of the workflow: prepare data, define architecture, compile with the right loss and optimizer, train, evaluate honestly, and iterate. That workflow doesn't change much as the problems get harder; only the pieces inside it grow more sophisticated.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>tensorflow</category>
    </item>
    <item>
      <title>Understanding Database Normalization: Organizing Your Web Data for Speed</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Fri, 17 Jul 2026 07:30:12 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/understanding-database-normalization-organizing-your-web-data-for-speed-31lk</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/understanding-database-normalization-organizing-your-web-data-for-speed-31lk</guid>
      <description>&lt;p&gt;&lt;a href="https://repository.telkomuniversity.ac.id/pustaka/181748/mastering-mysql-for-web-a-beginner-s-guide-mastering-computer-science-.html" rel="noopener noreferrer"&gt;Database normalization&lt;/a&gt; is one of those concepts that developers learn once in school, promptly forget, and then rediscover the hard way when a production database starts crawling under the weight of duplicated, inconsistent data. If you've ever updated a customer's email address in one table only to realize it still shows the old address somewhere else, you've already felt the pain that normalization exists to prevent. This guide walks through what database normalization actually means, why it matters for the speed and reliability of your web applications, and how to apply it without overcomplicating your schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Database Normalization Actually Means
&lt;/h2&gt;

&lt;p&gt;At its core, normalization is the process of structuring a relational database so that data is stored logically, without unnecessary repetition. Instead of cramming every piece of related information into one giant table, you split data into smaller, purpose-built tables and connect them through relationships. The goal isn't just tidiness for its own sake; it's about eliminating the update anomalies that happen when the same fact lives in multiple places and only some of those places get updated.&lt;/p&gt;

&lt;p&gt;The practice was formalized by Edgar F. Codd in the 1970s as part of the relational database model, and it's expressed through a series of "normal forms." Each normal form builds on the one before it, adding stricter rules about how data should be organized. Most real-world applications only need to worry about the first three normal forms, though a couple of more advanced forms exist for edge cases.&lt;/p&gt;

&lt;p&gt;It helps to think of normalization less as a rigid checklist and more as a way of asking one repeated question: does this piece of data belong here, or does it belong somewhere else? Answering that question consistently across your schema is what keeps a database maintainable as it grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Normal Form: Getting Rid of Repeating Groups
&lt;/h2&gt;

&lt;p&gt;First normal form, often abbreviated as 1NF, requires that each column in a table hold a single, atomic value, and that each row be uniquely identifiable. A common violation of 1NF looks like a "phone_numbers" column that stores a comma-separated list of numbers in a single field. That might seem convenient at first, but it makes searching, indexing, and updating individual numbers unnecessarily painful.&lt;/p&gt;

&lt;p&gt;The fix is usually to break that repeating data into its own table. Instead of one row per customer with a bloated phone number field, you create a separate table where each row represents a single phone number tied to a customer through a foreign key. This small change makes queries dramatically simpler, since the database engine can now index and search phone numbers directly rather than parsing strings.&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;-- Before 1NF: repeating group in a single column&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;customers_bad&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&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;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;phone_numbers&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="c1"&gt;-- e.g. "555-1234, 555-5678"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- After 1NF: phone numbers moved to their own table&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;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&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;100&lt;/span&gt;&lt;span class="p"&gt;)&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;TABLE&lt;/span&gt; &lt;span class="n"&gt;customer_phones&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;phone_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;phone_number&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;20&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;Once the data is split this way, adding a third or fourth phone number for a customer no longer requires rewriting an entire string field. It's just a new row in a table designed for exactly that purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Second Normal Form: Removing Partial Dependencies
&lt;/h2&gt;

&lt;p&gt;Second normal form, or 2NF, only becomes relevant when a table has a composite primary key, meaning the key is made up of more than one column. 2NF requires that every non-key column depend on the entire composite key, not just part of it. If a column only depends on one piece of the key, it's a sign that the table is trying to do too much.&lt;/p&gt;

&lt;p&gt;Imagine an order_items table where the primary key is a combination of order_id and product_id, but the table also stores the product's name and price directly. The product name and price don't actually depend on the order; they depend only on the product. That's a partial dependency, and it means the same product information gets duplicated across every order that includes it.&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;-- Before 2NF: product_name depends only on product_id, not the full key&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;order_items_bad&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;product_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;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- After 2NF: product details moved to their own table&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;products&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;product_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;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;unit_price&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&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;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_id&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;With this structure, renaming a product happens in exactly one place: the products table. Every order that references that product automatically reflects the change, because the order_items table only stores a reference, not a copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Third Normal Form: Eliminating Transitive Dependencies
&lt;/h2&gt;

&lt;p&gt;Third normal form, or 3NF, tackles a subtler problem: transitive dependencies, where a non-key column depends on another non-key column rather than on the primary key itself. A classic example is storing a customer's city and zip code directly in an orders table. The zip code determines the city, not the order, so the city is transitively dependent on the zip code rather than on the order itself.&lt;/p&gt;

&lt;p&gt;This might not sound like a big deal until you consider what happens when a zip code's associated city changes, or when a typo creates two different city names for the same zip code across different rows. 3NF fixes this by pulling the zip-code-to-city relationship into its own reference table.&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;-- Before 3NF: city is transitively dependent on zip_code, not order_id&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;orders_bad&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;zip_code&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;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;city&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;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- After 3NF: zip code and city relationship isolated&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;zip_codes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;zip_code&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;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;city&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;100&lt;/span&gt;&lt;span class="p"&gt;)&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;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;zip_code&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;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;zip_codes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zip_code&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;This pattern shows up constantly in web applications, from address data to product categories to user roles. Any time you notice that one column's value can be derived from another non-key column rather than from the row's identity, it's worth asking whether that data belongs in its own table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Speed, Not Just Tidiness
&lt;/h2&gt;

&lt;p&gt;It's tempting to think of normalization purely as a data-integrity exercise, but it has real performance implications for web applications too. A properly normalized schema tends to have smaller, more focused tables, which means indexes are more effective and queries scan less data to find what they need. Updates also become cheaper, since changing a single fact — a product's price, a user's email — only touches one row instead of potentially hundreds of duplicated copies scattered across a bloated table.&lt;/p&gt;

&lt;p&gt;Normalized schemas also make caching strategies more predictable. When a piece of data lives in exactly one place, invalidating a cache entry after an update is straightforward. When that same data is duplicated across multiple tables or rows, you either accept stale data somewhere in your system or build complicated invalidation logic to track every copy, which introduces its own bugs.&lt;/p&gt;

&lt;p&gt;None of this means normalization is free, however. Splitting data across more tables means more joins, and joins aren't free either. For read-heavy applications with predictable query patterns, some teams deliberately denormalize specific tables after starting from a normalized design, trading some duplication for fewer joins on their hottest queries. The key phrase there is "after starting from a normalized design" — you want to understand exactly what redundancy you're introducing and why, rather than ending up there by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the Right Balance in Practice
&lt;/h2&gt;

&lt;p&gt;A common mistake among developers newer to database design is treating normalization as an all-or-nothing pursuit, chasing higher normal forms like Boyce-Codd normal form or fourth normal form even in contexts where the added complexity doesn't pay for itself. In most web applications, getting a schema solidly into third normal form covers the vast majority of practical benefits: no repeating groups, no partial dependencies, no transitive dependencies. Beyond that point, the returns diminish quickly for typical CRUD-style applications.&lt;/p&gt;

&lt;p&gt;The more useful skill, in practice, is learning to recognize which parts of your schema genuinely benefit from strict normalization and which parts are reasonable candidates for controlled denormalization. Reporting tables, materialized views, and read replicas built for analytics workloads are often deliberately denormalized, because the priority there is fast reads over a large, relatively static dataset rather than transactional integrity. Your core transactional tables — the ones handling orders, payments, and user accounts — are usually where strict normalization pays off the most, since those are the tables where a stale or duplicated fact can cause real problems.&lt;/p&gt;

&lt;p&gt;Getting comfortable with this balance takes some experience, and it's fine to start conservative. A schema that's slightly over-normalized is usually easier to fix than one that's under-normalized, since you can always denormalize a specific table later once you understand your actual query patterns. Reversing years of accumulated data inconsistency in a poorly normalized production database, on the other hand, is a much harder problem to walk back.&lt;/p&gt;

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

&lt;p&gt;Database normalization isn't an academic exercise reserved for computer science courses; it's a practical discipline that directly affects how fast, reliable, and maintainable your web application's data layer will be. Working through the first three normal forms — eliminating repeating groups, partial dependencies, and transitive dependencies — gives you a schema that's easier to query, cheaper to update, and far less prone to the kind of data drift that causes subtle bugs down the line. Next time you're designing a new table or reviewing an existing schema, take a few minutes to ask whether each column truly belongs where it sits, and you'll likely spot opportunities to simplify before those small inconsistencies turn into real production headaches.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
    </item>
    <item>
      <title>The Rise of Serverless Databases: What Web Developers Need to Know</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Fri, 17 Jul 2026 07:19:42 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/the-rise-of-serverless-databases-what-web-developers-need-to-know-42h2</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/the-rise-of-serverless-databases-what-web-developers-need-to-know-42h2</guid>
      <description>&lt;p&gt;Serverless databases have quietly become one of the most important shifts in how modern web applications handle data. If you've provisioned a traditional &lt;a href="https://hilfan.staff.telkomuniversity.ac.id/mengenal-jenis-jenis-database-materi-penting-dalam-perkuliahan-prodi-it-di-telkom-university/" rel="noopener noreferrer"&gt;database&lt;/a&gt; instance recently, you already know the drill: pick an instance size, guess at future traffic, configure connection pools, and hope you didn't over- or under-provision. Serverless databases remove most of that guesswork by scaling capacity automatically in response to actual demand, billing you for what you use rather than what you reserve. For web developers building anything from a side project to a production SaaS platform, understanding this shift is quickly becoming as essential as understanding REST APIs or containerization.&lt;/p&gt;

&lt;p&gt;This article walks through what serverless databases actually are, why they've gained so much traction, the tradeoffs they introduce, and how to start working with them in a real application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a Database "Serverless"
&lt;/h2&gt;

&lt;p&gt;The term "serverless" is a bit of a misnomer, since there are always servers involved somewhere. What it really refers to is the abstraction of server management away from the developer. A serverless database automatically provisions compute and storage resources as needed, scales those resources up or down based on query load, and can often scale all the way down to zero when the database isn't being used.&lt;/p&gt;

&lt;p&gt;This is fundamentally different from traditional managed databases, where you still choose an instance type, such as a specific number of vCPUs and a fixed amount of RAM, even if a cloud provider handles patching and backups for you. With a serverless database, that instance-sizing decision disappears entirely. The platform handles it dynamically, often within milliseconds, in response to incoming queries.&lt;/p&gt;

&lt;p&gt;Popular examples in this space include Amazon Aurora Serverless, PlanetScale, Neon, and Turso, each of which takes a slightly different architectural approach but shares the same core promise: pay for actual usage, not reserved capacity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scale-to-Zero and Cold Starts
&lt;/h3&gt;

&lt;p&gt;One of the more interesting characteristics of many serverless databases is the ability to scale to zero during periods of inactivity. This is particularly attractive for development environments, staging databases, or low-traffic side projects where paying for an always-on instance feels wasteful.&lt;/p&gt;

&lt;p&gt;The tradeoff is the cold start. When a serverless database has scaled down, and a new request arrives, there's often a brief delay while resources spin back up. For latency-sensitive production workloads, this cold start behavior needs to be understood and tested, since a few hundred milliseconds of added latency on a rare request can matter a great deal for user-facing features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Web Developers Are Adopting Serverless Databases
&lt;/h2&gt;

&lt;p&gt;The appeal isn't purely about cost, though cost is a significant factor. A startup with unpredictable or spiky traffic can end up paying far less for a serverless database than for a permanently provisioned instance sized to handle peak load. But there are other reasons this model has caught on so quickly among web developers specifically.&lt;/p&gt;

&lt;p&gt;Modern web architecture has shifted heavily toward serverless compute platforms like Vercel, Netlify, and AWS Lambda for the application layer itself. Pairing a traditional, connection-heavy relational database with a fleet of serverless functions creates a well-known problem: every function invocation can open a new database connection, and databases have hard limits on how many concurrent connections they can handle. Serverless databases are often built from the ground up to handle this exact pattern, either through HTTP-based query interfaces or built-in connection pooling that eliminates the connection exhaustion problem.&lt;/p&gt;

&lt;p&gt;There's also a developer experience angle that's easy to underestimate. Spinning up a new branch of your database for a feature branch, the way PlanetScale and Neon both support, changes how teams think about schema migrations and testing. Instead of running migrations against a shared staging database and hoping nothing breaks, a developer can branch the database itself, test destructive schema changes in isolation, and merge with confidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Connection Pooling Problem, Illustrated
&lt;/h3&gt;

&lt;p&gt;To understand why this matters so much, it helps to look at what happens without it. A traditional PostgreSQL setup paired with serverless functions might look 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="c1"&gt;// A naive connection pattern that breaks under serverless load&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Each invocation may create a brand new pool&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM users WHERE id = $1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under real traffic, this pattern can spawn far more connections than the underlying database can accept, leading to connection errors during traffic spikes. A serverless-native database typically solves this with an HTTP-based driver instead, as shown below using Neon's serverless driver for Postgres:&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;// Using an HTTP-based serverless driver avoids persistent connections&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;neon&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="s1"&gt;@neondatabase/serverless&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`SELECT * FROM users WHERE id = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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;Because this driver communicates over HTTP rather than maintaining a persistent TCP connection, it sidesteps the connection limit problem almost entirely, which makes it a natural fit for environments like edge functions, where traditional drivers often don't even work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Between Serverless Database Providers
&lt;/h2&gt;

&lt;p&gt;Not all serverless databases are built the same way, and the differences matter depending on what you're building. Aurora Serverless, for example, is essentially a serverless wrapper around a familiar relational engine, which makes it a comfortable choice for teams already invested in the AWS ecosystem and traditional SQL tooling.&lt;/p&gt;

&lt;p&gt;PlanetScale, built on Vitess, focuses heavily on horizontal scalability and schema branching, and has historically been popular for MySQL-based applications that anticipate significant growth. Neon takes a similar branching-first approach but is built specifically for Postgres, including instant support, copy-on-write database branches that make CI testing far cheaper. Turso, meanwhile, is built around libSQL, a fork of SQLite, and is designed for extremely low-latency reads by replicating data close to the edge, which suits read-heavy applications with a global user base.&lt;/p&gt;

&lt;p&gt;The right choice depends less on which platform is "best" and more on which query patterns, existing tooling, and consistency requirements match your application. A team already fluent in Postgres tooling gains little by switching database engines just to get serverless scaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Schema Migration Example
&lt;/h3&gt;

&lt;p&gt;Regardless of provider, most serverless databases still expect you to manage schema through familiar migration tooling. Here's a minimal example using Drizzle ORM against a serverless Postgres connection:&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;// schema.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pgTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&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="s1"&gt;drizzle-orm/pg-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&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="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;defaultNow&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;Migrations still run the way they always have, and the day-to-day experience of writing queries and defining schema barely changes. The serverless part of the equation happens beneath this layer, in how the database physically allocates and releases compute.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tradeoffs Worth Understanding Before You Commit
&lt;/h2&gt;

&lt;p&gt;It would be misleading to present serverless databases as a strict upgrade with no downsides, because that isn't accurate. Cold starts, as mentioned earlier, are a real consideration for latency-sensitive applications, and while providers have worked hard to shrink them, they haven't disappeared entirely.&lt;/p&gt;

&lt;p&gt;Pricing models can also become unpredictable in the opposite direction from what teams expect. A database that's cheap when idle can become expensive under sustained heavy load, since usage-based pricing doesn't cap costs the way a fixed-size instance implicitly does. Teams with steady, high, predictable traffic sometimes find that a traditional provisioned instance is actually cheaper than paying for serverless compute around the clock.&lt;/p&gt;

&lt;p&gt;There's also the question of vendor lock-in and tooling maturity. Branching workflows, HTTP-based drivers, and edge replication are compelling features, but they're also provider-specific, and migrating away from a serverless database platform later can involve more than just changing a connection string. It's worth prototyping with realistic traffic patterns before fully committing a production application to any single serverless database provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started Without Overcommitting
&lt;/h2&gt;

&lt;p&gt;For developers curious about this shift, the lowest-risk starting point is usually a side project or a new feature branch rather than a wholesale migration of an existing production database. Spinning up a free-tier Neon or PlanetScale database, connecting it to a serverless function, and observing how it behaves under realistic load teaches far more than reading documentation alone.&lt;/p&gt;

&lt;p&gt;Pay particular attention to cold start behavior in your specific use case, since this is the characteristic most likely to surprise you in production. Test what happens when your database has been idle for an hour, and a real user request arrives, and decide whether that delay is acceptable for your application. For most CRUD-heavy web applications, it is; for applications with strict sub-100-millisecond latency requirements, it may not be.&lt;/p&gt;

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

&lt;p&gt;Serverless databases represent a genuine architectural shift rather than just a marketing label slapped onto existing products. By automatically scaling compute and storage, eliminating manual capacity planning, and solving the connection-pooling headaches that come with serverless application layers, they address real pain points that web developers have been working around for years. They aren't a universal fix, and cold starts, usage-based pricing, and provider lock-in are all tradeoffs worth weighing seriously before migrating a production system.&lt;/p&gt;

&lt;p&gt;If you're building a new web application today, especially one deployed on a serverless or edge compute platform, it's worth spinning up a serverless database and testing it against your actual traffic patterns rather than assuming a traditional instance is the safer default. The tooling has matured enough that, for a large share of modern web applications, serverless databases are no longer the experimental option — they're becoming the practical one.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
    </item>
    <item>
      <title>Why Your Cloud Strategy Needs Intelligent Automation</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Wed, 15 Jul 2026 02:39:28 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/why-your-cloud-strategy-needs-intelligent-automation-12j0</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/why-your-cloud-strategy-needs-intelligent-automation-12j0</guid>
      <description>&lt;p&gt;Most companies didn't choose their current cloud setup so much as accumulate it. A team spun up a few EC2 instances to hit a deadline, another department signed a contract with a different provider for a data project, and three years later, nobody can draw an accurate diagram of what's actually running. If any of that sounds familiar, you're not alone, and it's exactly why a &lt;a href="https://mie.telkomuniversity.ac.id/en/cloud-manufacturing/" rel="noopener noreferrer"&gt;cloud strategy&lt;/a&gt; built around intelligent automation has become less of a nice-to-have and more of a survival requirement. Public cloud spending is projected to exceed $1.1 trillion in 2026, and a meaningful share of that spend is going toward infrastructure nobody is actively managing. Intelligent automation is the discipline that closes that gap between what you're paying for and what you're actually using.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of Manual Cloud Operations
&lt;/h2&gt;

&lt;p&gt;Manual cloud management doesn't fail loudly. It fails quietly, through a thousand small decisions nobody has time to revisit. An engineer provisions a slightly oversized instance because it's faster than right-sizing it properly, a storage bucket keeps snapshots long after they're needed, and a security group rule written for a one-off test never gets removed. None of these choices looks dangerous in isolation, but industry estimates suggest roughly 30% of cloud infrastructure spend is wasted on overprovisioning and idle resources. That's not a rounding error; for a mid-sized enterprise, it can represent millions of dollars a year sitting idle in the cloud console.&lt;/p&gt;

&lt;p&gt;The human cost compounds the financial one. Site reliability teams end up spending their time on repetitive, low-judgment tasks instead of the architecture work that actually needs their expertise. Every hour spent manually rotating credentials or chasing down an unpatched instance is an hour not spent improving resilience or shipping features. Over time, this creates a kind of operational debt that's harder to see than technical debt in code, but just as expensive to carry.&lt;/p&gt;

&lt;p&gt;There's also a governance dimension that's easy to underestimate. As organizations spread workloads across multiple providers, keeping policies consistent by hand becomes close to impossible. Compliance has become one of the top barriers organizations report when scaling their cloud footprint, and manual processes are a big part of why. When every environment has its own tribal knowledge and undocumented exceptions, audits turn into archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Intelligent Automation Actually Means
&lt;/h2&gt;

&lt;p&gt;It's worth separating intelligent automation from the scripting most teams already do. A cron job that restarts a service at 2 a.m. is automation, but it isn't intelligent in any meaningful sense; it just executes the same command regardless of context. Intelligent automation, by contrast, incorporates monitoring data, historical patterns, and increasingly AI-driven analysis to make decisions and take action with minimal human intervention.&lt;/p&gt;

&lt;p&gt;Gartner's research on infrastructure and operations frames this well: intelligent automation applies AI techniques, including generative AI, to automate decision-making and execute actions rather than simply triggering pre-written scripts. The distinction matters in practice. A traditional automation rule might say, "If CPU usage exceeds 90%, send an alert." An intelligent automation system can instead recognize that the spike follows a predictable weekly pattern, scale resources proactively before the threshold is hit, and only escalate to a human when the pattern genuinely breaks from historical norms.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Reactive Scripts to Proactive Systems
&lt;/h3&gt;

&lt;p&gt;This shift from reactive to proactive is the real value proposition. Reactive automation waits for something to go wrong and then responds. Proactive, intelligent systems use signals from across the environment to anticipate problems before they become incidents. A simple example is autoscaling driven by predictive load modeling rather than static thresholds, which prevents the awkward lag where resources scale up only after users have already experienced slowness.&lt;/p&gt;

&lt;p&gt;Here's a small illustration of the difference using Python and a basic anomaly-detection approach rather than a fixed threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sensitivity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Flags unusual resource usage based on historical mean and
    standard deviation, rather than a hardcoded threshold.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;std_dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;std_dev&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="bp"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;z_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_usage&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;std_dev&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sensitivity&lt;/span&gt;

&lt;span class="c1"&gt;# Example: last 30 days of CPU utilization percentages
&lt;/span&gt;&lt;span class="n"&gt;historical_cpu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;current_reading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;detect_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_reading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;historical_cpu&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Anomaly detected — triggering scale-up workflow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Usage within normal range&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of logic, embedded into a broader orchestration pipeline, is what separates intelligent automation from a static alert rule. It adapts to your actual usage patterns instead of forcing every workload into the same fixed thresholds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Automation Into Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;Intelligent automation works best when it's not bolted on after the fact but built into how infrastructure gets defined in the first place. Teams that manage infrastructure as code already have a natural foundation for this, since policies and configurations are version-controlled and repeatable rather than clicked together manually in a console.&lt;/p&gt;

&lt;p&gt;Consider a Terraform configuration that includes automated tagging and lifecycle policies as a baseline, rather than relying on someone remembering to clean up resources later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"app_server"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0abcdef1234567890"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.medium"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;
    &lt;span class="nx"&gt;Owner&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"platform-team"&lt;/span&gt;
    &lt;span class="nx"&gt;AutoShutdown&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
    &lt;span class="nx"&gt;ReviewDate&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timeadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;"720h"&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_lifecycle_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"expire-old-logs"&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enabled"&lt;/span&gt;

    &lt;span class="nx"&gt;expiration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;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;Neither of these blocks is complicated, but that's the point. Intelligent automation doesn't always mean building elaborate machine learning pipelines. Often it means making sure that cleanup, tagging, and lifecycle rules are enforced automatically, every single time, instead of depending on someone remembering to do it. The "intelligence" comes from designing the system so that the default behavior is the correct behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Start Without Overhauling Everything
&lt;/h2&gt;

&lt;p&gt;Organizations that get automation right rarely start with the most ambitious project on the list. They start with the process that's the most repetitive, the most error-prone, and the most resented by the team doing it manually. Patch management is a common starting point, since it's high-volume, low-judgment, and directly tied to security posture. Cost governance is another good candidate, particularly automated right-sizing recommendations paired with approval workflows so nothing gets resized without a human sign-off.&lt;/p&gt;

&lt;p&gt;It's also worth resisting the instinct to automate everything at once. A phased rollout lets teams build trust in the system's decisions before granting it broader authority. Early implementations often run in a "recommend, don't execute" mode, where the automation flags an action and a human approves it. As confidence grows and the system's track record holds up, more of that approval step can shift to the automation itself, with humans retaining oversight on higher-stakes decisions.&lt;/p&gt;

&lt;p&gt;Multi-cloud environments add another wrinkle worth planning for early. With a large share of enterprises now running workloads across more than one provider, automation tooling that only understands a single cloud's APIs creates blind spots almost immediately. Choosing platform-agnostic orchestration tools, or at minimum standardizing tagging and policy definitions across providers, saves considerable rework later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance and Trust Still Matter
&lt;/h2&gt;

&lt;p&gt;None of this works if automation becomes a black box that nobody fully understands or trusts. Every automated action should be logged, auditable, and reversible where possible. This isn't just a compliance checkbox; it's what makes teams comfortable handing over more responsibility to the system over time. A rollback plan matters as much as the automation rule itself, because the first time an automated action causes an unexpected outage, the team's appetite for further automation can evaporate overnight.&lt;/p&gt;

&lt;p&gt;It's also worth being honest about where AI-driven automation is genuinely ready for production and where it's still maturing. Broader adoption of autonomous AI agents in enterprise environments remains uneven, with many organizations still in pilot phases rather than full production deployment. That's a reasonable, even healthy, place to be. Intelligent automation doesn't require full autonomy to deliver value; a system that catches 80% of routine cleanup and anomaly detection, while escalating genuine edge cases to humans, is already a significant improvement over fully manual operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing It Together
&lt;/h2&gt;

&lt;p&gt;A cloud strategy that doesn't account for intelligent automation is, in practice, a strategy for accumulating cost and complexity indefinitely. The organizations managing this well aren't necessarily the ones with the biggest AI budgets; they're the ones that treated automation as a core part of infrastructure design rather than an afterthought bolted on once things got messy. Start with the repetitive, high-friction processes your team already dreads, build in auditability from day one, and expand automation's authority only as trust in its decisions is earned.&lt;/p&gt;

&lt;p&gt;If your current cloud environment feels more like an accumulation of decisions than a deliberate architecture, that's the signal to begin. Pick one recurring manual process this quarter, whether it's patch management, resource cleanup, or cost governance, and automate it with proper guardrails in place. The compounding savings, both in dollars and in engineering hours, tend to justify the investment far faster than most teams expect.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>automation</category>
    </item>
    <item>
      <title>Set It and Forget It: The Magic of Automatic Cloud Backups</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:58:00 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/set-it-and-forget-it-the-magic-of-automatic-cloud-backups-37le</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/set-it-and-forget-it-the-magic-of-automatic-cloud-backups-37le</guid>
      <description>&lt;p&gt;There's a particular kind of dread that comes from a hard drive click that doesn't sound right, or a laptop that won't wake up after an update. Automatic cloud backups exist precisely to make that moment a non-event instead of a catastrophe. Rather than relying on a person to remember to plug in an external drive or manually upload files before bed, automated systems quietly copy your data to remote storage on a schedule you never have to think about again. This guide walks through how automatic cloud backups actually work, why "set it and forget it" is more achievable than most people assume, and how to build a setup — personal or technical — that holds up when you need it most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Manual Backups Fail (Even With Good Intentions)
&lt;/h2&gt;

&lt;p&gt;Most people who lose data didn't skip backups because they didn't care. They skipped them because the process required remembering to do it. A weekly reminder gets snoozed once, then twice, and eventually stops appearing altogether once the habit breaks.&lt;/p&gt;

&lt;p&gt;Manual backup routines also tend to degrade quietly. An external drive fills up and stops accepting new files, but nobody notices because the backup "worked" the last time someone checked. Automatic systems remove the human failure point entirely by decoupling the backup from anyone's memory or motivation.&lt;/p&gt;

&lt;p&gt;There's also a consistency problem with manual processes. A file saved on Tuesday might not make it into Friday's manual backup if the person forgets which folders changed. Automated tools track changes continuously, so nothing falls through the cracks between backup sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Automatic Cloud Backups Actually Work
&lt;/h2&gt;

&lt;p&gt;At a basic level, an automatic backup tool watches a set of files or directories, detects when something changes, and pushes those changes to remote storage without requiring a person to trigger the action. Most consumer tools like Backblaze, iCloud, or Google Drive's desktop sync handle this invisibly, running as a background process that starts the moment your device boots.&lt;/p&gt;

&lt;p&gt;For more technical setups, the same principle applies, but with more visible machinery. A scheduled job — often a cron job on Linux or macOS, or Task Scheduler on Windows — runs a script at a defined interval. That script typically compresses relevant files, encrypts them if needed, and uploads them to a &lt;a href="https://openlibrarypublications.telkomuniversity.ac.id/index.php/engineering/article/download/28078/26549/55653" rel="noopener noreferrer"&gt;cloud storage&lt;/a&gt; bucket using an API or command-line tool provided by the storage vendor.&lt;/p&gt;

&lt;p&gt;The distinction between full backups and incremental backups matters here. A full backup copies everything every time, which is simple but slow and storage-hungry. An incremental backup only copies what changed since the last run, which is faster and cheaper but requires the tool to track file states accurately. Most mature backup systems default to incremental backups after an initial full copy, striking a balance between reliability and efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Simple Automated Backup with a Cron Job and AWS S3
&lt;/h2&gt;

&lt;p&gt;For anyone comfortable with the command line, a genuinely reliable backup system doesn't require expensive software. A cron job paired with the AWS Command Line Interface can back up a directory to Amazon S3 on a schedule, and it costs pennies a month for typical personal use.&lt;/p&gt;

&lt;p&gt;Here's a basic Bash script that syncs a local folder to an S3 bucket, only uploading files that have changed since the last run:&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="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Configuration&lt;/span&gt;
&lt;span class="nv"&gt;SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/home/user/documents"&lt;/span&gt;
&lt;span class="nv"&gt;BUCKET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-backup-bucket"&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/cloud-backup.log"&lt;/span&gt;

&lt;span class="c"&gt;# Run the sync and log the output&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Backup started: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOURCE_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"s3://&lt;/span&gt;&lt;span class="nv"&gt;$BUCKET_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delete&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s2"&gt;"*.tmp"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s2"&gt;"node_modules/*"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Backup completed successfully: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Backup FAILED: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;aws s3 sync&lt;/code&gt; command is doing the heavy lifting here — it compares the local directory against the bucket and only transfers files that are new or modified, which is what makes this an incremental backup rather than a full copy every time. The &lt;code&gt;--delete&lt;/code&gt; flag keeps the remote bucket in sync with deletions on the local machine, though it's worth thinking carefully about whether that behavior fits your use case, since it means a locally deleted file will also disappear from the backup.&lt;/p&gt;

&lt;p&gt;To make this run automatically, save the script and register it with cron:&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="c"&gt;# Open the crontab editor&lt;/span&gt;
crontab &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Add this line to run the backup every day at 2 AM&lt;/span&gt;
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/user/scripts/cloud-backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that line is saved, the backup runs every night without anyone touching a keyboard. Checking the log file occasionally is a good habit, since even automated systems benefit from an occasional human glance to confirm they're behaving as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption: The Step People Skip and Shouldn't
&lt;/h2&gt;

&lt;p&gt;Uploading files to the cloud automatically is only half the job done responsibly. Data sitting in someone else's storage infrastructure should be encrypted, both to protect against unauthorized access to your provider account and to add a layer of defense if credentials are ever compromised.&lt;/p&gt;

&lt;p&gt;Most cloud storage providers offer server-side encryption by default, which protects data at rest on their infrastructure. But client-side encryption, where files are encrypted before they ever leave your machine, offers stronger guarantees because the provider never has access to your unencrypted data at all.&lt;/p&gt;

&lt;p&gt;Adding this to the earlier script is straightforward with a tool like &lt;code&gt;gpg&lt;/code&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="c"&gt;# Encrypt a file before upload using a symmetric passphrase&lt;/span&gt;
gpg &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--passphrase-file&lt;/span&gt; /home/user/.backup_key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--symmetric&lt;/span&gt; &lt;span class="nt"&gt;--cipher-algo&lt;/span&gt; AES256 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOURCE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/archive.tar.gz.gpg"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOURCE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/archive.tar.gz"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This encrypts the archive locally before the sync command ever touches it, meaning the version sitting in the cloud bucket is unreadable without the passphrase. The tradeoff is that losing the passphrase means losing access to the backup entirely, so storing that key somewhere durable and separate from the backup itself is essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Backup Frequency and Retention Wisely
&lt;/h2&gt;

&lt;p&gt;A backup schedule that runs too infrequently defeats the purpose, but one that runs constantly can waste bandwidth and money without meaningfully improving protection. For most personal use cases, a daily backup during off-peak hours captures the vast majority of meaningful changes without straining resources.&lt;/p&gt;

&lt;p&gt;Retention policy deserves just as much thought as frequency. Keeping every single backup forever sounds safe, but it becomes expensive and unwieldy fast, especially with full backups. A common approach is to keep daily backups for a couple of weeks, weekly backups for a couple of months, and monthly backups for a year or more, deleting anything older automatically through lifecycle rules that most cloud storage providers support natively.&lt;/p&gt;

&lt;p&gt;Amazon S3, for example, allows lifecycle policies that automatically transition older backups to cheaper storage tiers like S3 Glacier, and eventually delete them after a defined period. Setting this up once means the retention strategy enforces itself indefinitely, with no manual cleanup required.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Automatic Backups Don't Protect Against
&lt;/h2&gt;

&lt;p&gt;It's tempting to treat "the backup runs automatically" as equivalent to "my data is safe," but that's not quite accurate. A backup system that syncs continuously will faithfully replicate a ransomware encryption event or an accidental mass deletion just as quickly as it replicates legitimate changes, unless versioning is enabled.&lt;/p&gt;

&lt;p&gt;Versioning, supported by most major cloud providers, keeps prior copies of a file even after it's overwritten or deleted. This is what actually protects against the scenario where automatic sync becomes the vehicle for propagating a mistake rather than preventing one. Enabling versioning on a storage bucket is usually a single configuration change, and it's one of the highest-value settings available in any backup setup.&lt;/p&gt;

&lt;p&gt;It's also worth remembering that automatic backups protect against hardware failure and accidental loss, but they don't replace good account security. If cloud credentials are compromised, an attacker with access to the account can potentially delete both the live data and its backups. Multi-factor authentication on the storage account itself closes this gap and is worth the two extra minutes of setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Restores, Not Just Backups
&lt;/h2&gt;

&lt;p&gt;The uncomfortable truth about backup systems is that most people only discover whether theirs works at the exact moment they need it, which is the worst possible time to find out it doesn't. A backup that has never been restored is, in a meaningful sense, unverified.&lt;/p&gt;

&lt;p&gt;Periodically pulling a file back down from cloud storage and confirming it opens correctly costs a few minutes and eliminates an enormous amount of risk. This is especially true for encrypted backups, where a corrupted key or a misconfigured passphrase file can silently render months of backups useless without anyone noticing until it's too late.&lt;/p&gt;

&lt;p&gt;Teams managing backups for critical infrastructure often schedule quarterly restore drills specifically because "set it and forget it" should apply to the backup process itself, not to verifying that it works. The automation handles the tedious part; a periodic human check handles the part that actually confirms peace of mind is warranted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing It All Together
&lt;/h2&gt;

&lt;p&gt;Automatic cloud backups turn data protection from a chore that depends on memory and discipline into a background process that runs whether or not anyone is paying attention. Whether that means enabling a consumer tool like Backblaze on a home laptop or writing a cron-scheduled script that syncs to S3 with encryption and lifecycle rules, the underlying goal is the same: remove the human failure point from the equation.&lt;/p&gt;

&lt;p&gt;The setup takes an afternoon. The peace of mind lasts as long as the system keeps running quietly in the background, which — if configured correctly — is indefinitely. Start with whichever piece feels most approachable, whether that's turning on a built-in sync tool or writing your first backup script, and build outward from there. Your future self, staring at a drive that won't spin up, will be glad you did.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>backup</category>
      <category>aws</category>
      <category>cloudstorage</category>
    </item>
    <item>
      <title>Smarter Syncing: The Rise of AI in Your Cloud Storage</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:48:46 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/smarter-syncing-the-rise-of-ai-in-your-cloud-storage-4p3o</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/smarter-syncing-the-rise-of-ai-in-your-cloud-storage-4p3o</guid>
      <description>&lt;p&gt;A few years ago, cloud storage was a simple proposition: you dragged a file into a folder, and it appeared on your other devices. That was the whole pitch. Today, &lt;a href="https://staff.telkomuniversity.ac.id/2025/10/22/agentic-ais-hidden-data-trail-and-how-to-shrink-it/" rel="noopener noreferrer"&gt;&lt;strong&gt;AI in cloud storage&lt;/strong&gt;&lt;/a&gt; is quietly rewriting that promise, turning passive file repositories into systems that predict what you need, compress data more intelligently, and catch problems before you ever notice them. If you've synced a laptop and a phone in the last year, you've probably already benefited from this shift without realizing it.&lt;/p&gt;

&lt;p&gt;This change didn't happen overnight, and it isn't just a marketing buzzword bolted onto old infrastructure. Machine learning models are now embedded directly into the sync engines that move your files between devices, deciding in real time what to prioritize, what to defer, and what looks suspicious enough to flag. Understanding how this works — and where it's headed — helps you make better decisions about which services you trust with your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Traditional Sync Started Falling Short
&lt;/h2&gt;

&lt;p&gt;Classic file synchronization relies on straightforward triggers. A file changes, a timestamp updates, and the system pushes that change to every connected device. This approach works fine for a single user with a handful of files, but it breaks down at scale. Teams sharing thousands of documents, photographers uploading gigabytes of RAW images, and developers pushing frequent code changes all expose the same weakness: dumb sync treats every byte as equally urgent.&lt;/p&gt;

&lt;p&gt;The result is bandwidth waste, battery drain on mobile devices, and sync conflicts that leave users staring at two versions of the same spreadsheet. Storage providers noticed that the bottleneck wasn't disk space anymore — it was decision-making. Someone, or something, needed to decide what mattered most in the moment, and rule-based logic couldn't keep up with how unpredictable real usage patterns actually are.&lt;/p&gt;

&lt;p&gt;This is precisely the gap that machine learning was well-suited to fill. Instead of hardcoding rules like "always sync photos first," providers could train models on actual behavior and let the system adapt on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Machine Learning Actually Improves Syncing
&lt;/h2&gt;

&lt;p&gt;At the core of AI-enhanced sync is a prioritization engine. Rather than processing files in the order they were modified, the system learns which files a user is likely to open next and moves those to the front of the queue. If you tend to open your presentation folder every Monday morning, a well-trained model notices that pattern and pre-fetches those files before you even ask.&lt;/p&gt;

&lt;p&gt;Predictive caching works similarly on the download side. Services like Dropbox and Google Drive have published research on using usage signals — time of day, device type, recent activity — to decide which files to keep readily available locally versus which can stay purely in the cloud. This isn't guesswork dressed up as intelligence; it's a genuine reduction in wasted transfers, and it shows up as faster load times for the files people actually reach for.&lt;/p&gt;

&lt;p&gt;Compression is another area where AI has made a measurable difference. Traditional compression algorithms treat all data the same way, applying a fixed method regardless of content. Neural network-based compression, by contrast, can recognize that a folder full of similar screenshots or near-duplicate photos has exploitable redundancy that generic algorithms miss. The savings compound quickly for anyone storing large media libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delta Sync Gets Smarter Too
&lt;/h3&gt;

&lt;p&gt;Delta syncing — sending only the changed portion of a file rather than the whole thing — has existed for years, but AI has sharpened its precision. Below is a simplified example of how a modern sync client might use a rolling hash to detect meaningful change blocks before deciding what to transmit.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rolling_checksum&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="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Generate checksums for fixed-size blocks to detect changed regions.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;checksums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;checksums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;checksums&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_changed_blocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_checksums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_checksums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compare block checksums and return indices that differ.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_checksums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_checksums&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production system layers a prediction model on top of this basic block comparison, weighting which changed blocks are worth syncing immediately based on how likely the user is to need them soon. The checksum logic stays deterministic and auditable, while the AI layer only influences scheduling and priority — a distinction worth understanding if you're evaluating how much "black box" behavior is actually involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and Anomaly Detection
&lt;/h2&gt;

&lt;p&gt;Beyond speed, AI has become central to how cloud storage providers detect threats. Ransomware behaves in a fairly recognizable way: it encrypts large numbers of files in a short window, often changing file extensions or headers in patterns that differ sharply from normal user activity. Anomaly detection models trained on typical sync behavior can flag this kind of mass modification and pause syncing before the damage propagates to every connected device.&lt;/p&gt;

&lt;p&gt;Microsoft OneDrive and Google Drive have both discussed anomaly-based ransomware detection features in this vein, where a sudden spike in file changes triggers an automatic hold and a user notification. This is a meaningful shift from older backup strategies that only helped after the fact, once you'd already lost your most recent work to an encrypted mess.&lt;/p&gt;

&lt;p&gt;Account-level anomaly detection follows a similar logic. Login attempts from unfamiliar locations, unusual access patterns to sensitive folders, or bulk downloads that don't match a user's history can all trigger additional verification steps. None of this requires the system to understand what your files mean — it just needs to notice when behavior deviates sharply from an established baseline.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Anomaly Scoring Example
&lt;/h3&gt;

&lt;p&gt;Here's a stripped-down illustration of how a sync service might score recent activity against a rolling baseline, using nothing more exotic than a z-score threshold.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;anomaly_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_change_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return how many standard deviations recent activity is from the baseline.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="n"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stdev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stdev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stdev&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="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_change_count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;stdev&lt;/span&gt;

&lt;span class="c1"&gt;# Example: history of daily file changes over two weeks
&lt;/span&gt;&lt;span class="n"&gt;daily_history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;today_changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;340&lt;/span&gt;  &lt;span class="c1"&gt;# a sudden spike
&lt;/span&gt;
&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;anomaly_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today_changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;daily_history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Anomaly detected — pausing automatic sync for review.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real systems are naturally more sophisticated, layering in contextual signals like file type, time of day, and account history, but the underlying principle — comparing current behavior against a learned baseline — is the same idea scaled up with more data and better models.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-offs Nobody Talks About Enough
&lt;/h2&gt;

&lt;p&gt;It would be misleading to present this shift as purely upside-down. AI-driven sync systems need behavioral data to function, which means providers are collecting more granular usage patterns than a simple "last modified" timestamp ever required. Users who care deeply about privacy should read the fine print on what activity signals are retained and for how long, since prediction models are only as good as the data feeding them.&lt;/p&gt;

&lt;p&gt;False positives are another real cost. An anomaly detector tuned aggressively enough to catch ransomware quickly will also occasionally flag a legitimate bulk edit — a photographer re-tagging an entire shoot, for instance — and pause syncing unnecessarily. Providers are still tuning the balance between responsiveness and annoyance, and it shows in user complaints about sync pauses that turn out to be false alarms.&lt;/p&gt;

&lt;p&gt;There's also a quieter concern about vendor lock-in. The more a storage provider's value comes from its proprietary prediction models rather than raw storage capacity, the harder it becomes to switch providers without losing the "smart" behavior you've gotten used to. That's a genuine trade-off worth weighing against the convenience gains, especially for organizations planning a multi-year storage strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for How You Choose a Provider
&lt;/h2&gt;

&lt;p&gt;If you're evaluating cloud storage options today, it's worth looking past marketing claims about "AI-powered" features and asking specific questions. Does the predictive caching actually reduce your wait times, or is it mostly invisible background optimization? How transparent is the provider about what anomaly detection triggers a sync pause, and how easy is it to override a false positive? These are the details that separate genuinely useful intelligence from a feature checkbox.&lt;/p&gt;

&lt;p&gt;For most individual users, the practical benefit will show up as fewer stalls, faster access to recently used files, and a safety net against the worst ransomware scenarios. For teams and businesses, the calculation is more nuanced, weighing data governance requirements against the operational efficiency AI-driven sync can provide.&lt;/p&gt;

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

&lt;p&gt;AI has moved cloud storage well past its original job of simply keeping files in the same place across devices. Prioritization models, smarter compression, delta sync improvements, and anomaly-based security have combined to make the experience faster and safer, even if most of that work happens invisibly in the background. The trade-offs around data collection and false positives are real and worth understanding, but for most users, the shift toward intelligent syncing has been a net improvement rather than a gimmick.&lt;/p&gt;

&lt;p&gt;If you haven't checked what your current provider actually does under the hood, it's worth a few minutes to look. Review your storage settings, see what anomaly detection or predictive sync features are already enabled, and decide whether they match how you actually work.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>ai</category>
      <category>storage</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>The Shift to the Cloud: How Distributed Databases are Powering the Modern Internet</title>
      <dc:creator>Fu'ad Husnan</dc:creator>
      <pubDate>Fri, 10 Jul 2026 04:27:28 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/the-shift-to-the-cloud-how-distributed-databases-are-powering-the-modern-internet-3l57</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/the-shift-to-the-cloud-how-distributed-databases-are-powering-the-modern-internet-3l57</guid>
      <description>&lt;p&gt;Every time you refresh a social media feed, check a bank balance, or add an item to a shopping cart, a distributed database is quietly doing the heavy lifting behind the scenes. The shift to the cloud has fundamentally rewired how applications store and retrieve data, moving teams away from single, monolithic servers and toward systems designed to run across dozens or even hundreds of machines at once. This transition didn't happen overnight, and it wasn't driven by hype. It happened because traditional databases simply couldn't keep up with the scale, availability, and global reach that modern applications demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Traditional Databases Started to Buckle
&lt;/h2&gt;

&lt;p&gt;For decades, a single powerful server running a relational database was enough. You'd scale up by buying a bigger machine, adding more RAM, or upgrading the CPU. This approach, known as vertical scaling, worked fine when your user base was measured in thousands rather than millions.&lt;/p&gt;

&lt;p&gt;The problem is that vertical scaling has a hard ceiling. There's only so much RAM you can cram into one box, and eventually the cost of that next upgrade becomes absurd compared to just adding more machines. Worse, a single server is also a single point of failure. If that machine goes down, so does your entire application, and no amount of hardware spending fixes that fundamental fragility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docif.telkomuniversity.ac.id/aplikasi-berbasis-web-pengertian-jenis-contoh-keunggulan/" rel="noopener noreferrer"&gt;Cloud computing&lt;/a&gt; exposed this limitation just as internet traffic was exploding. Companies like Amazon and Google were processing requests from users scattered across continents, and no single data center, let alone a single server, could serve all of them with acceptable speed. Latency became a business problem, not just a technical inconvenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Makes a Database "Distributed"
&lt;/h2&gt;

&lt;p&gt;A distributed database spreads data across multiple physical nodes, which might sit in different racks, different data centers, or different continents entirely. Instead of one machine owning all the data, the system partitions information and replicates it, so that no single node is a bottleneck or a liability.&lt;/p&gt;

&lt;p&gt;This isn't just about copying data everywhere for safety, although replication does provide fault tolerance. It's also about sharding, which means splitting a dataset into smaller pieces distributed across nodes based on some key, like a user ID or a geographic region. A well-sharded system lets you add more nodes as your data grows, rather than hitting a wall and needing an entirely new architecture.&lt;/p&gt;

&lt;p&gt;The tricky part is that once data lives in multiple places, you have to answer a hard question: what happens when two nodes disagree about the current state of a piece of data? This is where the real engineering complexity begins, and it's the reason distributed databases took years to mature into production-ready tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  The CAP Theorem and Why Trade-offs Are Unavoidable
&lt;/h3&gt;

&lt;p&gt;Any conversation about distributed databases eventually runs into the CAP theorem, which states that a distributed system can only guarantee two of three properties at any given time: consistency, availability, and partition tolerance. Since network partitions are a fact of life in distributed systems, the real choice most engineers face is between consistency and availability when something goes wrong.&lt;/p&gt;

&lt;p&gt;Systems like traditional relational databases running in a distributed configuration often lean toward consistency, meaning every read reflects the most recent write, even if that means some requests get delayed or rejected during a network hiccup. Systems like Cassandra or DynamoDB often lean toward availability, meaning the system will always respond, even if that response is occasionally a few seconds out of date.&lt;/p&gt;

&lt;p&gt;Neither choice is universally correct. A banking ledger probably needs strong consistency, because showing an incorrect balance is unacceptable. A social media-like counter can tolerate a moment of staleness because nobody notices if the count is off by one for a second. Understanding this trade-off is the single most important mental model for anyone working with distributed systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Popular Distributed Database Architectures
&lt;/h2&gt;

&lt;p&gt;The market has settled around a few dominant patterns, each suited to different workloads. NoSQL document stores like MongoDB gained massive popularity because they let developers store flexible, JSON-like documents without rigid schemas, which pairs naturally with agile development and rapidly changing application requirements.&lt;/p&gt;

&lt;p&gt;Wide-column stores like Apache Cassandra and Google's Bigtable were built specifically for write-heavy workloads at massive scale, the kind you see with time-series data, sensor logs, or activity feeds. These systems sacrifice some query flexibility in exchange for extremely fast writes and horizontal scalability that can stretch across thousands of nodes without breaking a sweat.&lt;/p&gt;

&lt;p&gt;Then there's the newer category of distributed SQL databases, sometimes called NewSQL, exemplified by systems like CockroachDB and Google Spanner. These aim to give developers the familiar guarantees of a relational database, including strong consistency and SQL query support, while still scaling horizontally like a NoSQL system. This category exists because plenty of teams love the safety of relational models but refuse to give up the scalability that made NoSQL attractive in the first place.&lt;/p&gt;

&lt;p&gt;Here's a simplified example of how you might define a Cassandra table optimized for time-series sensor data, where writes need to be fast and reads are typically scoped to a specific device and time range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sensor_readings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;device_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reading_time&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;humidity&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reading_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;CLUSTERING&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading_time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the primary key combines &lt;code&gt;device_id&lt;/code&gt; and &lt;code&gt;reading_time&lt;/code&gt;. This is the partition key design at work: all readings for a single device land on the same partition, making it fast to query recent readings for that device, while spreading readings across many different devices horizontally across the cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Replication Keeps Data Alive Across Regions
&lt;/h2&gt;

&lt;p&gt;Replication is what allows a distributed database to survive the loss of an entire data center without losing data or going offline. Most systems support some flavor of leader-follower replication, where writes go to a primary node and then propagate to replicas, or multi-leader replication, where multiple nodes can accept writes simultaneously and reconcile differences afterward.&lt;/p&gt;

&lt;p&gt;The reconciliation step is where things get genuinely interesting. When two nodes accept conflicting writes to the same piece of data, the system needs a deterministic way to decide which write wins, or how to merge them. Techniques like vector clocks, last-write-wins timestamps, and conflict-free replicated data types, often abbreviated as CRDTs, have all emerged as solutions to this exact problem.&lt;/p&gt;

&lt;p&gt;Here's a small Python example simulating a basic last-write-wins conflict resolution strategy, the kind of logic that underpins simpler distributed systems:&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;resolve_conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;write_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Resolves a conflict between two writes to the same key
    using a last-write-wins strategy based on timestamp.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;write_a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;write_b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;write_a&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;write_b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;write_a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;write_b&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Tie-breaker: use node ID to guarantee determinism
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;write_a&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;write_a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;write_b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;write_b&lt;/span&gt;

&lt;span class="c1"&gt;# Example writes from two different nodes
&lt;/span&gt;&lt;span class="n"&gt;write_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1718000001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node-a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;write_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inactive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1718000005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node-b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolve_conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;write_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Resolved value: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last-write-wins is simple to implement, but it's worth noting that it can silently discard legitimate updates if clocks aren't perfectly synchronized across nodes. Production systems that care deeply about correctness often invest in more sophisticated approaches, or lean on hardware-assisted clock synchronization like Google Spanner's TrueTime API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Managed Cloud Database Boom
&lt;/h2&gt;

&lt;p&gt;One of the biggest shifts in the last several years hasn't been in the database technology itself, but in who operates it. Running a distributed database used to require a dedicated infrastructure team capable of handling node failures, capacity planning, and version upgrades at 3 a.m. Managed database services from AWS, Google Cloud, and Azure have largely removed that burden.&lt;/p&gt;

&lt;p&gt;Services like Amazon Aurora, Google Cloud Spanner, and Azure Cosmos DB let engineering teams get the benefits of a distributed database without hiring a specialized operations team to babysit it. This has genuinely democratized access to database technology that was once reserved for companies with the scale and budget of Google or Amazon. A five-person startup can now spin up a globally distributed database in an afternoon, something that would have required months of infrastructure work a decade ago.&lt;/p&gt;

&lt;p&gt;This shift also changed how teams think about cost. Instead of provisioning for peak capacity year-round, many managed services now offer usage-based pricing that scales down during quiet periods. That flexibility matters enormously for businesses with seasonal or unpredictable traffic patterns, since they're no longer paying for idle servers most of the year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Challenges Teams Still Run Into
&lt;/h2&gt;

&lt;p&gt;None of this comes for free, and it's worth being honest about the trade-offs rather than pretending distributed databases are a strictly better version of a single-server setup. Debugging becomes significantly harder when a query touches multiple nodes, because the failure might be in the network, in one specific replica, or in the coordination logic itself, rather than in your application code.&lt;/p&gt;

&lt;p&gt;Latency between regions is also a persistent challenge. If your database is distributed across the United States, Europe, and Asia for redundancy, a write that requires confirmation from a quorum of nodes in different regions will always be slower than a write to a single local server. Teams often end up making deliberate architectural choices, like keeping certain latency-sensitive data regional rather than fully globalized, to work around this reality.&lt;/p&gt;

&lt;p&gt;Cost is another area where distributed systems can surprise teams. Cross-region data transfer fees, the overhead of maintaining multiple replicas, and the operational complexity of monitoring a large cluster all add up. It's not unusual for a team to migrate to a distributed database expecting savings and instead find their cloud bill has grown, simply because they're now paying for redundancy and geographic reach they didn't previously have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Is Heading Next
&lt;/h2&gt;

&lt;p&gt;The next phase of this shift seems to be about making distributed systems feel less distributed from a developer's perspective. Tools and platforms are increasingly hiding the complexity of sharding, replication, and conflict resolution behind simpler APIs, so that a developer can write what looks like ordinary SQL and let the underlying system handle the distributed mechanics.&lt;/p&gt;

&lt;p&gt;Edge computing is also pulling database technology even closer to end users, with providers now offering databases that replicate data to points of presence near where requests originate, shaving milliseconds off response times for globally distributed user bases. As applications continue to expect instant responsiveness regardless of where a user happens to be sitting, this trend toward pushing data closer to the edge is likely to keep accelerating.&lt;/p&gt;

&lt;p&gt;If you're building anything today that expects to scale beyond a single region or handle serious concurrent traffic, understanding these systems isn't optional anymore. Start by getting comfortable with the CAP theorem trade-offs, experiment with a managed distributed database on a small project, and pay close attention to how your data access patterns interact with partitioning and replication. The internet runs on this architecture now, and the teams who understand it well are the ones building the applications that stay online when it matters most.&lt;/p&gt;

</description>
      <category>database</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
