<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ MathJax - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ MathJax - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 22:24:18 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/mathjax/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Apply Academic Theories to Human-Centered Web Design [Full Handbook] ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever abandoned an app right at the sign‑up page? Or felt uneasy navigating a website because the buttons were scattered randomly, the colors clashed, and the layout felt confusing and unneces ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-apply-academic-theories-to-human-centered-web-design-handbook/</link>
                <guid isPermaLink="false">69fe29e9f239332df4f7cd02</guid>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UX ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ux design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ user experience ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Great John ]]>
                </dc:creator>
                <pubDate>Fri, 08 May 2026 18:22:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d7621fda-83a6-460e-aa38-bce970d4a655.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever abandoned an app right at the sign‑up page? Or felt uneasy navigating a website because the buttons were scattered randomly, the colors clashed, and the layout felt confusing and unnecessarily complex?</p>
<p>Maybe you were asked to complete twenty fields in one go. You carefully filled everything out, hit Submit — and only then were you told that your password didn't meet some hidden, unspoken requirement. A requirement that was never communicated upfront.</p>
<p>Instead of helpful guidance, you were met with a vague message: “Invalid input." Invalid how, you wonder?</p>
<p>Required fields weren’t marked. There was no real‑time validation. No helpful red outline showing which field was wrong. Just a generic prompt telling you to “go back and correct missing information,” as if you’re supposed to magically know what the system wants.</p>
<p>So you scroll.</p>
<p>You search.</p>
<p>You guess.</p>
<p>And you're now getting frustrated.</p>
<p>The reason you're frustrated is simple: no one enjoys repeating a task they thought they had already completed — especially when the mistakes could've been prevented with clear guidance along the way.</p>
<p>You manage to fill in the form and you tap the Submit button.</p>
<p>Nothing happens.</p>
<p>No loading spinner.</p>
<p>No subtle animation.</p>
<p>No confirmation message.</p>
<p>No success screen.</p>
<p>Just silence. For a brief moment, you’re left wondering: Did it go through? So you tap again. And maybe… one more time.</p>
<p>At this point, you become fed up and you either postpone the signup process to when you have the time, or you may not ever return.</p>
<p>Even if you haven’t experienced this exact scenario, you’ve almost certainly felt the same kind of friction: that moment when a digital interface makes you pause, hesitate, or wonder what you’re supposed to do next.</p>
<p>These frustrations often arise because frontend developers either overlook or are unaware of the essential design principles and theories that underpin a smooth, intuitive user experience.</p>
<p>As a frontend developer, your interface should minimise cognitive load, provide immediate clarity, and guide users effortlessly through every task.</p>
<p>In this handbook, I'll introduce the academic theories that should inform and elevate your frontend decisions.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-10-fittss-law">1.0 Fitts’s Law:</a></p>
<ul>
<li><p><a href="#heading-11-use-padding-wisely">1.1 Use padding wisely</a></p>
</li>
<li><p><a href="#heading-12-use-infinite-targets">1.2 Use infinite targets</a></p>
</li>
<li><p><a href="#heading-design-takeaway-from-fitts-law">Design Takeaway from Fitts Law:</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-20-hicks-law">2.0 Hick's Law:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-hicks-law">Design Takeaway from Hick's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-30-gestalt-principles">3.0 Gestalt Principles:</a></p>
<ul>
<li><p><a href="#heading-key-gestalt-principles">Key Gestalt Principles:</a></p>
</li>
<li><p><a href="#heading-31-proximity">3.1 Proximity</a></p>
</li>
<li><p><a href="#heading-32-similarity">3.2 Similarity</a></p>
</li>
<li><p><a href="#heading-33-continuity">3.3 Continuity</a></p>
</li>
<li><p><a href="#heading-34-closure">3.4 Closure</a></p>
</li>
<li><p><a href="#heading-35-figureground">3.5 Figure/Ground</a></p>
</li>
<li><p><a href="#heading-36-common-fate">3.6 Common Fate</a></p>
</li>
<li><p><a href="#heading-37-focal-point">3.7 Focal Point</a></p>
</li>
<li><p><a href="#heading-design-takeaways-from-the-gestalt-principles">Design Takeaways from the Gestalt Principles</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-40-von-restorff-effect-the-isolation-effect">4.0 Von Restorff Effect (The Isolation Effect):</a></p>
<ul>
<li><a href="#heading-design-takeway-from-von-restorff">Design takeway from Von Restorff</a></li>
</ul>
</li>
<li><p><a href="#heading-50-jakobs-law">5.0 Jakob’s Law</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-jakobs-law">Design Takeaway from Jakob's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-60-millers-law">6.0 Miller’s Law</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-millers-law">Design Takeaway from Miller's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-70-the-goal-gradient-hypothesis">7.0 The Goal-Gradient Hypothesis</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-goal-gradient-hypothesis">Design Takeaway from Goal-Gradient Hypothesis</a></li>
</ul>
</li>
<li><p><a href="#heading-80-zeigarnik-effect">8.0 Zeigarnik Effect</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-zeigarnik-effect">Design Takeaway from Zeigarnik Effect</a></li>
</ul>
</li>
<li><p><a href="#heading-90-teslas-law">9.0 Tesla’s Law:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-teslas-law">Design Takeaway from Tesla's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-100-peak-end-rule">10.0 Peak End Rule:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-peak-end-rule">Design takeaway from Peak End Rule</a></li>
</ul>
</li>
<li><p><a href="#heading-110-postels-law">11.0 Postel’s Law:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-postels-law">Design Takeaway from Postel's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-120-doherty-threshold">12.0 Doherty Threshold:</a></p>
<ul>
<li><a href="#heading-design-takeaways-from-doherty-threshold">Design Takeaways from Doherty Threshold</a></li>
</ul>
</li>
<li><p><a href="#heading-130-serial-position-effect-primacy-and-recency">13.0 Serial Position Effect (Primacy and Recency):</a></p>
<ul>
<li><a href="#heading-design-takeaways-serial-position-effect">Design Takeaways Serial Position Effect</a></li>
</ul>
</li>
<li><p><a href="#heading-140-occams-razor">14.0 Occam’s Razor:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-occams-razor">Design Takeaway from Occam's Razor</a></li>
</ul>
</li>
<li><p><a href="#heading-150-parkinsons-law">15.0 Parkinson's Law</a></p>
<ul>
<li><a href="#heading-design-takeaway-for-parkinsons-law">Design Takeaway for Parkinson's law</a></li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-references">References</a></p>
</li>
</ul>
<p>You might wonder what academic theories have to do with frontend development.</p>
<p>The answer is simple. Academic theories aren't abstract ideas. There are the result of rigorous scientific investigation — controlled experiments, validated models, and decades of research into how humans think, learn, perceive, and interact with information.</p>
<p>Because these theories are grounded in evidence rather than opinion, they offer reliable guidance for building interfaces that align with how the human brain actually processes information.</p>
<p>Applying them to frontend development means you're not designing by guesswork or personal preference. Instead, you're applying tested, scientific insights to create clearer, faster, more humane user experiences.</p>
<p>In other words, when you build with academic theory in mind, your frontend becomes more than just visually appealing — it becomes cognitively efficient, behaviourally aligned, and measurably easier for users to navigate.</p>
<p>You can use the following laws and principles to guide your development work. Let’s start by looking at Fitt’s law.</p>
<h2 id="heading-10-fittss-law"><strong>1.0 Fitts’s Law:</strong></h2>
<p>Fitts’s law is the brainchild of Paul Fitts. He was among the early psychologists who recognised that many human errors result from flawed design rather than simple human weakness.</p>
<p>During World War II, he studied airplane cockpit layouts and concluded that numerous incidents attributed to pilot error were actually caused by poor design decisions (Hall, 2023; Budiu, 2022).</p>
<p>Here's the formula:</p>
<p>$$T = a + b \cdot \log_2\left(1 + \frac{D}{W}\right)$$</p>
<p>T = Movement Time</p>
<p>D = Distance to the target</p>
<p>W = Width (size) of the target</p>
<p>a, b = Empirically determined constants</p>
<p>Based on his findings, Fitts postulated that the time required to acquire/reach a target is determined by the distance to the target and the size of the target.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/7d2699bd-4878-4ab0-8669-21616cb8faf1.png" alt="7d2699bd-4878-4ab0-8669-21616cb8faf1" style="display:block;margin:0 auto" width="691" height="244" loading="lazy">

<p><em>Fig 1.0: Illustration of Fitts Law.</em></p>
<p>From the above, between Target B and Target C, it will be faster to interact with Target C than Target B simply because of the distance (Target B is farther away). Interestingly, though Target A and Target C are at the same distance, Target C will still be faster to interact with and less error-prone because of its larger size.</p>
<p>In simple terms, Fitt’s Law tells us that the time required to move to a target depends on two main factors: the distance to the target and the size of the target. The farther away an element is, the longer it takes to reach. The smaller it is, the more precision it demands, which increases the interaction time and the likelihood of errors.</p>
<p>Conversely, closer and larger targets reduce cognitive load, motor effort, and frustration.</p>
<p>In a nutshell, Fitts’s main message to developers is to reduce the distance users must travel on the screen and to make important buttons large and visually dominant.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/bc293949-cadc-46b2-892d-bdfa1dfc6db3.jpg" alt="bc293949-cadc-46b2-892d-bdfa1dfc6db3" style="display:block;margin:0 auto" width="928" height="664" loading="lazy">

<p><em>Fig 1.1: Showing Call-to-Action buttons are the largest and most visually prominent elements on each screen.</em></p>
<p>From the image above, you can see that the Call-to-Action buttons on each of the screens are the most visually dominant button and largest in size. They're also placed within the natural region. This makes them faster/easier to interact with.</p>
<p>You should also place your Call-to-Action button within the natural zone. This is a zone on a mobile phone where it's easy to reach with the thumb (as most people use their thumbs to select things on a phone screen). Here's a diagram showing the "natural zone" on a typical smartphone. It's much faster for a user to interact within the "natural zone" than the "hard zone" (see figure).</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/e98d8d91-b4e6-4b4a-96e5-2524a6e09028.png" alt="e98d8d91-b4e6-4b4a-96e5-2524a6e09028" style="display:block;margin:0 auto" width="1536" height="1024" loading="lazy">

<p><em>Fig 1.2: Showing three different zones for buttons placement (natural, stretching and hard region)</em></p>
<h3 id="heading-11-use-padding-wisely">1.1 Use Padding Wisely</h3>
<p>Fitts' law can be applied to your development by increasing padding wisely. You can also use padding to increase the interactive area. By doing this, you're increasing the size of the targets.</p>
<p>This is important, because imagine a menu that disappears the moment your cursor drifts a few inches away. You’re weren't trying to close it — you simply moved slightly, and suddenly the entire menu collapses. That tiny slip forces you to start the interaction all over again. It’s a small mistake, but it creates a disproportionately frustrating experience.</p>
<p>This happens because the interactive area is too narrow.</p>
<p>That’s why effective padding — or more broadly, generous interactive zones — is essential. By increasing the clickable or hoverable area around a menu, you are increasing the size of the targets, which makes the interaction more stable, more forgiving, and far less cognitively demanding.</p>
<p>This ensures users can move naturally without fear of accidentally “falling off” the target.</p>
<h3 id="heading-12-use-infinite-targets">1.2 Use Infinite Targets</h3>
<p>Another fundamental principle that emerges from Fitt’s Law is the idea of infinite targets. When an interface element is placed at the very edge or corner of a screen, it becomes effectively “infinite” because the cursor can't move beyond the screen boundary. The edge acts as a physical barrier, allowing the user to fling the mouse in that direction without precision or careful aiming.</p>
<p>As a result, corners and edges become the fastest, easiest, and most reliable places for users to access important controls.</p>
<p>This is why operating systems such as Apple’s macOS and Microsoft Windows position their most essential menus and buttons at these locations. The macOS Apple Menu sits in the top‑left corner, Windows historically placed the Start button in the bottom‑left corner, and both systems anchor taskbars, docks, and notification areas along screen edges.</p>
<p>These placements reduce cognitive load, minimise motor effort, and increase interaction speed because users do not need to slow down or correct their cursor movement. The screen itself “catches” the pointer.</p>
<p>In essence, infinite targets transform small interface elements into large, easy‑to‑hit zones simply by leveraging the geometry of the screen.</p>
<p>What this means for you: place your most important and frequently used actions where users can reach them with the least effort. Screen edges and corners act as natural stopping points, meaning users can't overshoot them.</p>
<h3 id="heading-design-takeaways-from-fitts-law">Design Takeaways from Fitts Law:</h3>
<p><strong>Place Primary Actions Where the Task Ends:</strong><br>Placing a submit button at the top‑right forces users to travel all the way back after completing a long form. This increases interaction cost and breaks flow. The best place for a submit button is at the bottom of the form — exactly where the user finishes the task. This aligns with natural reading and interaction patterns.</p>
<p><strong>Keep Related Actions Physically Close:</strong><br>Separating “Add to Cart” and “Check Out” across opposite sides of the screen forces unnecessary thumb movement. Group related actions to reduce effort and speed up decisions.</p>
<p><strong>Make Primary Targets Large and Visually Dominant:</strong><br>Your main CTAs (“Subscribe Now,” “Pay Now,” “Create Account,” “Sign Up”) should be the most recognisable elements on the screen. Large, high‑contrast targets reduce errors and improve speed.</p>
<p><strong>Place High‑Value Actions at Screen Edges and Corners:</strong><br>Edges and corners act as “infinite targets” because the cursor can’t overshoot them. This makes them the fastest, easiest, and most reliable places for critical controls.</p>
<p>A tiny icon in the middle of the screen is hard to hit. The same icon placed at an edge becomes effectively huge because the boundary “catches” the pointer. Also, actions like navigation, primary CTAs, or global controls should live where users can reach them with minimal effort. Avoid burying important actions in the centre of the screen.</p>
<p><strong>Increase Target Size With Generous Padding:</strong><br>Small interactive zones force users to aim with pixel‑level precision. Adding padding expands the clickable or hoverable area, making interactions easier, faster, and more forgiving.</p>
<p><strong>Prevent Accidental “Fall‑Off” With Larger Hit Areas:</strong><br>Menus that collapse the moment the cursor drifts slightly create frustration. A wider interactive zone keeps the menu open during natural mouse movement, reducing accidental resets.</p>
<p>Users don’t move perfectly. Interfaces should accommodate slight slips without punishing them. Larger targets reduce cognitive load and eliminate unnecessary frustration. so by increasing the effective size of buttons, menus, and controls, you create interactions that feel stable and predictable, and users can move confidently without fear of losing their place.</p>
<p><strong>To Sum Up:</strong> The farther away an element is, the longer it takes to reach. The smaller it is, the more precision it demands, which increases the interaction time and the likelihood of errors. Conversely, closer and larger targets reduce cognitive load, motor effort, and frustration.</p>
<h2 id="heading-20-hicks-law"><strong>2.0 Hick's Law</strong>:</h2>
<p>Hick’s Law is a psychological principle that describes the relationship between the number of choices presented to a user and the time it takes them to make a decision. It was formulated by William Edmund Hick in 1952 (Yablonski, 2022; Proctor &amp; Scheider, 2018).</p>
<p>The law states that as the number of options increases, the decision time increases logarithmically. In simple terms, more choices slow users down, while fewer choices speed up decision-making.</p>
<p>$$T = a + b \cdot \log_2(n + 1)$$</p>
<p>Where:</p>
<p>T = time to make a decision,</p>
<p>n = number of choices,</p>
<p>b= a constant that depends on the task and the individual</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/d2c45f4b-77e9-42dc-a9c3-165dfe5f7ce7.jpg" alt="d2c45f4b-77e9-42dc-a9c3-165dfe5f7ce7" style="display:block;margin:0 auto" width="1136" height="692" loading="lazy">

<p><em>Figure 2.0 illustrates the relationship between user experience, reaction time, and the number of actions.</em></p>
<p>This is how users feel, for example, when they encounter a form that asks for too much information upfront. The longer the form gets, the more frustrated they become.</p>
<p>Examples of this are overloading menus with too many items, presenting long, unorganised forms, giving too many calls-to-action on one screen, and building nested menus with excessive depth.</p>
<p>All of these create friction and can lead to cognitive overload.</p>
<h3 id="heading-design-takeaway-from-hicks-law">Design Takeaway from Hick's Law</h3>
<p><strong>Avoid Overloading Users With Too Many Actions:</strong><br>Too many buttons, menu items, or choices at once increases cognitive load and slows decision‑making. Users freeze when everything competes for attention.</p>
<p><strong>Keep Navigation Clean and Focused:</strong><br>Cluttered menus hurt both usability and SEO. Search engines struggle to track overly complex navigation structures, and users struggle to find what matters.</p>
<p><strong>Use Progressive Disclosure to Reduce Complexity:</strong><br>Hide advanced or rarely used options under “More” or expandable sections. Reveal complexity only when the user needs it.</p>
<p><strong>Break Complex Tasks Into Smaller, Manageable Steps:</strong><br>Progressive disclosure works beautifully for multi‑step forms and decision flows. Smaller steps reduce overwhelm and improve completion rates.</p>
<p><strong>Group Related Options Into Logical Categories:</strong><br>Organising actions into meaningful clusters helps users process information faster. For example, placing “Edit” and “Delete” together leverages natural mental grouping.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/mbYIfRxSkHs" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 2.0: Video description of Progressive Disclosure.</em></p>
<p>From the video above, instead of showing all the menu details at once, it is better to hide them initially. As you can see, the additional information only appears when the arrow down button is pressed. This approach prevents overwhelming the user and keeps the interface clean and focused.</p>
<p>You should also reduce decision anxiety, as too many choices create doubt and friction (as they say, the more you ask from a user, the less you get).</p>
<p>Beyond this, try to use recommended labels, show brief descriptions, provide visual previews, and use comparison tables wisely to show comparison between products especially when they have many characteristics. An example of a comparison table is shown below:</p>
<p><a href="https://drive.google.com/file/d/1sY-tb9W1QDnyrH9dd3NSYsteP9WoMT27/view?usp=drive_link"><img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/5e52527a-3faf-4da1-97c2-a2a09ca7af8b.jpg" alt="Use of comparison table to compare products" style="display:block;margin:0 auto" width="1038" height="972" loading="lazy"></a></p>
<p><em>Figure 2.1: A comparison table being used to simplify complex information.</em></p>
<p>Also, rather than showing advanced configuration options by default, display only the most commonly used settings. Advanced options can be hidden under an expandable section like “Advanced” or “More Settings. This makes your interface less cluttered and more visually organized.</p>
<p>And speaking of visual organization, this is the perfect moment to introduce Gestalt principles — the psychological rules that explain how users naturally group and interpret what they see.</p>
<p><strong>To Sum Up</strong>: As the number of options increases, the decision time increases logarithmically.</p>
<h2 id="heading-30-gestalt-principles"><strong>3.0 Gestalt Principles</strong>:</h2>
<p>In the 1920s, a group of German psychologists – Max Wertheimer, Kurt Koffka, and Wolfgang Köhlern – introduced what are now known as the Gestalt Principles. Their work sought to understand how humans perceive and interpret visual information (Bustamante, 2023).</p>
<p>The word “Gestalt” is German for “unified whole,” reflecting the core idea behind the theory: people naturally perceive objects as organised patterns and complete forms rather than as separate, disconnected parts.</p>
<p>These principles explain how the human mind structures visual elements to make sense of the world. Over time, they have become highly influential in fields such as design, user experience (UX), psychology, and data visualization, where understanding perception is critical.</p>
<h3 id="heading-key-gestalt-principles">Key Gestalt Principles:</h3>
<h3 id="heading-31-proximity">3.1 Proximity</h3>
<p>Elements that are placed close to each other are perceived as a group, while those spaced far apart are seen as separate. This is why labels are placed directly next to their corresponding input fields.</p>
<p>For example: In a blog feed, the "Title," "Author," and "Date" should have small margins between them (8px), while the space between one blog post card and the next should be much larger (40px). This tells the user's brain: "These three text strings belong to this specific post."</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/b68ab7f4-1b2e-47f1-8e8c-8ae8f32198c1.jpg" alt="b68ab7f4-1b2e-47f1-8e8c-8ae8f32198c1" style="display:block;margin:0 auto" width="1046" height="956" loading="lazy">

<p><em>Fig 3:0 Illustration of proximity (Gestalt Principle)</em></p>
<p>From the fig above, the spacing within the blog feed plays a powerful role in how effortlessly users interpret what they see. When elements sit close together, the brain instinctively treats them as belonging to the same unit. This is why placing the author credit just 8px beneath the title creates an immediate mental link. The viewer doesn’t need to pause or decode who wrote which article; proximity does the cognitive work automatically, forming a tight, intuitive grouping.</p>
<p>Equally important is the generous 40px gap between individual cards. This larger spacing introduces “visual breathing room.” Without it, a feed can quickly collapse into a dense wall of text, overwhelming the user and discouraging exploration. The wider margin establishes a clear boundary—a natural stop-and-start rhythm—that makes each card feel distinct and the entire layout more scannable.</p>
<p>Finally, subtle spacing differences can guide behaviour, not just perception. The slightly larger 12px margin above the read‑more link separates it from the passive information above it. This spacing cues the user that the link represents an action rather than another piece of descriptive text. It’s a small adjustment, but it shifts the element’s role from informational to interactive, helping users understand what they can <em>do</em> next.</p>
<p>Together, these spacing decisions transform a simple list of posts into a structured, intuitive, and behaviourally clear interface—one where the user never has to think about the layout, because the layout is already thinking for them.</p>
<p>Proximity controls meaning: move elements closer to show connection, separate them to show difference.</p>
<h3 id="heading-32-similarity">3.2 Similarity</h3>
<p>We naturally group elements that share similar visual characteristics, such as color, shape, size, or orientation.</p>
<p>For example, even if buttons are spread across a page, if they're all the same shade of blue, the user understands they perform similar functions.</p>
<p>If your primary "Submit" button is blue with rounded corners, every other primary action on your site should look exactly the same. If you suddenly use a square red button for a primary action, the user will be confused because the "similarity" is broken.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/151d9658-5a4b-4d1d-b885-02c73c53cdbd.jpg" alt="151d9658-5a4b-4d1d-b885-02c73c53cdbd" style="display:block;margin:0 auto" width="1618" height="2170" loading="lazy">

<p><em>Fig 3:1 : illustration of similarity (Gestalt principle)</em></p>
<p>As you can see from above, the layout clearly demonstrates how the Gestalt Principle of Similarity works by showing two different visual situations: one where everything matches, and one where a single element breaks the pattern.</p>
<p>All three product cards share the same visual characteristics:</p>
<ul>
<li><p>Same card shape</p>
</li>
<li><p>Same border and shadow</p>
</li>
<li><p>Same image size and placement</p>
</li>
<li><p>Same blue “Add to Cart” button</p>
</li>
<li><p>Same font style and spacing</p>
</li>
</ul>
<p>Because these elements look alike, your brain automatically groups them as one category — “products that belong together.”<br>You don’t have to think about it; the similarity creates instant visual unity.</p>
<p>This is the Gestalt Principle of Similarity in action.</p>
<p>In the second row, everything is still similar except one button:</p>
<ul>
<li><p>The middle product’s button is orange, not blue</p>
</li>
<li><p>It has square corners, not rounded</p>
</li>
<li><p>The text is italic, not regular</p>
</li>
<li><p>The label changes to “Quick Buy”</p>
</li>
</ul>
<p>Because this button breaks the shared pattern, your brain immediately notices it and treats it as different or special.</p>
<p>Developers can use broken similarity to intentionally highlight featured items, promotions, or urgent actions.</p>
<p>When similarity is broken, the different element stands out and draws attention.</p>
<h3 id="heading-33-continuity">3.3 Continuity</h3>
<p>The human eye prefers to follow a continuous path or curve rather than jagged or broken lines. We perceive items aligned on a line or curve as being related. This is often used in navigation menus or horizontal carousels to guide the user's gaze.</p>
<p>For example, you might have a horizontal carousel where the last visible card is slightly "cut off" at the edge of the screen. This visual break creates a path that encourages the user to keep scrolling as their eyes follow the line of cards.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/e44556e8-4174-4073-9c05-63b5df9a1278.jpg" alt="e44556e8-4174-4073-9c05-63b5df9a1278" style="display:block;margin:0 auto" width="1650" height="214" loading="lazy">

<p><em>Fig 3:2: illustration of continuity (Gestalt principle)</em></p>
<p>As you can see, all four form fields — <em>First Name</em>, <em>Last Name</em>, <em>Email Address</em>, and <em>Phone Number</em> — are perfectly aligned along one continuous horizontal path. Because the human eye naturally prefers to follow an unbroken line, your gaze moves smoothly from left to right across the fields without effort.</p>
<p>The final field is slightly cut off at the edge, which creates a subtle visual cue that the line continues beyond the visible area. This encourages the user to keep scrolling or swiping, because their eyes are already following the direction of the form.<br>when elements are arranged along a straight path, curve, or flow, the brain automatically treats them as connected and expects the pattern to continue.</p>
<p>Another example is Instagram Stories, which are arranged in a smooth horizontal line at the top of the app. Instagram reinforces this by slightly revealing the next story circle at the edge of the screen. That tiny “peek” acts as a continuation cue — your eyes expect the line to keep going, so your finger follows.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/bb019a6d-33c9-4509-b1c7-191b5a453c46.jpg" alt="bb019a6d-33c9-4509-b1c7-191b5a453c46" style="display:block;margin:0 auto" width="1320" height="520" loading="lazy">

<p><em>Fig 3:3: illustration of continuity (Gestalt principle)</em></p>
<p>As you can see from above, all the circular story icons are arranged in a straight horizontal line, and your visual system instinctively follows that line from left to right without effort.</p>
<p>The slight visibility of the next story at the edge of the screen strengthens this effect, signaling that the sequence continues beyond what's currently shown. Also, because the icons share the same size, spacing, and shape, there are no visual interruptions, allowing your eyes to glide across them in one continuous motion.</p>
<p>This seamless flow is exactly what continuity describes: the tendency of the human eye to follow the direction of a line or pattern, assuming it continues even when part of it is out of view.</p>
<p>Continuity is the tendency of the human eye to follow the direction of a line or pattern, assuming it continues even when part of it is out of view.</p>
<h3 id="heading-34-closure">3.4 Closure</h3>
<p>Closure refers to the mind’s ability to perceive a complete, unified form even when parts of that form are missing. Rather than requiring every boundary, line, or shape to be explicitly drawn, the brain instinctively fills in the gaps. When used intentionally, closure allows interfaces to feel cleaner, more elegant, and more cognitively efficient.</p>
<p>When we look at a complex arrangement of visual elements, we tend to look for a single, recognisable pattern. If an image is missing parts, our brains fill in the gaps to "close" the shape.</p>
<p>One of the most celebrated examples of closure in visual identity design is the panda symbol used by the World Wide Fund for Nature (WWF). This logo demonstrates how strategic omission can produce a memorable, emotionally resonant, and universally recognisable mark.</p>
<p>At first glance, the panda illustration appears simple, composed of a few bold black shapes arranged against a white background.</p>
<p>Yet a closer look reveals that the panda is not fully drawn. There are no outlines defining the body, no complete contours around the head, and no explicit boundaries separating limbs from background. Instead, the designer uses a series of carefully placed shapes (ears, eye patches, nose, and partial limbs) to imply the rest of the animal. The viewer’s mind fills in the missing information, completing the silhouette effortlessly.</p>
<p>This is closure at its most effective: the brain constructs a whole from fragments, creating a sense of completeness without visual overload.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/0c40effd-e4d2-4669-af1d-56faac976b4a.jpg" alt="0c40effd-e4d2-4669-af1d-56faac976b4a" style="display:block;margin:0 auto" width="522" height="784" loading="lazy">

<p><em>Fig 3:4: illustration of closure (Gestalt principle)</em></p>
<p>For example, a "hamburger menu" (three lines) isn't a literal drawer, but our brains "close" the shape to understand it represents a menu.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/b67253cd-2a2c-4565-9818-f5586dad6d36.jpg" alt="b67253cd-2a2c-4565-9818-f5586dad6d36" style="display:block;margin:0 auto" width="1244" height="236" loading="lazy">

<p><em>Fig 3:5: illustration of closure (Gestalt principle)</em></p>
<p>An example of closure in practice can be seen in step indicators commonly used in checkout flows. These components often rely on partial shapes, implied boundaries, and incomplete outlines to guide the user through a sequence of actions.</p>
<p>For instance, upcoming steps may be represented by dashed circles. Although the circles aren't fully drawn, the viewer immediately recognises them as complete shapes. The brain resolves the missing segments, allowing the interface to communicate progression without heavy borders or fully rendered icons. This subtle use of closure reduces visual clutter while preserving clarity.</p>
<p>Closure refers to the mind’s ability to perceive a complete, unified form even when parts of that form are missing.</p>
<h3 id="heading-35-figureground">3.5 Figure/Ground</h3>
<p>This principle describes the mind's tendency to separate an object (the figure) from its surrounding area (the ground or background). In web design, using a "modal" or "pop-up" relies on this: by blurring the background, you force the user to see the pop-up as the focal figure.</p>
<p>When a user clicks "Login" on a modal/lightbox, the background site often dims (the "Ground") while the login box stays bright and centered (the "Figure"). This immediate depth change tells the user exactly where their attention belongs.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/UCdjymjASOU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 3.5.0 Video description of Figure/Ground (Gestalt Principle)</em></p>
<p>From the video above, you can see that when the Quick View button is clicked, the selected figure stands out while the background darkens. This contrast guides the user’s attention and helps them focus on the figure. Developers can use this technique to direct users’ attention to what matters most or to what they want users to notice.</p>
<p>This principle describes the mind's tendency to separate an object (the figure) from its surrounding area (the ground or background).</p>
<h3 id="heading-36-common-fate">3.6 Common Fate</h3>
<p>Elements that move in the same direction are perceived as more related than elements that are stationary or move in different directions. Think of a dropdown menu: when all sub-items slide down together, they are clearly part of the same "unit."</p>
<p>For example, when you click a FAQ header and five sub-items slide down at the exact same speed and direction, the "Common Fate" tells the user that all those items belong to that specific category. If they flew in from different directions, the relationship would be lost.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/T10xmlne6E4" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 3.6.1 Video description of common fate (Gestalt Principle)</em></p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/5FncGLRy0bM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 3.6.2 Video description of common fate (Gestalt Principle)</em></p>
<p>From the video shown above, the e‑commerce animation example demonstrates these principles clearly by using two distinct motion patterns: a group of regular products that move upward together, and a pair of special‑category items that enter dramatically from the left. Through these contrasting movements, the interface communicates category differences without relying on text labels or explicit instructions.</p>
<p>Therefore, developers can use this motion‑based differentiation as a design strategy to guide users’ perception—allowing the interface to signal hierarchy, category structure, and product importance purely through animated behaviour rather than through static visual labels.</p>
<p>Elements that move in the same direction are perceived as more related than elements that are stationary or move in different directions.</p>
<h3 id="heading-37-focal-point">3.7 Focal Point</h3>
<p>Whatever stands out visually will capture and hold the viewer’s attention first. This is essentially the principle of emphasis. A bright "Sign Up" button in a sea of gray text acts as the focal point, directing the user's primary action.</p>
<p>For example, an alert banner or a pricing table should stand out from its surroundings. Beyond this, in a three-tier pricing table (Basic, Pro, Enterprise), the "Pro" column is often slightly larger or a different color. This creates a focal point that draws the eye to the "recommended" option immediately.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/711c9ee9-b546-4041-bf86-30c80154bd47.jpg" alt="711c9ee9-b546-4041-bf86-30c80154bd47" style="display:block;margin:0 auto" width="1784" height="646" loading="lazy">

<p><em>Fig 3:7: illustration of closure (Gestalt principle)</em></p>
<p>In visual interface design, the Gestalt principle of Focal Point plays a crucial role in directing user attention toward the most important element on a screen.</p>
<p>A focal point is created when one element breaks the established pattern of surrounding elements, making it stand out immediately.</p>
<p>In e‑commerce interfaces, this principle is often applied to highlight primary actions such as purchasing, subscribing, or upgrading. The “Buy Now” button provides a clear and practical example of how focal points function within a layout.</p>
<p>From the example above, the first two buttons share the same visual characteristics: neutral colours, and regular weight text. This repetition establishes a visual pattern that the user quickly becomes familiar with.</p>
<p>But the “Buy Now” button intentionally disrupts this pattern. It uses a bright colour, which contrasts sharply with the muted tones of the other buttons. This colour difference alone is enough to draw the eye, as humans are naturally sensitive to changes in hue and saturation within a uniform environment.</p>
<p>The Focal Point may sound like it's similar to the principle of Similarity, but the two operate in completely opposite ways within perceptual psychology.</p>
<p>Similarity explains how the mind naturally groups elements that share visual characteristics –&nbsp;such as colour, shape, or size – into coherent units. Once this grouping is established, the interface gains structure and predictability.</p>
<p>Focal Point, on the other hand, works by intentionally breaking that structure. Instead of reinforcing uniformity, it introduces a deliberate contrast – through colour, scale, brightness, or motion –&nbsp;to draw the viewer’s attention to one specific element.</p>
<p>In other words, Similarity creates the background pattern, while Focal Point identifies the one element that must stand out against that pattern.</p>
<p>Whatever stands out visually will capture and hold the viewer’s attention first.</p>
<h3 id="heading-design-takeaways-from-the-gestalt-principles">Design Takeaways from the Gestalt Principles</h3>
<p><strong>Use Spacing as Your Primary Grouping Tool:</strong><br>Elements that belong together should sit closer to each other than to anything else. Spacing communicates structure faster than borders or boxes. Use tight internal spacing (6–12px) for related items and wide external spacing (24–48px) to separate groups.</p>
<p><strong>Build a Strict, Consistent Visual System — and Stick to It:</strong><br>Define clear rules for button types, text styles, icon sizes, and alignment patterns. Consistent left‑aligned text blocks, predictable carousel lines, and stable flow patterns reduce cognitive load and make interfaces feel trustworthy.</p>
<p><strong>Guide the Brain With Spacing, Alignment, Consistency, Contrast, and Motion:</strong><br>The human brain is always trying to group, follow, and prioritise what it sees. Your job is to guide that instinct through intentional layout decisions, not fight against it.</p>
<h2 id="heading-40-von-restorff-effect-the-isolation-effect"><strong>4.0 Von Restorff Effect (The Isolation Effect)</strong>:</h2>
<p>This is the brainchild of Hedwig von Restorff, posited in 1933. In principle it states: An item that stands out is more noticable and more likely to be remembered than other items (Hunt, 1995).</p>
<p>So unique or visually distinct elements grab attention and are more memorable – in other words, distinctiveness dictates memory. When a user interacts with an interface, their brain naturally seeks patterns to minimize cognitive effort.</p>
<p>While consistency is generally a virtue in design, a perfectly uniform layout can lead to "banner blindness" or habituation, where the user stops noticing details.</p>
<p>By strategically breaking a pattern through changes in color, size, shape, or spacing, the developer can "isolate" an element, triggering a biological response that flags the item as high-priority.</p>
<p>Note that although the Focal Point principle may initially seem similar to the Von Restorff Effect, they describe two different psychological processes.</p>
<p>Focal Point is a Gestalt visual principle that explains how one element becomes the centre of attention within a composition because it carries the strongest visual contrast –&nbsp;through size, colour, brightness, position, or motion. Its purpose is to guide the viewer’s eye toward the most important element in the layout.</p>
<p>The Von Restorff Effect comes from cognitive psychology, not Gestalt theory. It states that an item that is noticeably different from a group of similar items is not only more attention‑grabbing but also more memorable.</p>
<p>So Focal Point is about where the eye goes first, while the Von Restorff Effect is about what the brain remembers later.</p>
<h3 id="heading-design-takeaways-from-von-restorff">Design takeaways from Von Restorff</h3>
<p><strong>Use Isolation to Make CTAs Impossible to Miss:</strong><br>On a page filled with neutral text and standard links, a single high‑contrast button (like a bold “Primary Blue” or “Emergency Red”) instantly becomes the standout element. This leverages the Von Restorff Effect to pull the user’s eye toward the most important action.</p>
<p><strong>Create a Visual “Hitch” in the Scan Path:</strong><br>A distinct CTA interrupts the user’s natural left‑to‑right, top‑to‑bottom scanning rhythm. This makes actions like “Buy Now” or “Sign Up” the first thing they notice and the last thing they forget.</p>
<p><strong>Make Critical Actions Visually Distinct:</strong><br>Because users naturally notice the one element that breaks a pattern, your most important actions should use deliberate contrast — color, size, shape, weight, or motion. Isolate key information instead of letting it blend into surrounding UI noise.</p>
<p><strong>Avoid Over‑Differentiation — or Nothing Stands Out:</strong> If every button is loud, animated, or uniquely styled, the interface becomes chaotic. The Von Restorff Effect only works when there is a clear, stable pattern — and you break it once, intentionally.</p>
<p><strong>To Sum Up:</strong> An item that stands out is more noticable and more likely to be remembered than other items.</p>
<h2 id="heading-50-jakobs-law"><strong>5.0 Jakob’s Law</strong></h2>
<p>Jakob’s Law states that users spend most of their time on other sites, so they expect your interface to behave like the ones they already know.</p>
<p>Familiar patterns — hamburger menus, top navigation, search icons, and clickable top‑left logos — reduce cognitive load because users don’t have to interpret anything new.</p>
<p>But while Jakob’s Law is foundational to UX, I think it can also unintentionally suppress innovation.</p>
<p>When developers over‑prioritise familiarity, they fall into a standardisation trap: endlessly optimising conventional patterns instead of exploring fundamentally better ones.</p>
<p>The Pie Menu is a perfect illustration of this. According to Fitts’s Law, the time required to reach a target depends on its distance and size. Linear menus place the last item much farther from the cursor than the first, creating uneven interaction costs.</p>
<p>Radial menus position every option at an equal distance from the centre, and their wedge‑shaped targets effectively grow larger as the pointer moves outward.</p>
<p>Mathematically, pie/radial menu are faster to interact with and more efficient — yet they remain rare in mainstream web design because they violate users’ expectations. In other words, Jakob’s Law keeps us locked into a familiar but suboptimal pattern simply because “that’s how it’s always been done.”</p>
<p>But the challenge is not choosing between familiarity and innovation, but balancing them.</p>
<p>This is where the Aesthetic–Usability Effect becomes powerful. Research shows that users perceive attractive interfaces as easier to use, and they are more forgiving of minor usability friction when the design is visually pleasing.</p>
<p>A beautifully crafted Pie Menu, for example, can encourage users to invest the small amount of learning required to use it. By applying aesthetic delight strategically, developers can introduce innovative patterns without overwhelming users.</p>
<p>The principle that emerges is simple: Be conventional where it matters, and innovative where it delights.</p>
<h3 id="heading-design-takeaway-from-jakobs-law">Design Takeaway from Jakob's Law</h3>
<p><strong>Keep Trust‑Critical Elements Predictable:</strong><br>Navigation, search, authentication, and other high‑stakes interactions must follow established conventions. Users rely on these patterns for speed, confidence, and safety — this is where Jakob’s Law should be respected without exception.</p>
<p><strong>Experiment Only in Low‑Risk, High‑Creativity Areas:</strong><br>In creative or productivity‑focused zones — like editing tools in a photo app — you can safely introduce new interaction models such as radial menus, gesture wheels, or context‑aware tool selectors. These areas invite exploration and benefit from efficiency‑driven innovation.</p>
<p><strong>To Sum Up:</strong> Be conventional where it matters, and innovative where it delights.</p>
<h2 id="heading-60-millers-law"><strong>6.0 Miller’s Law</strong></h2>
<p>Miller’s Law originates from George A. Miller’s classic paper “The Magical Number Seven, Plus or Minus Two.” It states that the average person can hold only about 7 (±2) chunks of information in working memory at any given moment (Miller, 1956).</p>
<p>Crucially, Miller emphasised that the brain doesn’t store isolated items — it groups them into meaningful units called chunks. Because working memory is so limited, developers must structure information in ways that respect this cognitive boundary.</p>
<p>This principle has direct implications for interface design. Long, unbroken strings of information overwhelm users, whereas chunked formats are far easier to process.</p>
<p>For example, instead of displaying a phone number as 1234567890, formatting it as 123‑456‑7890 transforms ten digits into three manageable chunks. The same logic applies to navigation: aim for five to nine primary menu items, and if you need more, group them into categories. Users remember the category as a single chunk rather than each individual link.</p>
<p>Miller’s Law also explains why long forms are so intimidating. When a user sees 30 fields on one page, their brain interprets it as a single, massive task — far beyond the 7±2 limit.</p>
<p>A progressive stepper solves this by breaking the form into smaller stages of 5–7 fields each. This reduces cognitive load, creates a sense of progress, and significantly lowers abandonment rates.</p>
<p>The same principle applies to product listings or search results. Expecting users to compare 50 items at once is unrealistic. Instead, provide strong filtering tools so users can narrow the set to a manageable size — ideally within the range their working memory can meaningfully evaluate.</p>
<p>In essence, Miller’s Law reminds developers that humans don’t process information in bulk. They process it in structured, meaningful chunks.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/3add2891-c6f8-4df1-a044-6cea6c5f1945.jpg" alt="Image of progressive stepper" style="display:block;margin:0 auto" width="818" height="960" loading="lazy">

<p><em>Fig 6.0: Illustrating progressive stepper</em></p>
<p>In the example above, the interface uses both a progress bar and a stepper to guide the user through multiple stages of a task. After completing the first page and selecting “Continue,” the user moves to the next step, and the progress bar updates accordingly. This creates a clear sense of forward movement and accomplishment.</p>
<p>By breaking the process into smaller segments, the interface prevents cognitive overload. If all the information were presented on a single page, users might feel overwhelmed, unsure where to begin, or discouraged by the sheer volume of work.</p>
<p>A step‑by‑step flow transforms a large task into a sequence of manageable actions, increasing the likelihood of completion.</p>
<h3 id="heading-design-takeaway-from-millers-law">Design Takeaway from Miller's Law</h3>
<p><strong>Respect the 7±2 Working‑Memory Limit:</strong><br>Users can only hold about seven chunks of information at once. Long, unbroken content overwhelms them, while chunked information is instantly easier to process.</p>
<p><strong>Chunk Information Into Meaningful Units:</strong><br>The brain doesn’t store isolated items — it groups them. Format data (like phone numbers), menus, and settings into clear, memorable chunks instead of long, flat lists.</p>
<p><strong>Keep Navigation Within 5–9 Primary Items:</strong><br>If you need more than nine options, group them into categories. Users remember the category as a single chunk, not each individual link.</p>
<p><strong>Break Long Forms Into Smaller Steps:</strong><br>A 30‑field form feels like one giant task. A progressive stepper with 5–7 fields per step keeps users below the cognitive overload threshold and dramatically reduces abandonment.</p>
<p><strong>Reduce Comparison Load With Strong Filters:</strong><br>Expecting users to compare 50 products at once is unrealistic. Provide filtering tools that shrink the decision set to something the working memory can actually handle.</p>
<p><strong>Design for Chunked Thinking, Not Bulk Processing:</strong><br>Humans don’t process information in bulk — they process structured, meaningful groups. Interfaces that respect this limitation feel lighter, faster, and more intuitive.</p>
<p><strong>To Sum Up:</strong> A step‑by‑step flow transforms a large task into a sequence of manageable actions, increasing the likelihood of completion.</p>
<h2 id="heading-70-the-goal-gradient-hypothesis">7.0 The Goal-Gradient Hypothesis</h2>
<p>This is the perfect moment to introduce the Goal‑Gradient Hypothesis, originally proposed by behaviorist Clark Hull in 1932 (Yablonski, 2022). The hypothesis states that people become more motivated as they get closer to achieving a goal. In other words, users naturally accelerate their engagement when they sense they are nearing completion.</p>
<p>This principle is incredibly powerful in UX design, especially for progress tracking, gamification, and reward systems.</p>
<p>The takeaway is straightforward: Because users are more motivated near the finish line, progress indicators should be prominent and meaningful.</p>
<p>Percentages, progress bars, and step counters reinforce momentum. Micro‑achievements — such as badges, checkmarks, or subtle confetti — amplify motivation by celebrating small wins.</p>
<p>Tasks should be broken into measurable milestones so users can see themselves advancing.</p>
<p>This is why e‑learning platforms display messages like “You’ve completed 8 of 10 lessons — almost there!” and why fitness apps highlight progress with prompts such as “3 km done, 2 km to go.” These cues leverage the goal‑gradient effect to keep users engaged, energized, and eager to finish.</p>
<p>By combining progressive steppers with clear progress feedback, developers create interfaces that feel lighter, more encouraging, and far more motivating — ultimately improving completion rates and overall user satisfaction.</p>
<p>But what happens when a goal isn't completed? Why do we sometimes feel uncomfortable leaving things unfinished? That discomfort is explained by another psychological principle called the Zeigarnik Effect — the tendency for people to remember and feel tension about incomplete tasks. We will look at this next.</p>
<h3 id="heading-design-takeaway-from-goal-gradient-hypothesis">Design Takeaway from Goal-Gradient Hypothesis</h3>
<p><strong>Make Progress Visible to Boost Motivation:</strong><br>According to the Goal‑Gradient Hypothesis, users naturally speed up as they sense they’re nearing completion. Prominent progress bars, percentages, and step counters tap into this instinct and keep momentum high.</p>
<p><strong>Celebrate Micro‑Achievements to Reinforce Engagement:</strong><br>Badges, checkmarks, subtle confetti, and “step completed” cues reward small wins. These micro‑rewards amplify motivation and make long tasks feel lighter and more achievable.</p>
<p><strong>Break Tasks Into Measurable Milestones:</strong><br>Users stay motivated when they can see themselves advancing. Divide complex flows into clear steps so progress feels tangible rather than overwhelming.</p>
<p><strong>Use Progress Feedback to Drive Completion:</strong><br>Messages like “8 of 10 lessons completed — almost there” or “3 km done, 2 km to go” leverage the goal‑gradient effect to energise users and pull them toward the finish line.</p>
<p><strong>Combine Steppers With Clear Feedback for Maximum Impact:</strong><br>Progressive steppers paired with strong visual feedback create interfaces that feel encouraging, structured, and motivating — dramatically improving completion rates.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/-mDuFB3W2bg" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 8.0 : Video illustrating goal gradient</em></p>
<p><strong>To Sum Up:</strong> People become more motivated as they get closer to achieving a goal.</p>
<h2 id="heading-80-zeigarnik-effect"><strong>8.0 Zeigarnik Effect</strong></h2>
<p>The Zeigarnik Effect is a psychological principle stating that people remember unfinished or interrupted tasks better than completed ones (Cherry, 2024).</p>
<p>Memory begins with sensory input, which is processed into short-term memory. Unfinished tasks persist in our thoughts, leading to active recall. This ongoing engagement can turn them into long-term memories, enhancing recall until resolved. This increases engagement, encourages task completion, improves retention, and drives conversions.</p>
<p>So because people remember unfinished tasks better than completed ones (Zeigarnik Effect), developers use progress indicators to make users aware that something is incomplete and motivate them to finish it.</p>
<p>In your designs, you can break long forms into multi-step processes to encourage completion and display profile completion percentages (for example, 70% complete) to push users toward 100%.</p>
<p>This is the main reason why e-commerce platforms send abandoned cart reminders to bring users back to complete their purchases. It's also why apps use streak systems to encourage daily engagement and habit formation and learning platforms show course completion bars to motivate users to finish modules.</p>
<h3 id="heading-design-takeaway-from-zeigarnik-effect">Design Takeaway from Zeigarnik Effect</h3>
<p><strong>Unfinished Tasks Stay Active in Memory — Use That to Drive Completion:</strong><br>Because incomplete tasks linger in working memory (Zeigarnik Effect), users naturally keep thinking about what they haven’t finished. This tension boosts recall, engagement, and the likelihood of returning to complete the task.</p>
<p><strong>Make Incompleteness Visible With Progress Indicators:</strong><br>Progress bars, percentages, and step counters remind users that something is still unfinished. This gentle psychological pressure motivates them to continue until the task is complete.</p>
<p><strong>Break Long Flows Into Multi‑Step Processes:</strong><br>A massive form feels overwhelming, but a stepper with smaller chunks keeps users moving. Showing “70% complete” nudges them toward finishing the last stretch.</p>
<p><strong>Use Reminders to Re‑activate Unfinished Intent:</strong><br>Abandoned cart emails, streak reminders, and “continue your lesson” prompts work because the unfinished task is already active in the user’s mind. The reminder simply pulls them back into the loop.</p>
<p><strong>Celebrate Completion to Close the Cognitive Loop:</strong><br>Checkmarks, confirmations, and completion badges give users closure. This resolves the mental tension created by the unfinished task and reinforces positive behaviour.</p>
<p><strong>To Sum Up:</strong> Unfinished tasks persist in our thoughts, leading to active recall.</p>
<h2 id="heading-90-teslers-law"><strong>9.0 Tesler’s Law</strong>:</h2>
<p>This law was proposed by Lawrence Tesler. He was a computer scientist known for his work on human-computer interaction, and he contributed significantly to making software more user-friendly, including work on cut, copy, and paste functionality.</p>
<p>This law is otherwise known as the Law of Conservation of Complexity. The core Idea here is every process has a certain amount of “inherent complexity" that can't be removed. You can only decide who handles it: the user or the system.</p>
<p>Some examples of these inherent complexities might be:</p>
<ul>
<li><p>translating user actions into correct operations behind the scenes,</p>
</li>
<li><p>handling unreliable or slow network connections,</p>
</li>
<li><p>connecting with third-party APIs, services, or legacy systems,</p>
</li>
<li><p>sorting large datasets quickly,</p>
</li>
<li><p>performing complex search operations</p>
</li>
<li><p>managing version changes and compatibility issues,</p>
</li>
<li><p>managing state, interactions, and animations without confusing the user.</p>
</li>
</ul>
<p>All of these can be inherently complex, but it's the job of the developer to deal with the complexity.</p>
<p>As a developer, you should always try as much as possible to push complexity to the system. For example, instead of making a user type their full address manually, use an Auto-complete API (Google’s Places and Map is best for this). The complexity of finding and validating the address still exists, but the software handles the work for them.</p>
<p>Here's a practical example: let’s say you're designing a student platform that requires users to enter their university name. A practical approach would be to store an array of all universities in the UK in your codebase (This is the hard part Tesla hinted at).</p>
<p>As the user types, they don't need to enter the full name, and their full university name is shown (relating to what they have typed). For instance, if they intend to type “University of Sheffield,” simply typing “Sheff” should prompt the system to display the full university name, which they can then select.</p>
<p>In Dart, you can use a package like fuzzysearch to implement this kind of intelligent matching.</p>
<p>The advantage of this approach is greater than it first appears. It improves data consistency because users often enter the same information in different ways. For example, some users might type “Uni of Sheff,” others “Sheffield University,” and others “Uni of Sheffield,” while all are referring to “University of Sheffield.”</p>
<p>This is how messy data is created, and it creates more work for data analysts. Little wonder that data analysts spend up to 70% of their time cleaning data.</p>
<p>If developers invested more time in structuring how data is collected to ensure consistency, there would be far less work downstream for analysts. This same logic should be applied in how we collect date, time, and other information.</p>
<p>So apart from people's names and email addresses, you should try to standardize the data your app collects as much as possible. Use date and time pickers, stepper controls, input masks, checkboxes, dropdown menu and radio buttons, toggle switches. and so on.</p>
<p>The essence of removing complexity from the user is not only about improving usability, but also about ensuring that the data collected is standardised, structured, and consistent.</p>
<h3 id="heading-design-takeaway-from-teslers-law">Design Takeaway from Tesler's Law</h3>
<p><strong>Push Complexity to the System, Not the User:</strong><br>Every process contains unavoidable complexity. Your job is to handle it behind the scenes so the user experiences the simplest possible interaction.</p>
<p><strong>Automate Tasks Users Shouldn’t Have to Think About:</strong><br>Use tools like autocomplete, fuzzy search, intelligent defaults, and validation APIs to remove manual effort. The complexity still exists — but the system absorbs it instead of the user.</p>
<p><strong>Standardise Inputs to Prevent Messy Data:</strong><br>Users enter the same information in wildly different ways. Use pickers, dropdowns, input masks, radio buttons, and toggles to enforce consistent, structured data collection.</p>
<p><strong>Handle Inherent Technical Complexity Internally:</strong><br>Network issues, API quirks, large dataset sorting, search optimisation, state management, and animation logic are all developer responsibilities. Users should never feel this complexity.</p>
<p><strong>To Sum Up:</strong> Every process contains unavoidable complexity. Your job as a developer is to handle it behind the scenes so the user experiences the simplest possible interaction.</p>
<h2 id="heading-100-peak-end-rule"><strong>10.0 Peak End Rule</strong>:</h2>
<p>In 1993, Daniel Kahneman, Barbara Fredrickson, Charles Schreiber, and Donald Redelmeier invited volunteers into a lab for what sounded like a simple experiment. The task was straightforward: place a hand into a container of painfully cold water (Kahneman et al., 1993)</p>
<p>In the first round, participants kept their hand in 14°C water for 60 seconds. It was uncomfortable, sharp, and unpleasant but after one minute, it was over.</p>
<p>In the second round, they again endured 60 seconds in 14°C water. But this time, they were asked to keep their hand in for an extra 30 seconds. The temperature was raised slightly to 15°C. Still cold. Still unpleasant. Just slightly less intense.</p>
<p>Objectively, the second experience was worse. It lasted 90 seconds instead of 60. More total pain. More suffering.</p>
<p>Later, the researchers asked a simple question:</p>
<p>If you had to repeat one of the trials, which would you choose?” Surprisingly, most participants chose the longer one.</p>
<p>Why would anyone choose more pain?</p>
<p>The researchers realised something profound: people don’t remember experiences by calculating total discomfort. Instead, the mind summarizes the experience using just two key moments — the most intense point (the peak) and the final moment (the end).</p>
<p>In both trials, the peak pain was the same: 14°C. But the longer trial ended slightly better, at 15°C. That small improvement at the end reshaped how the entire episode was remembered. The participants’ “experiencing self” suffered more during the longer trial. But their “remembering self” preferred it because it ended on a less painful note.</p>
<p>From this, the researchers introduced what became known as the Peak–End Rule: we judge experiences largely by their most intense moment and how they finish, not by how long they last.</p>
<p>Since people largely judge an experience by how it ends, developers should focus on designing satisfying confirmation screens and smooth exit interactions. You should concentrate less on making every single moment perfect and instead prioritise optimising the peak and final moments.</p>
<p>A negative ending can overshadow an otherwise good experience, so carefully avoid frustrating final steps such as unexpected fees or confusing confirmations.</p>
<p>Emotional intensity strongly shapes memory, which is why many apps incorporate celebration animations, rewards, or success messages at key moments to leave a lasting positive impression.</p>
<h3 id="heading-design-takeaway-from-peak-end-rule">Design takeaway from Peak End Rule</h3>
<p><strong>People Judge Experiences by the Peak and the Ending — Not the Total Duration:</strong><br>Users don’t remember every moment. They remember the most intense point and how the experience ends. A slightly better ending can completely reshape how the entire interaction is remembered.</p>
<p><strong>Prioritise Strong, Positive Endings in Your UX Flows:</strong><br>A smooth final step, a clear confirmation, or a satisfying success screen leaves a disproportionately strong impression. A bad ending can overshadow an otherwise great experience.</p>
<p><strong>Design for Emotional Peaks at Key Moments:</strong><br>Celebration animations, rewards, checkmarks, and success messages create memorable emotional spikes. These peaks anchor the experience in the user’s memory.</p>
<p><strong>Don’t Try to Perfect Every Moment — Perfect the Right Moments:</strong><br>Optimise the peak and the end of the journey. These two moments define how users recall the entire interaction.</p>
<p><strong>Avoid Negative Surprises at the Finish Line:</strong><br>Unexpected fees, confusing confirmations, or friction at the last step can ruin the memory of the whole process. Protect the ending carefully.</p>
<p><strong>To Sum Up:</strong> We judge experiences largely by their most intense moment and how they finish, not by how long they last.</p>
<h2 id="heading-110-postels-law"><strong>11.0 Postel’s Law</strong>:</h2>
<p>Jon Postel’s famous principle – “Be conservative in what you send, be liberal in what you accept” –&nbsp; is a philosophy of kindness in software design. At its core, the principle argues that systems should be generous with what they accept from users, yet disciplined and predictable in what they output.</p>
<p>When developers follow this approach, users feel supported and understood. When they don’t, users feel punished for being human.</p>
<p>A user’s input is rarely perfect. People type quickly, make mistakes, follow their own habits, or rely on formats familiar to them. A robust system embraces this reality. It accepts messy, human input and quietly transforms it into clean, standardized data.</p>
<p>Real people don't think in strict formats. They write dates the way they learned in school, type phone numbers the way they say them aloud, and enter names and addresses in whatever structure feels natural to them.</p>
<p>A rigid system will reject anything that doesn’t match its narrow expectations, but a robust system, by contrast, adapts to the user.</p>
<p>Consider dates. A brittle interface might demand MM/DD/YYYY and reject everything else. A more humane system accepts a wide range of formats — “1 May 2024,” “2024‑05‑01,” “05/01/24,” or “May 1st, 2024” — and quietly converts them into a standard internal representation. This is where the complex handling described by Tesla's Law comes into play (Shifting complexity to the system, rather than the user).</p>
<p>Phone numbers follow the same pattern. People might enter (555) 123 4567, 555‑123‑4567, 5551234567, or +1 555 123 4567. A fragile system throws errors. A robust one parses all of them using libraries like libphonenumber and moves on.</p>
<p>Addresses are equally varied. “221B Baker St,” “221‑B Baker Street,” and “221 Baker St., Apt B” all refer to the same place. A forgiving system normalizes these instead of rejecting them.</p>
<p>Even names can be surprisingly complex. Hyphens, apostrophes, multiple words, and titles are all part of real human identity. Rejecting “O’Connor,” “Jean‑Luc,” or “Dr. Sarah Lee” is not just technically incorrect — it's disrespectful to the user.</p>
<p>Search bars offer another clear example. A strict search bar demands perfect spelling and exact phrasing. A robust one handles typos (“restuarant”), partial words (“resta”), synonyms (“food places”), and natural language (“where can I eat nearby”). It meets the user where they are instead of forcing them to think like a machine.</p>
<p>Currency should be normalized to a clear format such as GBP 5.00, no matter whether the user typed “£5,” “5 pounds,” or “5 GBP.”</p>
<p>Even file uploads benefit from standardization: whether the user uploads .jpeg, .jpg, .JPG, or .JPEG, the system should store everything as .jpg.</p>
<p>Error messages follow the same principle. Vague feedback like “Invalid password” leaves users confused and frustrated.</p>
<p>A clear, conservative message — “Incorrect password. Please try again.” — respects the user’s time. And instead of hiding password requirements, the system should state them upfront: minimum eight characters, at least one uppercase letter, at least one number.</p>
<p>Predictability reduces friction.</p>
<p>Because users inevitably make mistakes or enter data in unexpected ways, developers should design input fields that are tolerant rather than brittle. This means accepting flexible formats, offering autocorrect or intelligent parsing, and using forgiving validation rules that interpret the user’s intent instead of rejecting their effort.</p>
<p>Clear instructions, tooltips, and visible requirements should appear before submission so users understand what the system expects without trial and error.</p>
<p>When errors do occur, the interface should handle them gently—never crashing, and never forcing the user to start over.</p>
<p>Even simple variations, such as phone numbers typed with spaces, dashes, or parentheses, should be accepted and normalized behind the scenes.</p>
<p>By embracing flexibility on the input side and clarity on the output side, developers create systems that feel humane, resilient, and respectful of the way real people actually behave.</p>
<h3 id="heading-design-takeaway-from-postels-law">Design Takeaway from Postel's Law</h3>
<p><strong>Accept Messy Human Input, Output Clean Structured Data:</strong><br>Users type dates, names, phone numbers, and addresses in unpredictable ways. A humane system accepts this variability and quietly normalises it into a consistent internal format.</p>
<p>Rigid interfaces punish users for being human. Robust interfaces interpret intent — handling typos, partial matches, synonyms, and natural language without complaint.</p>
<p>Also accept variations in spacing, punctuation, casing, and structure. Let users type naturally — the system should handle the complexity, not them.</p>
<p><strong>Be Flexible With Input, Be Strict With Output:</strong><br>This is the heart of Postel’s Law. Let users express information naturally, but ensure your system stores and displays it in a predictable, standardised way.</p>
<p><strong>Use Intelligent Parsing and Autocorrection to Reduce Errors:</strong><br>Libraries like libphonenumber, fuzzy search, and natural‑language parsers allow systems to accept a wide range of formats while still producing clean, reliable data.</p>
<p><strong>Normalise Everything Behind the Scenes:</strong><br>Dates, phone numbers, currency, file extensions, and addresses should all be standardised internally. This prevents messy data and reduces downstream cleanup work.</p>
<p><strong>Provide Clear, Predictable Feedback:</strong><br>Error messages should be specific and helpful. Requirements should be visible upfront. Users should never be surprised, confused, or forced to start over.</p>
<p><strong>Combine Postel’s Law With Tesler’s Law:</strong><br>Shift complexity to the system. Intelligent handling of messy input reduces cognitive load, improves usability, and ensures consistent, high‑quality data.</p>
<p><strong>To Sum Up:</strong> A rigid system will reject anything that doesn’t match its narrow expectations, but a robust system, by contrast, adapts to the user.</p>
<h2 id="heading-120-doherty-threshold"><strong>12.0 Doherty Threshold</strong>:</h2>
<p>The Doherty Threshold is a principle in human–computer interaction which proposes that systems should respond quickly enough to keep users actively engaged (Mod 2024).</p>
<p>When response times stay below a certain limit, users remain focused and productive. But once performance already meets this optimal responsiveness level, making the system even faster or adding extra capability doesn't significantly enhance satisfaction or efficiency.</p>
<p>The idea was introduced by Walter J. Doherty in 1976 in his paper “A Comparison of Programming Systems and Doherty Threshold.” His research showed that maintaining rapid system feedback fast enough to sustain continuous interaction has a stronger impact on productivity than simply increasing system power or features beyond that point.</p>
<p>Doherty proposes that this shouldn't be greater than 400ms Rule: If the system responds within this window, the user feels in total control. If the response takes longer, the user's attention begins to wander, and their "train of thought" is broken.</p>
<p>The challenge, of course, is that not every operation can realistically complete within 400ms. Some tasks require heavy computation, large network calls, or complex rendering. This is where the concept of perceived performance becomes essential.</p>
<p>Even when the system can't finish the work quickly, it can feel fast by responding instantly at the UI level. Developers can achieve this illusion of speed through a combination of thoughtful design patterns and disciplined engineering practices.</p>
<p>On the technical side, performance begins with reducing unnecessary work. Keeping the number of HTML elements low helps the browser render faster. Rendering only the visible portion of long lists prevents the Document Oject Model (DOM) from becoming bloated. Splitting scripts and deferring non‑critical code ensures that essential interactions load first.</p>
<p>Using CSS transforms and opacity changes avoids expensive layout recalculations. Lazy‑loading images, videos, and scripts ensures that the interface becomes interactive long before all assets are downloaded.</p>
<p>These optimizations don’t just improve raw speed — they create the foundation for interfaces that feel responsive.</p>
<h3 id="heading-design-takeaways-from-doherty-threshold">Design Takeaways from Doherty Threshold</h3>
<p><strong>Instant Feedback</strong>: When a user clicks a button, provide a visual change (like a button press animation or a spinner) immediately, even if the background task takes longer.</p>
<p><strong>Skeleton Screens</strong>: Use placeholder blocks that mimic the layout of the page while data loads. This makes the app feel like it is responding instantly.</p>
<p><strong>Progressive Loading</strong>: Load text and basic structures first, then "pop in" high-resolution images later.</p>
<p><strong>Optimistic UI</strong>: When a user hits "Save," don't wait for the server. Update the UI instantly (Doherty) and handle the "messy" data formatting on the backend (Postel).</p>
<p><strong>Live Inline Validation</strong>: Show a green checkmark or a helpful error message as the user types. This keeps them below the 400ms "thought-break" limit.</p>
<p><strong>Debouncing</strong>: In search bars, start showing results after a few keystrokes so the user feels the app is "predicting" their needs.</p>
<p><strong>To Sum Up:</strong> When response times stay below a certain limit, users remain focused and productive. But once performance already meets this optimal responsiveness level, making the system even faster or adding extra capability doesn't significantly enhance satisfaction or efficiency.</p>
<h2 id="heading-130-serial-position-effect-primacy-and-recency"><strong>13.0 Serial Position Effect (Primacy and Recency)</strong>:</h2>
<p>Murdock’s study investigated how the position of a word in a list affects recall, known as the serial position effect. He presented 103 psychology students with lists of 10 to 40 words, one at a time, at either 1 or 2 seconds per word (McLeod, 2025).</p>
<p>Participants were divided into six groups, each experiencing a different combination of list length and presentation rate, and were asked to recall as many words as possible in any order.</p>
<p>The results showed that participants were most likely to remember words at the beginning of the list (primacy effect) and at the end of the list (recency effect), while words in the middle were recalled less often. The recency effect persisted even in longer lists, and the middle section of the recall curve formed a flat asymptote.</p>
<p>Murdock explained this using the multi-store model of memory: early words were rehearsed and transferred to long-term memory, last words remained in short-term memory, and middle words were neither sufficiently rehearsed nor retained, leading to poorer recall.</p>
<p>The experiment demonstrated that memory performance varies systematically with the position of information in a sequence.</p>
<p>This is the reason why the most important information or actions should never be buried in the middle.</p>
<p>As a developer, you should put your most critical navigation links (like "Home" or "Dashboard") at the far left or the top of a list. In a pricing table, put the most popular or recommended plan on the Place "Final Actions" (like "Log Out," "Cart," or "Support") at the end of a menu or the far right of a navigation bar.</p>
<p>In a long onboarding flow, put the most exciting benefit of the app on the very last slide so the user enters the app feeling motivated.</p>
<p>Avoid placing highly important buttons in the middle of a row. If you have a row of 7 buttons, the user is statistically likely to overlook the 4th one.</p>
<h3 id="heading-design-takeaways-serial-position-effect">Design Takeaways <strong>Serial Position Effect</strong></h3>
<p><strong>Place Critical Items at the Beginning or End — Never the Middle:</strong><br>Users reliably remember the first and last items in any sequence (primacy and recency). Anything placed in the middle is statistically more likely to be forgotten or ignored. Also, actions such as “Log Out,” “Cart,” “Support,” or “Checkout” should sit at the far right or bottom — the natural recency position.</p>
<p><strong>Put Essential Navigation Links at the Far Left or Top:</strong><br>Links like “Home,” “Dashboard,” or “Overview” should appear at the start of a menu, where recall and recognition are strongest.</p>
<p><strong>To Sum Up:</strong> The results showed that participants were most likely to remember words at the beginning of the list (primacy effect) and at the end of the list (recency effect), while words in the middle were recalled less often.</p>
<h2 id="heading-140-occams-razor"><strong>14.0 Occam’s Razor</strong>:</h2>
<p>Although first articulated in the 14th century by the Franciscan friar William of Ockham, Occam’s Razor remains one of the most indispensable principles in a developer’s toolkit. In fact, skipping this law while discussing other theories and principles would be like skipping the glue that holds the entire framework together.</p>
<p>At its core, Occam’s Razor states that “among competing explanations, the simplest one is usually the best.”</p>
<p>For example, if two user interfaces achieve the same goal, the one with fewer visual elements is typically superior because it requires less processing power.</p>
<p>The fundamental takeaway for modern developers regarding Occam’s Razor is that complexity is a tax on the user’s cognitive resources.</p>
<p>In an era of information density, the developer's primary role is no longer to provide "more" features – rather, it's to curate the most direct path to a solution.</p>
<p>In practice, Occam’s Razor becomes a reminder to keep things as simple as possible. This “less is more” mindset shapes everything from navigation to forms.</p>
<p>A good rule for navigation is the Rule of Five: aim for three to five main menu items instead of a long, overwhelming list. This keeps choices clear and prevents users from freezing up when they see too many options.</p>
<p>The same idea applies to data entry. When you ask only for the information that truly matters, you respect the user’s time and reduce the chance of “form fatigue,” which is one of the biggest reasons people abandon sign‑ups or checkout flows.</p>
<p>Simplicity isn’t just elegant — it’s practical, humane, and far more effective.</p>
<h3 id="heading-design-takeaway-from-occams-razor">Design Takeaway from Occam's Razor</h3>
<p><strong>Choose the Simplest Effective Solution:</strong><br>When two designs achieve the same goal, the one with fewer elements is almost always better. Simplicity reduces cognitive load and speeds up user decision‑making.</p>
<p><strong>Simplicity Is Not Just Aesthetic — It’s Humane:</strong><br>Clear, minimal interfaces respect the user’s time, reduce friction, and make the product feel effortless. Simplicity is both a design strategy and an act of empathy.</p>
<p><strong>To Sum Up:</strong> Simplicity isn’t just elegant — it’s practical, humane, and far more effective.</p>
<h2 id="heading-150-parkinsons-law">15.0 Parkinson's Law</h2>
<p>Occam’s Razor teaches us to prefer the simplest solution that works. But why do we so often end up with complex systems in the first place? That tendency is explained by another principle: Parkinson’s Law.</p>
<p>Parkinson’s Law states that "work expands to fill the time available for its completion". In design, this means projects often become overly complex or take longer than necessary if given too much time, resulting in inefficient, over-designed, or cluttered interfaces.</p>
<p>In design, this manifests as Feature Creep. If you give yourself three months to build an app, you will spend three months adding "nice-to-have" animations, extra settings toggles, and niche edge cases that nobody asked for and in reality, what you have added isn’t that important.</p>
<p>You just succeeded in adding layers of complexity that might ends up violating some of the laws we spoke about. Occam’s Razor reminds us that the simplest solution is often the most effective.</p>
<p>By being aware of Parkinson’s Law and the tendency for work to expand, developers can manage their time intentionally and focus only on what truly matters.</p>
<h3 id="heading-design-takeaway-for-parkinsons-law">Design Takeaway for Parkinson's law</h3>
<p><strong>Set Clear Constraints to Keep Designs Focused:</strong><br>Intentional time limits and scope boundaries prevent over‑designing. Constraints force clarity, prioritisation, and simplicity.</p>
<p><strong>Build Only What Truly Matters for the User:</strong><br>Parkinson’s Law reminds you to resist the urge to fill time with unnecessary features. Focus on the core experience, not the edge cases nobody asked for.</p>
<p><strong>Use Occam’s Razor to Counterbalance Parkinson’s Law:</strong><br>As work expands, complexity grows. Occam’s Razor pulls you back to the simplest effective solution. Together, the two principles prevent bloated, over‑engineered products.</p>
<p><strong>To Sum Up:</strong> Work expands to fill the time available for its completion</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Human-centered design is deeply influenced by a set of psychological principles that explain how users perceive, process, and interact with digital systems.</p>
<p>Among these, Fitts’s Law establishes that the time required to acquire a target depends on its size and distance. In practice, this means that larger and closer elements are easier and faster to interact with.</p>
<p>To apply this in practice, developers should make primary call-to-action elements prominent, large, and easily reachable –&nbsp;especially in mobile interfaces where thumb accessibility is critical.</p>
<p>Closely related to decision-making is Hick’s Law, which states that the more choices a user is presented with, the longer it takes to make a decision. Excessive options can overwhelm users and lead to decision fatigue.</p>
<p>To address this, developers should simplify interfaces, minimise unnecessary options, and guide users through processes step-by-step rather than presenting everything at once.</p>
<p>Another important cognitive principle discussed is Miller’s Law, which suggests that the average person can hold approximately seven (plus or minus two) items in working memory at a time. This limitation highlights the need to present information in manageable chunks.</p>
<p>By breaking content into smaller groups and avoiding information overload, developers can improve comprehension and usability.</p>
<p>User expectations are strongly shaped by Jakob’s Law, which says that people spend most of their time on other websites and therefore expect similar patterns across digital products.</p>
<p>Instead of reinventing basic interactions, developers should follow familiar conventions such as placing the logo in the top‑left, the shopping cart in the top‑right, and keeping scrolling behaviour predictable.</p>
<p>But innovation is still possible where it truly adds value. As we discussed with the Aesthetic‑Usability Effect, users are far more tolerant of new or unusual design patterns when the interface is visually appealing and thoughtfully crafted.</p>
<p>The Gestalt Principles provided additional insight into how users visually organise information. The principle of proximity suggests that objects placed close together are perceived as related, so grouping related elements improves clarity. Similarity indicates that elements with consistent colours, shapes, or styles are seen as belonging together, reinforcing visual hierarchy and function. Closure explains that users can perceive incomplete shapes as complete, allowing for minimalistic designs where the brain fills in missing details. Continuity highlights that users naturally follow smooth visual paths, meaning layouts should guide the eye logically through alignment and structure.</p>
<p>We also looked at The Von Restorff Effect which emphasizes that elements which stand out are more likely to be remembered. By using contrast in colour, size, or design, important features such as buttons or alerts can capture user attention.</p>
<p>Managing complexity was addressed by Tesler’s Law, which asserts that every system has inherent complexity that cannot be eliminated but only managed.</p>
<p>Developers must therefore shift complexity away from the user by simplifying interfaces while handling intricate processes behind the scenes.</p>
<p>The Zeigarnik Effect reveals that people remember unfinished tasks better than completed ones, creating a sense of mental tension. This can be leveraged by incorporating progress indicators, checklists, and reminders that encourage users to complete tasks.</p>
<p>Similarly, the Peak-End Rule suggests that users judge an experience based on its most intense moment and its conclusion. Developers should create memorable highlights and ensure a smooth, satisfying ending to user journeys.</p>
<p>We also discussed the Goal-Gradient Effect, which explains that users become more motivated as they approach the completion of a task. By showing progress –such as indicating that a process is “80% complete” –&nbsp;and breaking tasks into stages, developers can encourage users to finish what they have started.</p>
<p>In terms of system interaction, Postel’s Law advises developers to be flexible in accepting user input while maintaining strict standards for output. This means allowing different input formats while ensuring consistent and reliable system responses.</p>
<p>Performance is equally important, as highlighted by the Doherty Threshold, which shows that productivity increases when system response times stay under 400 milliseconds. Fast systems keep users engaged and create a sense of ease.</p>
<p>This means that developers should focus on building interfaces that feel instant, even when real processing takes longer, by combining smart engineering practices with thoughtful design patterns that maintain the illusion of speed.</p>
<p>Memory and attention are further explained by the Serial Position Effect, where users tend to remember the first and last items in a sequence more than those in the middle. Developers should position key information or actions at the beginning or end of lists.</p>
<p>Simplicity is reinforced by Occam’s Razor, which argues that the simplest solution is often the most effective. Eliminating unnecessary features reduces friction and enhances usability, and we further discussed about Parkinson’s Law, which suggests that tasks expand to fill the time available, indicating the importance of setting constraints such as deadlines or timers to encourage timely action.</p>
<p>These principles collectively highlight the importance of simplicity, clarity, performance, and user psychology in design. By applying them thoughtfully, developers can create intuitive, efficient, and engaging user experiences that align with both human behaviour and user expectations.</p>
<h2 id="heading-references">References</h2>
<p>Budiu, R. (2022). <em>Fitts’s Law and Its Applications in UX</em>. [online] Nielsen Norman Group. Available at: <a href="https://www.nngroup.com/articles/fitts-law/">https://www.nngroup.com/articles/fitts-law/</a>.</p>
<p>Bustamante, N. (2023). <em>Gestalt Psychology? Definition, Principles, &amp; Examples - Simply Psychology</em>. [online] <a href="http://www.simplypsychology.org">www.simplypsychology.org</a>. Available at: <a href="https://www.simplypsychology.org/what-is-gestalt-psychology.html">https://www.simplypsychology.org/what-is-gestalt-psychology.html</a>.</p>
<p>Cherry, K. (2024). <em>The Zeigarnik Effect Is Why You Keep Thinking of Unfinished Work</em>. [online] Verywell Mind. Available at: <a href="https://www.verywellmind.com/zeigarnik-effect-memory-overview-4175150">https://www.verywellmind.com/zeigarnik-effect-memory-overview-4175150</a>.</p>
<p>DO, A.M., RUPERT, A.V. and WOLFORD, G. (2008). Evaluations of pleasurable experiences: The peak-end rule. <em>Psychonomic Bulletin &amp; Review</em>, 15(1), pp.96–98. doi:<a href="https://doi.org/10.3758/pbr.15.1.96">https://doi.org/10.3758/pbr.15.1.96</a>.</p>
<p>GUPTA, S., GUPTA, S., MAHENDRA, A. and GUPTA, S. (2006). Inverse Halo Nevus. <em>Dermatologic Surgery</em>, 32(6), pp.871–872. doi:<a href="https://doi.org/10.1097/00042728-200606000-00025">https://doi.org/10.1097/00042728-200606000-00025</a>.</p>
<p>‌Hall, D. (2023). <em>Pilot Error, Chapanis and The Shape of Things to Come</em>. [online] UX Magazine. Available at: <a href="https://uxmag.com/articles/pilot-error-chapanis-and-the-shape-of-things-to-come">https://uxmag.com/articles/pilot-error-chapanis-and-the-shape-of-things-to-come</a>.</p>
<p>Hunt, R.R. (1995). The subtlety of distinctiveness: What von Restorff really did. <em>Psychonomic Bulletin &amp; Review</em>, 2(1), pp.105–112. doi:<a href="https://doi.org/10.3758/bf03214414">https://doi.org/10.3758/bf03214414</a>.</p>
<p>Kahneman, D., Fredrickson, B.L., Schreiber, C.A. and Redelmeier, D.A. (1993). When More Pain Is Preferred to Less: Adding a Better End. <em>Psychological Science</em>, 4(6), pp.401–405. doi:<a href="https://doi.org/10.1111/j.1467-9280.1993.tb00589.x">https://doi.org/10.1111/j.1467-9280.1993.tb00589.x</a>.</p>
<p>Mod, D. (2024). <em>Doherty Threshold: UX Law of Swift Interactions</em>. [online] Articles on everything UX: Research, Testing &amp; Design. Available at: <a href="https://blog.uxtweak.com/doherty-threshold/">https://blog.uxtweak.com/doherty-threshold/</a>.</p>
<p>Miller, G.A. (1956). The magical number seven, plus or minus two: Some limits on our capacity for processing information. <em>Psychological Review</em>, [online] 101(2), pp.343–352. doi:<a href="https://doi.org/10.1037/0033-295x.101.2.343">https://doi.org/10.1037/0033-295x.101.2.343</a>.</p>
<p>Proctor, R.W. and Schneider, D.W. (2018). Hick’s law for choice reaction time: A review. <em>Quarterly Journal of Experimental Psychology</em>, [online] 71(6), pp.1281–1299. doi:<a href="https://doi.org/10.1080/17470218.2017.1322622">https://doi.org/10.1080/17470218.2017.1322622</a>.</p>
<p>Yablonski, J. (2022). <em>Hick’s Law</em>. [online] Laws of UX. Available at: <a href="https://lawsofux.com/hicks-law/">https://lawsofux.com/hicks-law/</a>.</p>
<p>Yablonski, J. (2022). <em>Goal-Gradient Effect</em>. [online] Laws of UX. Available at: <a href="https://lawsofux.com/goal-gradient-effect/">https://lawsofux.com/goal-gradient-effect/</a>.</p>
<p>‌</p>
<p>‌</p>
<p>‌</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Data Science Insights: Why the Mean Lies When Handling Messy Retail Data ]]>
                </title>
                <description>
                    <![CDATA[ In our daily life, we use the word "average" all the time: average salary, average marks, average age, and so on. Let's take the case of a retail shop. If we're looking at the average order value to u ]]>
                </description>
                <link>https://www.freecodecamp.org/news/data-science-insights-why-the-mean-lies-when-handling-messy-retail-data/</link>
                <guid isPermaLink="false">69fa21e5a386d7f121b5fe8c</guid>
                
                    <category>
                        <![CDATA[ Data Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ statistics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rakshath Naik ]]>
                </dc:creator>
                <pubDate>Tue, 05 May 2026 16:59:17 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/4441dcfc-d100-4613-9937-9c62449c6780.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In our daily life, we use the word "average" all the time: average salary, average marks, average age, and so on.</p>
<p>Let's take the case of a retail shop. If we're looking at the average order value to understand customer spending, we'd load the data, run the code, and get a result of $20 per order.</p>
<p>Done.</p>
<p>Except something looks odd.</p>
<p>When we take a closer look, we see that most customers are buying items worth \(8 - \)15. So where's $20 coming from?</p>
<p>In that case, the problem isn’t data – it’s the average. This is a clean textbook trap where everything works perfectly in the textbook, but real-world data doesn’t behave nicely.</p>
<p>Some customers buy in bulk (very large orders), some return orders (negative quantities), and a few anomalies distort the entire picture.</p>
<p>In this article, we'll use the Online Retail Dataset to answer a simple but tricky question: What does “average” really mean in the real world?</p>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-the-dataset">The Dataset</a></p>
</li>
<li><p><a href="#heading-mean-the-sensitive-giant">Mean: The Sensitive Giant</a></p>
</li>
<li><p><a href="#heading-median-the-robust-middle">Median: The Robust Middle</a></p>
</li>
<li><p><a href="#heading-beyond-averages-understanding-spread-with-quartiles">Beyond Averages: Understanding Spread with Quartiles</a></p>
</li>
<li><p><a href="#heading-applying-iqr-to-our-dataset">Applying IQR to Our Dataset</a></p>
</li>
<li><p><a href="#heading-final-comparison-and-insights">Final Comparison and Insights</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-connect-with-me">Connect with me</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along here, you'll need:</p>
<p><strong>Basic Python knowledge:</strong> Understanding of variables and functions.</p>
<p><strong>The Pandas library:</strong> Familiarity with loading data and basic DataFrame operations.</p>
<p><strong>A development environment:</strong> Access to a tool like Jupyter Notebook, VS Code, or Google Colab.</p>
<p><strong>A Dataset:</strong> For this analysis, I used the Online Retail Dataset, which is available for download <a href="https://archive.ics.uci.edu/dataset/352/online+retail">here</a>.</p>
<h2 id="heading-the-dataset"><strong>The Dataset</strong></h2>
<p>We'll work with the Online Retail Dataset, a real-world transactional dataset containing purchase records from a UK-based online retail store.</p>
<ol>
<li><p><strong>Source:</strong> UCI Machine Learning Repository</p>
</li>
<li><p><strong>Collected by:</strong> UK-based online retail company (2010–2011)</p>
</li>
<li><p><strong>Size:</strong> 541,909 transactions</p>
</li>
<li><p><strong>Features:</strong> 8 attributes (InvoiceNo, StockCode, Description, Quantity, InvoiceDate, UnitPrice, CustomerID, Country)</p>
</li>
<li><p><strong>Ownership:</strong> Public dataset hosted by UCI</p>
</li>
<li><p><strong>License:</strong> Open for research and educational use</p>
</li>
</ol>
<h2 id="heading-mean-the-sensitive-giant">Mean: The Sensitive Giant</h2>
<p>In statistics and data analysis, the terms "<strong>average</strong>" and "<strong>arithmetic mean</strong>" are often used interchangeably. We aim to find the mean total price in our dataset. Mean in the context of the Online Retail Dataset is given as:</p>
<p>$$\text{Average Order Value} = \frac{\text{Sum of all TotalPrice values}}{\text{Number of transactions}}$$</p>
<p>In our dataset, the mean is calculated by summing all transaction values (including bulk purchases and returns) and dividing by the total number of transactions. This means every value, irrespective of unusually high or any negative values, directly influences the final average.</p>
<pre><code class="language-python"># Load the dataset
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/00352/Online%20Retail.xlsx"
df = pd.read_excel(url, engine='openpyxl')

# Clean and Feature Engineering
df = df.dropna(subset=['CustomerID'])
df['TotalPrice'] = df['Quantity'] * df['UnitPrice']

# Calculate the Mean (Average Order Value)
mean_value = df['TotalPrice'].mean()
print(f"Average Order Value (Mean): {mean_value:.2f}")
</code></pre>
<p>The results are as follows:</p>
<pre><code class="language-python">Average Order Value (Mean): 20.40
</code></pre>
<p>At first glance, the results may look promising: every transaction contributes equally. But that’s where the problem lies. Sometimes a few transactions, which are extremely high or low, affect the mean for all customers who lie in the closer range.</p>
<p>Take a look at the graph for the mean below.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6942c2903c5d674e359eaf1e/583bebff-0e5e-44b8-80cb-48e4662b9abf.png" alt="The graph shows the calculated mean for the Online Retail Dataset, where we get a mean of 20.40" style="display:block;margin:0 auto" width="876" height="547" loading="lazy">

<p>The graph shows the mean Total Price for the Online Retail Dataset. We get a mean of 20.42. (Image by Author)</p>
<p>The graph shows <strong>a right-skewed distribution</strong> where the calculated mean of 20.40 is actually a textbook trap. The tallest bar clearly shows that the majority of transactions lie in the range of \(8 - \)15 range, but the <strong>red line</strong> is being dragged to the right by the <strong>long tail</strong> of high-value bulk orders by some customers.</p>
<p>In this scenario, the average price is well above what a typical customer actually spends because it's highly sensitive to outliers – and in reality, the bulk of the data lives in the lower price range.</p>
<p>In simple words, the mean is being pulled by some extreme values to the right, especially by some lying in the range of 200–300, which is noticeable in the graph.</p>
<h2 id="heading-median-the-robust-middle">Median: The Robust Middle</h2>
<p>When the mean is distorted by extreme values, we need a metric that remains unaffected by such outliers. This is where the median comes into play.</p>
<p>Median is defined as the <strong>middle value after sorting the data.</strong></p>
<p>In our dataset, we sort all the transactions and pick the middle one.</p>
<p>The formula for calculating the median is:</p>
<p>$$\text{Median} = \begin{cases} X_{\left[ \frac{n+1}{2} \right]} &amp; \text{if } n \text{ is odd} \ \frac{X_{\left[ \frac{n}{2} \right]} + X_{\left[ \frac{n}{2} + 1 \right]}}{2} &amp; \text{if } n \text{ is even} \end{cases}$$</p>
<p>Unlike the mean, the median doesn't depend on extreme values, and it cares only about the position of the data, not the magnitude.</p>
<pre><code class="language-python"># Clean and Feature Engineering
df = df.dropna(subset=['CustomerID'])
df['TotalPrice'] = df['Quantity'] * df['UnitPrice']

# Calculate only the Median
median_value = df['TotalPrice'].median()
print(f"Typical Order Value (Median): {median_value:.2f}")
</code></pre>
<p>The results are as follows:</p>
<pre><code class="language-python">Typical Order Value (Median): 11.10
</code></pre>
<p>Now you'll notice that the result lies in the \(8 — \)15 range, where most of the transactions lie.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6942c2903c5d674e359eaf1e/d89a4912-0e44-485e-8ea0-ff559cea6eba.png" alt="The figure demonstrates the graph for the median, where we get an accurate value of the transactions by the customers." style="display:block;margin:0 auto" width="876" height="547" loading="lazy">

<p>The figure demonstrates the graph for the median, where we get an accurate value of the transactions by the customers. (Image by Author)</p>
<p>In the previous graph, the mean was pulled to the right by large orders, but the median just asks what the middle customer spends. So even if someone spends $300 or some transactions are negative, the median stays stable.</p>
<p>In the above figure <strong>the median graph</strong> accurately highlights the range where most of the customers lie.</p>
<h2 id="heading-beyond-averages-understanding-spread-with-quartiles"><strong>Beyond Averages: Understanding Spread with Quartiles</strong></h2>
<p>So far, we've studied the median, but knowing the center is not enough.</p>
<p>To truly understand how customer spending is, we need to understand how the data is spread, and this is where quartiles come into play.</p>
<p>Quartiles divide the dataset into the following parts:</p>
<ol>
<li><p><strong>Q1(25th percentile):</strong> 25% of transactions are below this.</p>
</li>
<li><p><strong>Q2 (50th percentile):</strong> Median</p>
</li>
<li><p><strong>Q3 (75th percentile):</strong> 75% of transactions are below this.</p>
</li>
</ol>
<p>This is formally expressed as the Interquartile Range (IQR):</p>
<p>$$IQR = Q_3 - Q_1$$</p>
<h3 id="heading-the-iqr-detecting-outliers"><strong>The IQR: Detecting Outliers</strong></h3>
<p>The IQR measures the spread of the middle 50%.</p>
<p>If the IQR is small, then the data is concentrated. If it's large, the data is spread out. The IQR also helps us identify outliers mathematically.</p>
<p>Outlier Rule:</p>
<ol>
<li><p><strong>Lower Bound = Q1 — 1.5 * IQR</strong></p>
</li>
<li><p><strong>Upper Bound = Q3 + 1.5 * IQR</strong></p>
</li>
</ol>
<h4 id="heading-a-simple-example-to-understand-iqr">A Simple Example to Understand IQR</h4>
<p>Consider the following transaction values:</p>
<p>$$\left[ 5, 8, 10, 12, 15, 18, 20 \right]$$</p>
<h4 id="heading-step-1-find-the-median-q2">Step 1: Find the Median (Q2):</h4>
<p>The middle value is:</p>
<p>$$Q_2 = 12$$</p>
<h4 id="heading-step-2-find-q1-lower-quartile">Step 2: Find Q1 (Lower Quartile):</h4>
<p>The lower half is [5, 8, 10]. The median of the lower half is:</p>
<p>$$Q_1 = 8$$</p>
<h4 id="heading-step-3-find-q3-upper-quartile">Step 3: Find Q3 (Upper Quartile):</h4>
<p>The upper half is [15, 18, 20]. The median of the upper half is:</p>
<p>$$Q_3 = 18$$</p>
<h4 id="heading-step-4-calculate-iqr">Step 4: Calculate IQR:</h4>
<p>$$IQR = Q_3 - Q_1 = 18 - 8 = 10$$</p>
<h4 id="heading-step-5-find-outlier-bounds">Step 5: Find Outlier Bounds:</h4>
<p>$$\begin{aligned} \text{Lower Bound} &amp;= Q_1 - 1.5 \times IQR = 8 - 15 = -7 \ \text{Upper Bound} &amp;= Q_3 + 1.5 \times IQR = 18 + 15 = 33 \end{aligned}$$</p>
<p>Any value <strong>below -7 or above 33</strong> is an outlier (but in this demo problem, no outliers exist).</p>
<h2 id="heading-applying-iqr-to-our-dataset"><strong>Applying IQR to Our Dataset</strong></h2>
<p>In our retail dataset, instead of neat values, we have bulk values and even negative returns.</p>
<pre><code class="language-python"># 1. Calculate IQR and Bounds
Q1 = df['TotalPrice'].quantile(0.25)
Q3 = df['TotalPrice'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
</code></pre>
<p>When we calculate IQR for our dataset, we get:</p>
<pre><code class="language-python">Lower Bound: -18.75
Upper Bound: 42.45
Number of Outliers: 33180
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/6942c2903c5d674e359eaf1e/e528db9b-57f9-4ee4-b331-143c2b1947fb.png" alt="The figure demonstrates the outlier range for our dataset" style="display:block;margin:0 auto" width="1036" height="547" loading="lazy">

<p>The graph demonstrates outliers, which are any values falling outside the range of -18.75 to 42.45. (Image by Author)</p>
<p>As the graph shows, the values outside the range -18.75 to 42.45 are considered outliers. These values will be removed.</p>
<h3 id="heading-revisiting-the-mean-after-removing-outliers">Revisiting the Mean After Removing Outliers</h3>
<p>Using the IQR method, we've removed extreme transactions that fell outside the typical spending range.</p>
<pre><code class="language-python"># Clean and Feature Engineering
df = df.dropna(subset=['CustomerID'])
df['TotalPrice'] = df['Quantity'] * df['UnitPrice']

# Original Mean
mean_value = df['TotalPrice'].mean()
print(f"Original Mean: {mean_value:.2f}")

# IQR Calculation
Q1 = df['TotalPrice'].quantile(0.25)
Q3 = df['TotalPrice'].quantile(0.75)
IQR = Q3 - Q1

# Define bounds
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

print(f"Lower Bound: {lower_bound:.2f}")
print(f"Upper Bound: {upper_bound:.2f}")

# Remove Outliers
df_no_outliers = df[(df['TotalPrice'] &gt;= lower_bound) &amp; (df['TotalPrice'] &lt;= upper_bound)]

# New Mean after removing outliers
new_mean = df_no_outliers['TotalPrice'].mean()
print(f"Mean after removing outliers: {new_mean:.2f}")
</code></pre>
<p>After recomputing, we get:</p>
<pre><code class="language-python">Original Mean: 20.40
Lower Bound: -18.75
Upper Bound: 42.45
Mean after removing outliers: 11.63
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/6942c2903c5d674e359eaf1e/17e6c2d0-883f-4e48-b45b-d1bf93164c63.png" alt="The graph demonstrates that the mean improves significantly after all outliers are removed. (Image by Author)" style="display:block;margin:0 auto" width="876" height="547" loading="lazy">

<p>Removing outliers significantly shifts the mean toward the region where most transactions occur. We now have a much better mean of 11.63 as opposed to the right-stretched mean of 20.40 we got with outliers.</p>
<h2 id="heading-final-comparison-and-insights"><strong>Final Comparison and Insights</strong></h2>
<p>Looking at the results from all the graphs, we get a complete understanding of the dataset. The original mean was 20.40, which appeared to be significantly higher than the most transactions that actually occurred. In that case, the mean was pulled upward by some of the high-valued transactions and was distorted by the outliers.</p>
<p>The median, on the other hand, was 11.10, which lies within the range where most transactions are concentrated. This shows that the median is a much better representation of what a typical customer spends, as it's not affected by extreme values.</p>
<p>After removing the outliers using the IQR, the mean dropped to 11.63, bringing it very close to the median. This confirms that the earlier mean was not inherently wrong, but was simply influenced by extreme values in the data. Once those values were handled, the mean became a much more reliable measure of central tendency.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>The results show that the mean can be misleading when data contains outliers. In our dataset, the original mean of 20.40 overstated customer spending, while the median (11.10) gave a more realistic picture. After removing outliers, the mean shifted to 11.63, aligning closely with the median.</p>
<p>This highlights a key lesson: <strong>The mean isn't wrong, but it must be used with an understanding of the data.</strong></p>
<p>Choosing the right measure of average depends on the dataset, and in messy real-world scenarios, the median or a cleaned mean often tells the true story.</p>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ol>
<li><p><a href="https://medium.com/@rakshathnaik62">Medium</a></p>
</li>
<li><p><a href="https://www.linkedin.com/in/rakshath-/">LinkedIN</a></p>
</li>
</ol>
<p>If you want to dive deeper, you can visit: <a href="https://qubrica.com/mean-median-mode-python-guide/"><strong>Mean vs Median vs Mode: Understanding Central Tendency in Data Analysis</strong></a><strong>.</strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Deploy a Serverless Spam Classifier Using Scikit-Learn, AWS Lambda, & API Gateway ]]>
                </title>
                <description>
                    <![CDATA[ In today's digital world, spam is no longer just an annoyance - it's a growing security threat. To combat this, developers often turn to machine learning to build intelligent filters that can distingu ]]>
                </description>
                <link>https://www.freecodecamp.org/news/deploying-serverless-spam-classifier/</link>
                <guid isPermaLink="false">69f2e347b18c978233780179</guid>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ serverless ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Data Architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rakshath Naik ]]>
                </dc:creator>
                <pubDate>Thu, 30 Apr 2026 05:06:15 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/08672d22-a4df-4b99-8ef7-fffd18f5dc07.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today's digital world, spam is no longer just an annoyance - it's a growing security threat. To combat this, developers often turn to machine learning to build intelligent filters that can distinguish legitimate emails from malicious ones.</p>
<p>While building a machine learning model in a notebook is relatively straightforward, the real challenge lies in the last mile: deploying that model into a scalable, production-ready system that users can actually interact with.</p>
<p>In this project, I built an end-to-end serverless spam classifier, combining Scikit-learn for model development with AWS Lambda, Amazon S3, and Amazon API Gateway for deployment. The result is a lightweight, scalable API that can classify messages in real time.</p>
<p>The system is designed to be modular and cost-efficient, allowing the model to be retrained and updated independently without affecting the live API. From detecting "free iPhone" scams to identifying phishing attempts, this project demonstrates how to bridge the gap between machine learning experimentation and real-world deployment.</p>
<h3 id="heading-table-of-contents">Table of&nbsp;Contents</h3>
<ul>
<li><p><a href="#heading-1-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-2-building-the-brain-the-model">Building the Brain: The Model</a></p>
</li>
<li><p><a href="#heading-3-deploying-the-model-to-aws">Deploying the Model to AWS</a></p>
</li>
<li><p><a href="#heading-4-how-to-run-the-project-locally">How to Run The Project Locally</a></p>
</li>
<li><p><a href="#heading-5-our-project-architecture">Our Project Architecture</a></p>
</li>
<li><p><a href="#heading-6-conclusion-the-power-of-serverless-ai">Conclusion: The Power of Serverless AI</a></p>
</li>
<li><p><a href="#heading-7-acknowledgment-references">Acknowledgment / References</a></p>
</li>
</ul>
<h2 id="heading-1-prerequisites">1. Prerequisites</h2>
<ol>
<li><p><strong>Fundamental skills:</strong> Basic proficiency in Python and understanding of Machine Learning concepts like classification.</p>
</li>
<li><p><strong>AWS account:</strong> Access to an AWS account with permissions for Lambda, S3, and API Gateway.</p>
</li>
<li><p><strong>Environment:</strong> Python 3.11 installed, along with libraries like scikit-learn, pandas, and joblib.</p>
</li>
<li><p><strong>AWS CLI:</strong> Configured on your local machine for file uploads.</p>
</li>
<li><p><strong>HuggingFace account:</strong> You can directly download the model from my account.</p>
</li>
</ol>
<h2 id="heading-2-building-the-brain-the-model">2. Building the Brain: The&nbsp;Model</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6942c2903c5d674e359eaf1e/b43af198-1472-4914-9469-6cd5ca5384e2.png" alt="Demonstrational image to show the brain of AI." style="display:block;margin:0 auto" width="1000" height="563" loading="lazy">

<p><em>Photo by</em> <a href="https://unsplash.com/@steve_j?utm_source=medium&amp;utm_medium=referral"><em>Steve A Johnson</em></a> <em>on</em> <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral"><em>Unsplash</em></a></p>
<p>At the heart of this project lies a supervised learning approach. Instead of simply specifying which words are considered spam, we'll provide the computer with a dataset and an algorithm, enabling it to learn and identify spam patterns on its own.</p>
<h3 id="heading-1-vectorization-turning-text-into-math">1. Vectorization: Turning Text into&nbsp;Math</h3>
<p>Machine Learning models can't <strong>read</strong> text. They require numerical input. To solve this, we used the <a href="https://www.freecodecamp.org/news/how-to-extract-keywords-from-text-with-tf-idf-and-pythons-scikit-learn-b2a0f3d7e667/">TF-IDF</a> (Term Frequency-Inverse Document Frequency) Vectorizer.</p>
<pre><code class="language-python">feature_extraction = TfidfVectorizer(min_df=1, stop_words='english', lowercase=True)
X_train_features = feature_extraction.fit_transform(X_train
</code></pre>
<p>Here's the mathematical formula:</p>
<p>$$w_{i,j} = tf_{i,j} \times \log \left( \frac{N}{df_i} \right)$$</p>
<p>TF-IDF term definitions:</p>
<ul>
<li><p><strong>wᵢ,ⱼ (Weight):</strong> The final importance score of a specific word in a document.</p>
</li>
<li><p><strong>tfᵢ,ⱼ (Term Frequency):</strong> How often a word appears in a single email.</p>
</li>
<li><p><strong>N (Total Documents):</strong> The total count of all emails in your dataset.</p>
</li>
<li><p><strong>dfᵢ (Document Frequency):</strong> The number of different emails that contain this specific word.</p>
</li>
<li><p><strong>log(N/dfᵢ) (IDF):</strong> A penalty that lowers the score of common words like <strong>the</strong> or <strong>is</strong> that appear everywhere.</p>
</li>
</ul>
<p>It cleans the data by removing common words, converts all text to lowercase for consistency, and assigns more importance to rare and meaningful words while giving less importance to frequently used words.</p>
<h3 id="heading-2-training-the-logistic-regression-engine">2. Training: The Logistic Regression Engine</h3>
<p>We'll use <strong>Logistic Regression</strong> here, a classification algorithm that predicts the probability of an outcome.</p>
<p>In this stage, we feed our vectorized training data into the Logistic Regression algorithm. The goal is to establish a mathematical relationship between specific word weights and the <strong>Spam</strong> or <strong>Ham</strong> label.</p>
<p>During training, the model iteratively adjusts its internal parameters to minimize error, eventually learning that words like winner or free correlate highly with spam, while conversational language correlates with legitimate messages.</p>
<pre><code class="language-python">model = LogisticRegression()
model.fit(X_train_features, Y_train)
</code></pre>
<p>In our case, it calculates the probability that an email belongs to spam or HAM.</p>
<p>The algorithm uses the Sigmoid function to map any real-valued number into a value between 0 and 1.</p>
<p>$$P(y=1|x) = \frac{1}{1 + e^{-(z)}}$$</p>
<p>where z = β₀ + β₁x₁ +&nbsp;… + βₙxₙ.</p>
<h3 id="heading-3-evaluation-testing-the-intelligence">3. Evaluation: Testing the Intelligence</h3>
<p>After training, we need to verify if the brain actually works on data it hasn't seen before.</p>
<pre><code class="language-python">prediction_on_test_data = model.predict(X_test_features)
accuracy_on_test_data = accuracy_score(Y_test, prediction_on_test_data)
</code></pre>
<p>By comparing the model’s predictions against the actual labels in our test set, we calculate an Accuracy Score. This gives us the confidence that the model is ready for the real world (achieving ~94% accuracy in our tests).</p>
<h3 id="heading-4-exporting-the-logic-serialization">4. Exporting the Logic (Serialization)</h3>
<p>To move this brain from our local Python environment to the AWS Cloud, we'll use Joblib to save our work into binary files (.pkl).</p>
<pre><code class="language-python">joblib.dump(model, 'spam_model.pkl')
joblib.dump(feature_extraction, 'vectorizer.pkl')
</code></pre>
<p>We use the Pickle format because it allows us to freeze complex Python objects (mathematical weights and word mappings) into a portable binary format that can be instantly re-animated in the cloud.</p>
<p>We need the Vectorizer to translate new user text into the exact numerical coordinates the Model was trained to understand. Using one without the other is like having a key but no lock.</p>
<p>The trained Logistic Regression model and TF-IDF vectorizer are openly available for the community on Hugging Face here: <a href="https://huggingface.co/rakshath1/mail-spam-detector">Get the model on HuggingFace</a>.</p>
<h2 id="heading-3-deploying-the-model-to-aws">3. Deploying the Model to&nbsp;AWS</h2>
<p>Training a model is science, while deploying it is engineering. To make this classifier accessible to the world, we'll use a serverless stack that scales automatically and incurs nearly no maintenance costs.</p>
<h3 id="heading-1-model-storage-amazon-s3">1. Model Storage: Amazon&nbsp;S3</h3>
<p>First, we'll uploade our&nbsp;.pkl files to an S3 bucket. By decoupling the model from the code, we can update the AI's intelligence (simply by overwriting the file in S3) without redeploying the backend code. It makes the system highly maintainable.</p>
<h3 id="heading-2-the-production-backend-aws-lambda">2. The Production Backend: AWS&nbsp;Lambda</h3>
<p>To make the AI accessible, we'll move from a local script to a Serverless Cloud Architecture. This ensures the model is always available without the cost of a 24/7 server.</p>
<p>The deployment environment is AWS Lambda (Python 3.11). Since Lambda is a lightweight environment, it doesn't include Scikit-Learn or Joblib. To provide these, we'll download and store them in our S3 bucket and import them through the layers.</p>
<p><strong>Commands in AWS CLI:</strong></p>
<pre><code class="language-python">
# 1. Create a workspace
mkdir ml_layer &amp;&amp; cd ml_layer

# 2. Install scikit-learn and its dependencies into a folder
pip install \
    --platform manylinux2014_x86_64 \
    --target=python/lib/python3.11/site-packages \
    --implementation cp \
    --python-version 3.11 \
    --only-binary=:all: \
    scikit-learn joblib

# 3. Zip the folder
zip -r sklearn_lib.zip python

# 4. Upload to S3 (Using AWS CLI)
aws s3 cp sklearn_lib.zip s3://YOUR-BUCKET-NAME/
</code></pre>
<p>We store the Scikit-Learn library as a ZIP in S3 to bypass the AWS Lambda deployment package size limit. This allows the function to dynamically load heavy dependencies only when needed without bloating the core code.</p>
<p><strong>The Lambda Function:</strong></p>
<pre><code class="language-python">
import json
import boto3
import os
import sys
from io import BytesIO

# Ensures the custom Lambda layer(containing sklearn/joblib)
sys.path.append('/opt/python')

try:
    import joblib
except ImportError:
    # Fallback for specific Scikit-Learn distributions
    from sklearn.utils import _joblib as joblib

# Initialize S3 client
s3 = boto3.client('s3')

# Use placeholders for the article so readers can insert their own values
BUCKET_NAME = 'YOUR_S3_BUCKET_NAME' 
MODEL_KEY = 'spam_model.pkl'
VECTORIZER_KEY = 'vectorizer.pkl'

# Global variables for 'Warm Start' caching (improves performance by keeping model in RAM)
model = None
vectorizer = None

def load_model():
    """Downloads model files from S3 only if they aren't already in RAM"""
    global model, vectorizer
    if model is None or vectorizer is None:
        try:
            # 1. Load the Logistic Regression Model from S3
            m_obj = s3.get_object(Bucket=BUCKET_NAME, Key=MODEL_KEY)
            model = joblib.load(BytesIO(m_obj['Body'].read()))
            
            # 2. Load the TF-IDF Vectorizer directly from S3
            v_obj = s3.get_object(Bucket=BUCKET_NAME, Key=VECTORIZER_KEY)
            vectorizer = joblib.load(BytesIO(v_obj['Body'].read()))
        except Exception as e:
            raise Exception(f"Failed to load .pkl files from S3: {str(e)}")

def lambda_handler(event, context):
    try:
        # Ensure model and vectorizer are ready before processing
        load_model()
        
        # Handles both direct Lambda tests and API Gateway POST requests
        body = event.get('body', event)
        if isinstance(body, str):
            body = json.loads(body)
            
        text = body.get('text', '')
            
        if not text:
            return {
                'statusCode': 400,
                'body': json.dumps({'error': 'No text provided.'})
              }

        # 1. Transform input text to numeric features using the trained Vectorizer
        data_vec = vectorizer.transform([text])
        
        # 2. Predict using the Logistic Regression Model 
        prediction = int(model.predict(data_vec)[0])
        
      # 3. Map numeric result to human-readable label
        result_label = "HAM" if prediction == 1 else "SPAM"
        
        # RESPONSE WITH CORS
        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*' # needed for cross-domain web integration
            },
            'body': json.dumps({
                'status': 'success',
                'classification': result_label,
                'input_text': text
            })
        }
        
    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({'error_message': f"Inference Error: {str(e)}"})
        }
</code></pre>
<p>Key features of the Lambda function:</p>
<ol>
<li><p><strong>Warm start caching:</strong> By defining the model and vectorizer variables outside the lambda_handler, we store them in the container's memory. This significantly reduces cold start latency for subsequent requests.</p>
</li>
<li><p><strong>Dynamic dependency loading:</strong> The <strong>sys.path.append('/opt/python')</strong> line allows us to import heavy libraries from S3/Layers without exceeding the upload limit.</p>
</li>
<li><p><strong>Bimodal input handling:</strong> The function is designed to handle both direct JSON testing from the AWS console and stringified payloads sent via API Gateway.</p>
</li>
</ol>
<h3 id="heading-3-the-api-gateway-the-bridge-to-the-web">3. The API Gateway - The Bridge to the&nbsp;Web</h3>
<img src="https://cdn.hashnode.com/uploads/covers/6942c2903c5d674e359eaf1e/8aa3e8d7-569a-4dd5-a6ac-184922474952.png" alt="Demonstrational image to show the API Gateway." style="display:block;margin:0 auto" width="1000" height="563" loading="lazy">

<p>Photo by <a href="https://unsplash.com/@growtika?utm_source=medium&amp;utm_medium=referral">Growtika</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></p>
<h4 id="heading-creating-the-rest-api">Creating the REST API</h4>
<p>Next we'll create a REST API with a single POST method. Why POST, you might be wondering? Well, we need to securely send a JSON payload containing the user’s text message to our model.</p>
<ol>
<li><p>First navigate to the Amazon API Gateway console and select Create API -&gt; REST API.</p>
</li>
<li><p>Give your API a name, such as EmailSpamPredictor-API, and set the Endpoint Type to Regional.</p>
</li>
<li><p>Then in the left sidebar, click Resources and enter a resource name (e.g: <strong>/ predict</strong> as entered by me)</p>
</li>
<li><p>Next click the create method and select POST and then select Lambda Function for integration type</p>
</li>
<li><p>Ensure Lambda Proxy integration is enabled (this allows the full request to pass through to your code).</p>
</li>
</ol>
<p><strong>The CORS Configuration (The Troubleshooting Hub)</strong><br>This is where many developers encounter the dreaded <strong>Connection Error</strong>. Since our API is hosted on AWS, and if your front-end is on a separate website, the browser’s Same-Origin Policy will block the request by default.</p>
<p>To fix this, we'll enable <strong>CORS:</strong></p>
<ol>
<li><p><strong>Access-Control-Allow-Origin:</strong> Set to * (or specifically to your domain) to tell the browser that the API is allowed to talk to your front-end.</p>
</li>
<li><p><strong>The OPTIONS method:</strong> API Gateway creates an OPTIONS method automatically. This handles the Preflight request where the browser asks, “Are you allowed to receive data from me?” before sending the actual text.</p>
</li>
<li><p><strong>Access-Control-Allow-Headers:</strong> In the screenshot, you'll notice headers like Content-Type and Authorization are allowed. This ensures that when our JavaScript fetch() call sets the content type to application/json, the API Gateway doesn't reject it.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/6942c2903c5d674e359eaf1e/cf5c87c6-f374-4dda-8001-77a0aab52672.png" alt="Image illustrates the CORS configuration for our project. " style="display:block;margin:0 auto" width="1487" height="617" loading="lazy">

<p>Image illustrates the CORS configuration for our project. (Image by author)</p>
<h4 id="heading-deployment-stages">Deployment Stages</h4>
<p>Once the API is deployed to a production stage, AWS generates a permanent Invoke URL. This acts as the public gateway to our model and typically follows this structure: <a href="https://%5Bapi-id%5D.execute-api.%5Bregion%5D.amazonaws.com/prod/classify">https://[api-id].execute-api.[region].amazonaws.com/prod/classify</a>.</p>
<h4 id="heading-connecting-the-frontend-the-javascript-layer">Connecting the Frontend (The JavaScript Layer)</h4>
<p>With the API live, we can now write a simple JavaScript function to talk to our model. This script runs whenever a user clicks the <strong>Analyze</strong> button on your site.</p>
<pre><code class="language-python">
async function checkSpam() {
    const message = document.getElementById("userInput").value;
    const apiUrl = "YOUR_API_GATEWAY_INVOKE_URL";

    try {
        const response = await fetch(apiUrl, {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({ "text": message })
        });

        const data = await response.json();
        
        // Display result on the webpage
        const resultElement = document.getElementById("result");
        resultElement.innerText = `Prediction: ${data.classification}`;
        resultElement.style.color = data.classification === "SPAM" ? "red" : "green";

    } catch (error) {
        console.error("Error:", error);
        alert("Could not connect to the Spam Detector API.");
    }
}
</code></pre>
<h2 id="heading-4-how-to-run-the-project-locally">4. How to Run The Project&nbsp;Locally</h2>
<p>You can store the front-end as an HTML file. Once it's ready, you shouldn’t just double-click the&nbsp;.html file. Opening it as a <strong>file</strong> in your browser can cause security restrictions. Instead, you should host it using a simple local server.</p>
<p><strong>Step 1:</strong> Open the terminal or Command Prompt.</p>
<p><strong>Step 2:</strong> Navigate to your project folder</p>
<pre><code class="language-shell">cd [PATH_TO_YOUR_FOLDER]
</code></pre>
<p><strong>Step 3:</strong> Start a local Python web server.</p>
<pre><code class="language-shell">python -m http.server 8000
</code></pre>
<p><strong>Step 4:</strong> Access the application.</p>
<p>Open your browser and navigate to:<br><a href="http://localhost:8000/your-file-name.html">http://localhost:8000/your-file-name.html</a></p>
<p><strong>Watch the Demo:</strong></p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/q2X_azntmzY" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<h2 id="heading-5-our-project-architecture">5. Our Project Architecture</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6942c2903c5d674e359eaf1e/c17673d4-5dd0-43dc-8e8d-3015bcd31864.png" alt="Image showing the Architecture Diagram of our Project." style="display:block;margin:0 auto" width="1000" height="563" loading="lazy">

<p>The image illustrates the architecture of our project (Building a Serverless Spam Classifier). It shows the process that takes place from the client input to the final model output. (Image by Author)</p>
<ol>
<li><p><strong>Client Front-End Interaction:</strong> The process starts on the far left. A user interacts with the web interface (for example, a website or a desktop app). They input text like <strong>WIN free iPhone now</strong> and trigger a request.</p>
</li>
<li><p><strong>The Entry Point: API Gateway:</strong> The request hits the Amazon API Gateway, which acts as the <strong>security guard</strong> and translator.&nbsp;<br><strong>(a)</strong> CORS OPTIONS handles the pre-flight handshake to ensure the browser has permission to talk to the AWS cloud.&nbsp;<br><strong>(b)</strong> Classification Request (POST) routes the actual message data to your backend logic.</p>
</li>
<li><p><strong>The Engine: AWS Lambda (Python 3.11):</strong>&nbsp;The central “<strong>lightbulb</strong>” represents your Lambda function. This is where the code you wrote lives. It doesn’t run 24/7 – it only wakes up when a request arrives.</p>
</li>
<li><p><strong>Storage &amp; Retrieval: S3 Bucket:</strong> Since Lambda is lightweight, it doesn’t store your heavy Machine Learning files internally.<br><strong>Dependency and Model Download:</strong> The function reaches out to the S3 Bucket to pull in the sklearn_<a href="http://lib.zip">lib.zip</a> (the engine) and the&nbsp;.pkl files (the intelligence).&nbsp;<br><strong>Required Dependency and Model:</strong> These assets are loaded into the Lambda’s temporary memory to prepare for the prediction.</p>
</li>
<li><p><strong>The Inference Pipeline:</strong>&nbsp;Inside the Lambda, a three-step mathematical cycle occurs:<br><strong>(a) Text Vectorizer:</strong> Translates the words into numbers.<br><strong>(b) Logistic Regression:</strong> Calculates the probability of spam based on those numbers.<br><strong>(c) Label:</strong> Assigns a final result (Spam or Ham).</p>
</li>
<li><p><strong>The Result Delivery:</strong> The result is sent back through the API Gateway, including the necessary CORS Headers to ensure the browser accepts it. The front-end then updates to show the “<strong>Result: SPAM</strong>” with a visual indicator.</p>
</li>
</ol>
<h2 id="heading-6-conclusion-the-power-of-serverless-ai">6. Conclusion: The Power of Serverless AI</h2>
<p>By merging the mathematical simplicity of Logistic Regression with the industrial strength of AWS Serverless Architecture, we have transformed a static Python script into a globally accessible, scalable API.</p>
<p>This project demonstrates that you don’t need a massive budget or a 24/7 dedicated server to deploy high-quality Machine Learning.</p>
<p>Using the S3-to-Lambda workaround allowed us to bypass common storage hurdles, ensuring that our Brain (the model) and its Muscle (Scikit-Learn) could function seamlessly within the cloud’s ephemeral environment. It bridges the gap between experimentation and real-world applications, making AI systems practical, efficient, and accessible.</p>
<h2 id="heading-7-acknowledgment-references">7. Acknowledgment / References</h2>
<ul>
<li><p>Pre-trained spam classification model: View on Hugging Face (<a href="https://huggingface.co/rakshath1/mail-spam-detector"><strong>rakshath1/mail-spam-detector · Hugging Face</strong></a><strong>)</strong></p>
</li>
<li><p>Scikit-learn <a href="https://scikit-learn.org/stable/api/index.html?utm_source=chatgpt.com">Documentation</a></p>
</li>
<li><p>AWS Lambda <a href="https://docs.aws.amazon.com/lambda/latest/api/welcome.html?utm_source=chatgpt.com">Documentation</a></p>
</li>
<li><p>Amazon S3 <a href="https://aws.amazon.com/documentation-overview/s3/">Documentation</a></p>
</li>
<li><p>Amazon API Gateway <a href="https://docs.aws.amazon.com/apigateway/">Documentation</a></p>
</li>
</ul>
<h3 id="heading-connect-with-me">Connect With Me</h3>
<ul>
<li><p><a href="https://medium.com/@rakshathnaik62">Medium</a></p>
</li>
<li><p><a href="https://www.linkedin.com/in/rakshath-/">LinkedIN</a></p>
</li>
</ul>
<p><strong>You may also like</strong></p>
<ol>
<li><p><a href="https://qubrica.com/python-polars-v-s-pandas-libraries-comparison/">How Polars overtook Pandas</a></p>
</li>
<li><p><a href="https://qubrica.com/devops-is-dead-platform-engineering-2026/"><strong>DevOps is Dead. Long Live Platform Engineering</strong></a></p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Fashion App That Helps You Organize Your Wardrobe  ]]>
                </title>
                <description>
                    <![CDATA[ I used to spend too long deciding what to wear, even when my closet was full. That frustration made the problem feel very clear to me: it was not about having fewer clothes. It was about having better ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-fashion-app-to-organize-your-wardrobe/</link>
                <guid isPermaLink="false">69de6abf91716f3cfb5448a1</guid>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mokshita V P ]]>
                </dc:creator>
                <pubDate>Tue, 14 Apr 2026 16:26:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/bf593ff6-6de8-4b30-ab0a-700c3410ccb1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I used to spend too long deciding what to wear, even when my closet was full.</p>
<p>That frustration made the problem feel very clear to me: it was not about having fewer clothes. It was about having better organization, better visibility, and better guidance when making outfit decisions.</p>
<p>So I built a fashion web app that helps users organize their wardrobe, get outfit suggestions, evaluate shopping decisions, and improve recommendations over time using feedback.</p>
<p>In this article, I’ll walk through what the app does, how I built it, the decisions I made along the way, and the challenges that shaped the final result.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a href="#heading-what-the-app-does">What the App Does</a></p>
</li>
<li><p><a href="#heading-why-i-built-it">Why I Built It</a></p>
</li>
<li><p><a href="#heading-tech-stack">Tech Stack</a></p>
</li>
<li><p><a href="#heading-product-walkthrough-what-users-see">Product Walkthrough (What Users See)</a></p>
</li>
<li><p><a href="#heading-how-i-built-it">How I Built It</a></p>
</li>
<li><p><a href="#heading-challenges-i-faced">Challenges I Faced</a></p>
</li>
<li><p><a href="#heading-what-i-learned">What I Learned</a></p>
</li>
<li><p><a href="#heading-what-i-want-to-improve-next">What I Want to Improve Next</a></p>
</li>
<li><p><a href="#heading-future-improvements">Future Improvements</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-the-app-does">What the App Does</h2>
<p>At a high level, the app combines six core capabilities:</p>
<ol>
<li><p>Wardrobe management</p>
</li>
<li><p>Outfit recommendations</p>
</li>
<li><p>Shopping suggestions</p>
</li>
<li><p>Discard recommendations</p>
</li>
<li><p>Feedback and usage tracking</p>
</li>
<li><p>Secure multi-user accounts</p>
</li>
</ol>
<p>Users can upload clothing items, explore suggested outfits, and mark recommendations as helpful or not helpful. They can also rate outfits and track whether items are worn, kept, or discarded.</p>
<p>That feedback becomes structured data for improving future recommendation quality.</p>
<h2 id="heading-why-i-built-it">Why I Built It</h2>
<p>I wanted to create something that felt personal and actually useful. A lot of fashion apps look polished, but they do not always help with everyday decisions. My goal was to build something that could make wardrobe management easier and outfit selection less overwhelming. The app needed to do three things well:</p>
<ul>
<li><p>store each user’s wardrobe data</p>
</li>
<li><p>personalize recommendations</p>
</li>
<li><p>learn from user feedback over time .</p>
</li>
</ul>
<p>That feedback loop mattered to me because it makes the app feel more alive instead of static.</p>
<h2 id="heading-tech-stack">Tech Stack</h2>
<p>Here are the tools I used to built the app:</p>
<ul>
<li><p>Frontend: React + Vite</p>
</li>
<li><p>Backend: FastAPI</p>
</li>
<li><p>Database: SQLite (local development)</p>
</li>
<li><p>Background jobs: Celery + Redis</p>
</li>
<li><p>Authentication: JWT (access + refresh token flow)</p>
</li>
<li><p>Deployment support: Docker and GitHub Codespaces</p>
</li>
</ul>
<p>This ended up giving me a pretty modular setup, which helped a lot as features started increasing: fast frontend iteration, clean API boundaries, and room to evolve recommendations separately from UI.</p>
<h2 id="heading-product-walkthrough-what-users-see">Product Walkthrough (What Users See)</h2>
<h3 id="heading-1-onboarding-and-account-setup">1. Onboarding and Account Setup</h3>
<p>To start using the app, a user needs to register, verify their email, and complete some profile basics.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/1ff4fb0d-dc97-4088-b720-db917b53ba5b.png" alt="Onboarding screen showing account creation, email verification, and profile fields for body shape, height, weight, and style preferences." style="display:block;margin:0 auto" width="1319" height="850" loading="lazy">

<p>Each account is isolated, so wardrobe history and recommendations stay user-specific.</p>
<p>In this onboarding screen above, you can see account creation, email verification, and profile fields for body shape, height, weight, and style preferences.</p>
<h3 id="heading-2-wardrobe-upload">2. Wardrobe Upload</h3>
<p>Users can upload clothing images .</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/d69bf10b-b79b-4294-923c-5c9e5840098a.png" alt="Wardrobe upload form showing clothing image analysis results with category, dominant color, secondary color, and pattern details." style="display:block;margin:0 auto" width="1320" height="625" loading="lazy">

<p>Image analysis labels each item and makes it searchable for recommendations. The wardrobe upload form shows image analysis results with category, dominant color, secondary color, and pattern details listed.</p>
<h3 id="heading-3-outfit-recommendations">3. Outfit Recommendations</h3>
<p>Users can request recommendations, then rate outputs.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/61527ddf-11e4-4284-92fd-2d0c948ae2db.png" alt="Outfit recommendation dashboard showing ranked outfit cards with feedback and rating actions." style="display:block;margin:0 auto" width="1011" height="692" loading="lazy">

<p>Above you can see the outfit recommendation dashboard that shows ranked outfit cards with feedback and rating actions. Recommendations are ranked by a weighted scoring model.</p>
<h3 id="heading-4-shopping-and-discard-assistants">4. Shopping and Discard Assistants</h3>
<p>The app evaluates new items against existing wardrobe data and flags low-value wardrobe items that may be worth removing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/88ed83c4-fdba-40e7-ad32-f77bdf21cb4d.png" alt="Shopping and discard analysis screen showing recommendation scores, written reasons, and styling guidance for each item." style="display:block;margin:0 auto" width="1324" height="852" loading="lazy">

<p>You can see the recommendation scores, written reasons (not just a binary decision), and styling guidance for each item above. It also features a "how to style it" incase the user still wants to keep the item.</p>
<h2 id="heading-how-i-built-it">How I Built It</h2>
<h3 id="heading-1-frontend-setup-react-vite">1. Frontend Setup (React + Vite)</h3>
<p>I used React + Vite because I wanted fast iteration and a clean component structure.</p>
<p>The frontend is split into feature areas like onboarding, wardrobe management, outfits, shopping, and discarded-item suggestions. I also keep API calls in a service layer so the UI components stay focused on rendering and interaction.</p>
<p>The snippet below is a simplified example of the API service pattern used in the app. It is not meant to be copy-pasted as-is, but it shows the same structure the frontend uses when talking to the backend.</p>
<p>Example API client pattern:</p>
<pre><code class="language-javascript">export async function getOutfitRecommendations(userId, params = {}) {
  const query = new URLSearchParams(params).toString();
  const url = `/users/\({userId}/outfits/recommend\){query ? `?${query}` : ""}`;

  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${localStorage.getItem("access_token")}`,
    },
  });

  if (!response.ok) {
    throw new Error("Failed to fetch outfit recommendations");
  }

  return response.json();
}
</code></pre>
<p>Here's what's happening in that snippet:</p>
<ul>
<li><p><code>URLSearchParams</code> builds optional query strings like <code>occasion</code>, <code>season</code>, or <code>limit</code>.</p>
</li>
<li><p>The request path is user-scoped, which keeps each user’s recommendations isolated.</p>
</li>
<li><p>The <code>Authorization</code> header sends the access token so the backend can verify the session.</p>
</li>
<li><p>The response is checked before parsing so the UI can surface a useful error if the request fails.</p>
</li>
</ul>
<p>This pattern kept the frontend simple and reusable as the number of API calls grew.</p>
<h3 id="heading-2-backend-architecture-with-fastapi">2. Backend Architecture with FastAPI</h3>
<p>The backend is organized around clear route groups:</p>
<ul>
<li><p>auth routes for register, login, refresh, logout, and sessions</p>
</li>
<li><p>user analysis routes</p>
</li>
<li><p>wardrobe CRUD routes</p>
</li>
<li><p>recommendation routes for outfits, shopping, and discard analysis</p>
</li>
<li><p>feedback routes for ratings and helpfulness signals</p>
</li>
</ul>
<p>One of the most important design choices was enforcing ownership checks on user-scoped resources. That prevented one user from accessing another user’s wardrobe or feedback data.</p>
<p>The backend snippet below is another simplified example from the app’s route layer. It shows the request validation and orchestration logic, while the actual scoring work stays in the recommendation service.</p>
<pre><code class="language-python">@app.get("/users/{user_id}/outfits/recommend")
def recommend_outfits(user_id: int, occasion: str | None = None, season: str | None = None, limit: int = 10):
    user = get_user_or_404(user_id)
    wardrobe_items = get_user_wardrobe(user_id)

    if len(wardrobe_items) &lt; 2:
        raise HTTPException(status_code=400, detail="Not enough wardrobe items")

    recommendations = outfit_generator.generate_outfit_recommendations(
        wardrobe_items=wardrobe_items,
        body_shape=user.body_shape,
        undertone=user.undertone,
        occasion=occasion,
        season=season,
        top_k=limit,
    )

    return {"user_id": user_id, "recommendations": recommendations}
</code></pre>
<p>Here's how to read that code:</p>
<ul>
<li><p><code>get_user_or_404</code> loads the profile data needed for personalization.</p>
</li>
<li><p><code>get_user_wardrobe</code> fetches only the current user’s items.</p>
</li>
<li><p>The minimum wardrobe check prevents the recommendation logic from running on incomplete data.</p>
</li>
<li><p><code>generate_outfit_recommendations</code> handles the scoring logic separately, which keeps the route handler small and easier to test.</p>
</li>
<li><p>The response returns the results in a shape the frontend can consume directly.</p>
</li>
</ul>
<p>That separation helped keep the API layer readable while the recommendation logic stayed isolated in its own service.</p>
<h3 id="heading-3-recommendation-logic">3. Recommendation Logic</h3>
<p>I intentionally started with deterministic rules before introducing heavy ML. That made behavior easier to debug and explain.</p>
<p>The outfit recommender scores combinations using weighted signals:</p>
<p>$$\text{outfit score} = 0.4 \cdot \text{color harmony} + 0.4 \cdot \text{body-shape fit} + 0.2 \cdot \text{undertone fit}$$</p>
<p>The snippet below is a simplified example from the recommendation engine. It shows how the app combines multiple signals into a single score:</p>
<pre><code class="language-python">def score_outfit(combo, user_context):
    color_score = color_harmony.score(combo)
    shape_score = body_shape_rules.score(combo, user_context.body_shape)
    undertone_score = undertone_rules.score(combo, user_context.undertone)

    total = 0.4 * color_score + 0.4 * shape_score + 0.2 * undertone_score
    return round(total, 3)
</code></pre>
<p>The logic behind this approach is straightforward:</p>
<ul>
<li><p>color harmony helps the outfit feel visually coherent</p>
</li>
<li><p>body-shape scoring helps the outfit feel flattering</p>
</li>
<li><p>undertone scoring helps the colors work better with the user’s profile</p>
</li>
</ul>
<p>I used a similar structure for discard recommendations and shopping suggestions, but with different factors and thresholds.</p>
<h3 id="heading-4-authentication-and-secure-multi-user-design">4. Authentication and Secure Multi-user Design</h3>
<p>Security was one of the most important parts of this build.</p>
<p>I implemented:</p>
<ul>
<li><p>short-lived access tokens</p>
</li>
<li><p>refresh tokens with JTI tracking</p>
</li>
<li><p>token rotation on refresh</p>
</li>
<li><p>session revocation (single session and all sessions)</p>
</li>
<li><p>email verification and password reset flows</p>
</li>
</ul>
<p>The snippet below is a simplified example of the refresh-token lifecycle used in the app. It shows the important control points rather than every helper function:</p>
<pre><code class="language-python">def refresh_access_token(refresh_token: str):
    payload = decode_jwt(refresh_token)
    jti = payload["jti"]

    token_record = db.get_refresh_token(jti)
    if not token_record or token_record.revoked:
        raise AuthError("Invalid refresh token")

    new_refresh, new_jti = issue_refresh_token(payload["sub"])
    token_record.revoked = True
    token_record.replaced_by_jti = new_jti

    new_access = issue_access_token(payload["sub"])
    return {"access_token": new_access, "refresh_token": new_refresh}
</code></pre>
<p>What this code is doing:</p>
<ul>
<li><p>It decodes the refresh token and looks up its JTI in the database.</p>
</li>
<li><p>It rejects reused or revoked sessions, which helps prevent replay attacks.</p>
</li>
<li><p>It rotates the refresh token instead of reusing it.</p>
</li>
<li><p>It issues a fresh access token so the session stays valid without forcing the user to log in again.</p>
</li>
</ul>
<p>This design made multi-device sessions safer and gave me server-side control over logout behavior.</p>
<h3 id="heading-5-background-jobs-for-long-running-operations">5. Background Jobs for Long-running Operations</h3>
<p>Image analysis can be expensive, especially when the app needs to classify clothing, analyze colors, and estimate body-shape-related signals. To keep the request path responsive, I added Celery + Redis support for background tasks.</p>
<p>That gave the app two modes:</p>
<ul>
<li><p>synchronous processing for simpler local development</p>
</li>
<li><p>queued processing for heavier or slower jobs</p>
</li>
</ul>
<p>That tradeoff mattered because it let me keep the developer experience simple without blocking the app during more expensive work.</p>
<h3 id="heading-6-data-model-and-feedback-capture">6. Data Model and Feedback Capture</h3>
<p>A recommendation system only improves if it captures the right signals.</p>
<p>So I added dedicated feedback tables for:</p>
<ul>
<li><p>outfit ratings (1-5 + optional comments)</p>
</li>
<li><p>recommendation helpful/unhelpful feedback</p>
</li>
<li><p>item usage actions (worn/kept/discarded)</p>
</li>
</ul>
<p>Here is the shape of one of those models:</p>
<pre><code class="language-python">class RecommendationFeedback(Base):
    __tablename__ = "recommendation_feedback"

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
    recommendation_type = Column(String(50), nullable=False)
    recommendation_id = Column(Integer, nullable=False)
    helpful = Column(Boolean, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
</code></pre>
<p>How to read this model:</p>
<ul>
<li><p><code>user_id</code> ties feedback to the person who gave it.</p>
</li>
<li><p><code>recommendation_type</code> tells me whether the feedback belongs to outfits, shopping, or discard suggestions.</p>
</li>
<li><p><code>recommendation_id</code> identifies the exact recommendation.</p>
</li>
<li><p><code>helpful</code> stores the user’s direct response.</p>
</li>
<li><p><code>created_at</code> makes it possible to analyze feedback trends over time.</p>
</li>
</ul>
<p>This part of the system gives the app a real learning foundation, even though the feedback-to-model-update loop is still a future improvement.</p>
<h2 id="heading-challenges-i-faced">Challenges I Faced</h2>
<p>This was the section that taught me the most.</p>
<h3 id="heading-1-image-heavy-endpoints-were-slower-than-i-wanted">1. Image-heavy endpoints were slower than I wanted</h3>
<p>The analyze and wardrobe upload flows were doing a lot of work at once: image validation, classification, color extraction, storage, and database writes.</p>
<p>At first, that made the request flow feel heavier than it should have.</p>
<p>What I changed:</p>
<ul>
<li><p>I bounded concurrent image jobs so the app wouldn't try to do too much at once.</p>
</li>
<li><p>I separated slower jobs into background processing where possible.</p>
</li>
<li><p>I used load-test results to confirm which endpoints were actually expensive.</p>
</li>
</ul>
<p>The practical effect was that heavy image requests stopped competing with each other so aggressively. Instead of letting many expensive tasks pile up inside the same request cycle, I limited the active work and pushed slower operations into the queue when needed.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Bounding concurrency prevented the system from overloading CPU-bound tasks.</p>
</li>
<li><p>Moving expensive work into async jobs kept the main request/response cycle more responsive.</p>
</li>
<li><p>Load testing gave me evidence instead of guesswork, so I could tune the system based on real performance behavior.</p>
</li>
</ul>
<p>In other words, I didn't just “optimize” the endpoint in theory. I changed the execution model so expensive analysis could not block every other request behind it.</p>
<h3 id="heading-2-jwt-sessions-needed-real-server-side-control">2. JWT sessions needed real server-side control</h3>
<p>A basic JWT setup is easy to get working, but it becomes less useful if you cannot revoke sessions or manage multiple devices cleanly.</p>
<p>What I changed:</p>
<ul>
<li><p>I stored refresh tokens in the database.</p>
</li>
<li><p>I tracked token JTI values.</p>
</li>
<li><p>I rotated refresh tokens when users refreshed their session.</p>
</li>
<li><p>I added endpoints for logging out a single session or all sessions.</p>
</li>
</ul>
<p>The important shift here was moving from “token exists, therefore session is valid” to “token exists, matches the database record, and has not been revoked or replaced.” That gave the server the authority to invalidate old sessions immediately.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Server-side token tracking made revocation possible.</p>
</li>
<li><p>Rotation reduced the chance of token reuse.</p>
</li>
<li><p>Session management became visible to the user, which made the app feel more trustworthy.</p>
</li>
</ul>
<p>This is what made logout-all and multi-device management work in a real way instead of just being cosmetic UI actions.</p>
<h3 id="heading-3-user-data-isolation-had-to-be-explicit">3. User data isolation had to be explicit</h3>
<p>Because this is a multi-user app, I had to be careful that one account could never accidentally see another account’s wardrobe data.</p>
<p>What I changed:</p>
<ul>
<li><p>I added ownership checks to user-scoped routes.</p>
</li>
<li><p>I kept all wardrobe and feedback queries filtered by <code>user_id</code>.</p>
</li>
<li><p>I used encrypted image storage instead of exposing raw paths.</p>
</li>
</ul>
<p>In practice, this meant every route had to ask the same question: “Does this user own the resource they are trying to access?” If the answer was no, the request stopped immediately.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Ownership checks made data access rules explicit.</p>
</li>
<li><p>User-filtered queries prevented accidental cross-account reads.</p>
</li>
<li><p>Encrypted storage improved privacy and reduced the risk of exposing image data directly.</p>
</li>
</ul>
<p>That combination is what kept wardrobe data, feedback history, and images separated correctly across accounts.</p>
<h3 id="heading-4-docker-made-the-project-easier-to-share-but-only-after-the-stack-was-organized">4. Docker made the project easier to share, but only after the stack was organized</h3>
<p>The app includes the frontend, backend, Redis, Celery worker, and Celery Beat, so the first challenge was making the setup feel reproducible instead of fragile.</p>
<p>What I changed:</p>
<ul>
<li><p>I defined the stack in Docker Compose.</p>
</li>
<li><p>I documented the required environment variables.</p>
</li>
<li><p>I kept the dev stack aligned with how the app runs in practice.</p>
</li>
</ul>
<p>This removed a lot of setup ambiguity. Instead of asking someone to manually figure out how the frontend, backend, Redis, and workers fit together, I made the stack describe itself.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Docker let contributors start the project with fewer manual steps.</p>
</li>
<li><p>Clear environment configuration reduced setup mistakes.</p>
</li>
<li><p>Matching the stack to the architecture made the app easier to understand and test.</p>
</li>
</ul>
<p>That was important because the app depends on several moving parts, and the simplest way to make the project approachable was to make startup behavior predictable.</p>
<h2 id="heading-what-i-learned">What I Learned</h2>
<p>This project taught me a few important lessons:</p>
<ul>
<li><p>Small features become much more valuable when they work together.</p>
</li>
<li><p>Feedback data is one of the strongest signals for improving recommendations.</p>
</li>
<li><p>Clean data modeling matters a lot when multiple users are involved.</p>
</li>
<li><p>Docker and clear setup instructions make a project much easier for other people to try.</p>
</li>
</ul>
<p>I also learned that a project does not need to be huge to be useful. A focused app that solves one problem well can still feel meaningful.</p>
<h2 id="heading-what-i-want-to-improve-next">What I Want to Improve Next</h2>
<p>My roadmap from here:</p>
<ol>
<li><p>Integrate feedback directly into ranking updates</p>
</li>
<li><p>Add visual analytics for recommendation quality trends</p>
</li>
<li><p>Improve mobile UX parity</p>
</li>
<li><p>Deploy with persistent cloud storage and production database defaults</p>
</li>
<li><p>Provide a public demo mode for easier evaluation</p>
</li>
</ol>
<h2 id="heading-future-improvements">Future Improvements</h2>
<p>There are still a few things I would like to add later:</p>
<ul>
<li><p>a more advanced recommendation engine</p>
</li>
<li><p>visual analytics for user feedback</p>
</li>
<li><p>better mobile support</p>
</li>
<li><p>live deployment with persistent cloud storage</p>
</li>
<li><p>a public demo mode for easier testing</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This project began as a personal frustration and turned into a full web application with authentication, wardrobe storage, recommendation logic, and feedback infrastructure.</p>
<p>The most rewarding part was seeing how practical software decisions, not just flashy UI, can help people make everyday choices faster.</p>
<p>If you want to explore or run the project, <a href="https://github.com/Mokshitavp1/fashion_assistant">check out the repo</a>. You can try the flows and share feedback. I would especially love input on recommendation quality, UX clarity, and what features would make this genuinely useful in daily life.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Math Behind Artificial Intelligence: A Guide to AI Foundations [Full Book] ]]>
                </title>
                <description>
                    <![CDATA[ "To understand is to perceive patterns." - Isaiah Berlin This is not a math book filled with complex formulas, theorems, and concepts that are hard to grasp. Instead, it’s a detailed guide where we’l ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-math-behind-artificial-intelligence-book/</link>
                <guid isPermaLink="false">695d974f512957bf332d653a</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mathematics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ book ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tiago Capelo Monteiro ]]>
                </dc:creator>
                <pubDate>Tue, 06 Jan 2026 23:14:23 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767723634484/4748bd8a-26a1-4d9c-89c3-1a6d07bde69e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <blockquote>
<p>"To understand is to perceive patterns." - Isaiah Berlin</p>
</blockquote>
<p>This is <strong>not</strong> a math book filled with complex formulas, theorems, and concepts that are hard to grasp.</p>
<p>Instead, it’s a detailed guide where we’ll break complex ideas down into simpler terms.</p>
<p>Even if you only have a general understanding of algebra, you should be able to easily follow along.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ol>
<li><p><a href="#heading-chapter-1-background-on-this-book">Chapter 1: Background on this Book</a></p>
<ul>
<li><p><a href="#heading-the-objective-here">The Objective Here</a></p>
</li>
<li><p><a href="#heading-why-is-this-book-about-ai-different">Why is This Book About AI Different?</a></p>
</li>
<li><p><a href="#heading-let-me-introduce-myself">Let Me Introduce Myself</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-chapter-2-the-architecture-of-mathematics">Chapter 2: The Architecture of Mathematics</a></p>
<ul>
<li><p><a href="#heading-the-tree-of-mathematics-how-everything-connects">The Tree of Mathematics: How Everything Connects</a></p>
</li>
<li><p><a href="#heading-a-quick-history-of-mathematics-from-counting-to-infinity">A Quick History of Mathematics: From Counting to Infinity</a></p>
</li>
<li><p><a href="#heading-foundations-of-relativity-how-einstein-used-math-to-understand-space-and-time">Foundations of Relativity: How Einstein Used Math to Understand Space and Time</a></p>
</li>
<li><p><a href="#heading-godels-biggest-paradox-can-math-explain-itself">Gödel’s Biggest Paradox: Can Math Explain Itself?</a></p>
</li>
<li><p><a href="#heading-what-about-applied-math-and-engineering">What About Applied Math and Engineering?</a></p>
</li>
<li><p><a href="#heading-code-examples-analytical-and-numerical-approaches">Code Examples: Analytical and Numerical Approaches</a></p>
</li>
<li><p><a href="#heading-the-impact-of-a-grand-unified-theory-of-mathematics">The Impact of a Grand Unified Theory of Mathematics</a></p>
</li>
<li><p><a href="#heading-a-final-lesson-from-history">A Final Lesson From History</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-chapter-3-the-field-of-artificial-intelligence">Chapter 3: The Field of Artificial Intelligence</a></p>
<ul>
<li><p><a href="#heading-what-is-artificial-intelligence">What is Artificial Intelligence?</a></p>
</li>
<li><p><a href="#heading-symbolic-vs-non-symbolic-ai-whats-the-difference">Symbolic vs. Non-symbolic AI: What’s the Difference?</a></p>
</li>
<li><p><a href="#heading-before-ai-control-theory-as-the-first-ai">Before AI: Control Theory as the “First AI”</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-chapter-4-linear-algebra-the-geometry-of-data">Chapter 4: Linear Algebra - The Geometry of Data</a></p>
<ul>
<li><p><a href="#heading-what-are-matrices-and-why-do-they-simplify-equations">What Are Matrices and Why Do They Simplify Equations?</a></p>
</li>
<li><p><a href="#heading-vectors-and-transformations-moving-in-multiple-directions">Vectors and Transformations: Moving in Multiple Directions</a></p>
</li>
<li><p><a href="#heading-linear-independence-dependence-and-rank-why-it-matters">Linear Independence, Dependence, and Rank: Why It Matters</a></p>
</li>
<li><p><a href="#heading-determinants-measuring-space-and-scaling">Determinants: Measuring Space and Scaling</a></p>
</li>
<li><p><a href="#heading-what-are-mathematical-spaces-and-how-do-they-simplify-calculations">What Are Mathematical Spaces and How Do They Simplify Calculations?</a></p>
</li>
<li><p><a href="#heading-eigenvalues-and-eigenvectors-unlocking-hidden-patterns">Eigenvalues and Eigenvectors: Unlocking Hidden Patterns</a></p>
</li>
<li><p><a href="#heading-applications-of-linear-algebra-in-ai-and-control-theory">Applications of Linear Algebra in AI and Control Theory</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-chapter-5-multivariable-calculus-change-in-many-directions">Chapter 5: Multivariable Calculus - Change in Many Directions</a></p>
<ul>
<li><p><a href="#heading-limits-and-continuity-understanding-smooth-change">Limits and Continuity: Understanding Smooth Change</a></p>
</li>
<li><p><a href="#heading-why-are-limits-important-to-understand-derivatives-and-integrals">Why are limits important to understand derivatives and integrals?</a></p>
</li>
<li><p><a href="#heading-derivatives-how-things-change-and-how-fast">Derivatives: How Things Change and How Fast</a></p>
</li>
<li><p><a href="#heading-what-about-integral-calculus">What About Integral Calculus?</a></p>
</li>
<li><p><a href="#heading-applications-in-ai-and-control-theory-calculus-in-action">Applications in AI and Control Theory: Calculus in Action</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-chapter-6-probability-amp-statistics-learning-from-uncertainty">Chapter 6: Probability &amp; Statistics - Learning from Uncertainty</a></p>
<ul>
<li><p><a href="#heading-mean-median-mode-measuring-central-tendency">Mean, Median, Mode: Measuring Central Tendency</a></p>
</li>
<li><p><a href="#heading-variance-and-standard-deviation-measuring-spread">Variance and Standard Deviation: Measuring Spread</a></p>
</li>
<li><p><a href="#heading-what-is-the-normal-distribution-the-bell-curve-of-life">What Is the Normal Distribution? The Bell Curve of Life</a></p>
</li>
<li><p><a href="#heading-how-the-central-limit-theorem-helps-approximate-the-world">How the Central Limit Theorem Helps Approximate the World</a></p>
</li>
<li><p><a href="#heading-bayes-theorem-learning-from-evidence">Bayes Theorem: Learning from Evidence</a></p>
</li>
<li><p><a href="#heading-what-are-markov-models-predicting-the-next-step-one-step-at-a-time">What Are Markov Models? Predicting the Next Step, One Step at a Time</a></p>
</li>
<li><p><a href="#heading-applications-in-ai-and-control-theory-making-decisions-under-uncertainty">Applications in AI and Control Theory: Making Decisions Under Uncertainty</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-chapter-7-optimization-theory-teaching-machines-to-improve">Chapter 7: Optimization Theory - Teaching Machines to Improve</a></p>
<ul>
<li><p><a href="#heading-what-is-optimization-theory">What is Optimization Theory?</a></p>
</li>
<li><p><a href="#heading-why-optimization-drives-learning-in-ai">Why Optimization Drives Learning in AI</a></p>
</li>
<li><p><a href="#heading-simple-optimization-techniques-how-machines-learn-step-by-step">Simple Optimization Techniques: How Machines Learn Step by Step</a></p>
</li>
<li><p><a href="#heading-what-is-adam-the-most-popular-way-ai-models-finds-the-best-learning-path">What is Adam? The Most Popular Way AI Models Finds the Best Learning Path</a></p>
</li>
<li><p><a href="#heading-applications-in-ai-and-control-theory-of-optimization-theory">Applications in AI and Control Theory of Optimization Theory</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-conclusion-where-mathematics-and-ai-meet">Conclusion: Where Mathematics and AI Meet</a></p>
<ul>
<li><p><a href="#heading-mathematics-is-the-foundation-of-ai">Mathematics is the Foundation of AI</a></p>
</li>
<li><p><a href="#heading-the-future-on-device-ai-and-the-democratization-of-ai">The Future: On Device AI and the Democratization of AI</a></p>
</li>
<li><p><a href="#heading-final-reflections">Final Reflections</a></p>
</li>
<li><p><a href="#heading-acknowledgements">Acknowledgements</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-about-the-author">About the Author</a></p>
</li>
</ol>
<h2 id="heading-chapter-1-background-on-this-book">Chapter 1: Background on this Book</h2>
<h3 id="heading-the-objective-here">The Objective Here</h3>
<p>My objective in this book is simple: Explain the key mathematical ideas you need to grasp in order to deeply understand AI and train machine learning models.</p>
<p>So you might be wondering: Why is it important to have a good math foundation before creating these models?</p>
<p>Well, there are many reasons, but some are:</p>
<ul>
<li><p>It gives you the capacity to understand new AI research on your own.</p>
</li>
<li><p>You can use this same foundation to study other STEM concepts like signal theory and advanced statistical methods.</p>
</li>
<li><p>It helps you understand that AI models are just a mixture of different math ideas working together and gives you insight into how new innovations make LLMs more efficient.</p>
</li>
<li><p>It gives you a foundation so you know how to calibrate AI models and even create derivative models.</p>
</li>
</ul>
<p>These skills are also important for startup founders, especially in Silicon Valley. Many startups begin with APIs or API wrappers but eventually need their own AI solutions.</p>
<p>Outsourcing all AI isn't ideal. This book will help you understand AI foundations so you can design better growth strategies and communicate effectively with investors – especially those who were successful technical co-founders.</p>
<h3 id="heading-why-is-this-book-about-ai-different">Why is This Book About AI Different?</h3>
<p>In this book, we’ll look at AI from an engineering perspective. This differs from the typical computer science approach to AI that most introductory courses take.</p>
<p>In doing so, I won’t spend a lot of time explaining formulas and theorems. Instead, I’ll explain their importance, how and why they are applied the way they are.</p>
<p>In this way, I hope to offer a unique viewpoint that emphasizes the engineering principles and good practices that underlie all modern AI technologies.</p>
<p>I will also explain how many of these strange math ideas make billion dollar industries possible.</p>
<p>We’ll start with the fundamentals: the structure of the areas of mathematics and AI. After that, we’ll look at the four subareas of math that make AI possible:</p>
<ul>
<li><p>Linear Algebra</p>
</li>
<li><p>Calculus</p>
</li>
<li><p>Probability Theory and Statistics</p>
</li>
<li><p>Optimization Theory</p>
</li>
</ul>
<p>After going through all the math, we’ll connect it with the foundation of ChatGPT and all of these large language models.</p>
<p>This way, you’ll get a basic foundation in key math concepts that, when mixed together like the ingredients of a cake, make all AI models possible.</p>
<p>By knowing where the ideas come from, you’ll develop a system-level understanding of AI and a first-principles approach.</p>
<p>So just keep in mind that, even though concepts like integral calculus and eigenvalues/eigenvectors might not be widely used in AI, they’ll help you develop these system-level and first-principle approaches.</p>
<p>Also, this book will be a work in progress. After its first release, I’ll seek feedback on things I need to perfect, chapters to add, and so on.</p>
<p>Here is my email for any feedback you might have: <a href="mailto:monteiro.t@northeastern.edu">monteiro.t@northeastern.edu</a></p>
<p>And here is the book’s GitHub repository with all code: <a href="https://github.com/tiagomonteiro0715/The-Math-Behind-Artificial-Intelligence-A-Guide-to-AI-Foundations">https://github.com/tiagomonteiro0715/The-Math-Behind-Artificial-Intelligence-A-Guide-to-AI-Foundations</a></p>
<h3 id="heading-let-me-introduce-myself">Let Me Introduce Myself</h3>
<p>My name is Tiago Monteiro, an electrical and computer engineer and AI master's degree student at Northeastern University's Silicon Valley campus. I have authored 20+ articles with 240K+ views here on freeCodeCamp on math, AI, and tech.</p>
<p>If you’d like to know more about my background, I’ll share that at the end of the book.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>In terms of minimum requirements, you only need to know the basics of mathematics and programming:</p>
<ul>
<li><p>Basic algebra and what functions and the coordinate system are.</p>
</li>
<li><p>You should be able to read Python code and understand things like variables, functions, and loops.</p>
</li>
</ul>
<h2 id="heading-chapter-2-the-architecture-of-mathematics">Chapter 2: The Architecture of Mathematics</h2>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766099739986/049ff3c0-0150-495e-97e9-4f16f3861058.png" alt="Cover of the chapter the architecture of mathematics" style="display:block;margin:0 auto" width="1920" height="1080" loading="lazy">

<p>Math is more than numbers. It’s the science of locating complex patterns that shape our world. To truly understand math, we must look beyond numbers and formulas to grasp its structures.</p>
<p>This chapter aims to show math as a growing tree of ideas, a living system of logic, not just formulas to memorize. With analogies, history, and code examples, I want to help you understand math deeply and how to apply it to programming.</p>
<p>I’ve included code examples to connect theory and practice, showing how math ideas apply to real problems. Whether you're new to advanced math or are more experienced, these examples will help you apply math in programming.</p>
<p>This way, before we start going over the different math pillars that sustain AI, you will understand the structure of the field.</p>
<h3 id="heading-the-tree-of-mathematics-how-everything-connects">The Tree of Mathematics: How Everything Connects</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765001557970/7ac6c8c8-d0fd-4a67-be6a-6d8b9a1a6615.jpeg" alt="Seeing a tree from its root to a tree" style="display:block;margin:0 auto" width="2000" height="1299" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/bottom-view-of-green-leaved-tree-during-daytime-91153/">Lerkrat Tangsri</a></p>
<p>Imagine math as a vast, ever-growing tree.</p>
<p>The roots are the foundations: logic and set theory. From these roots, the main fields emerge: arithmetic, algebra, geometry, and analysis.</p>
<p>As the tree branches out, new subfields like topology and abstract algebra appear. Sometimes branches connect with each other.</p>
<p>This tree keeps growing in many directions. History shows that sometimes it grows rapidly due to scientific discoveries, while at other times, growth is slow.</p>
<p>And you might wonder: How many more branches and connections between them will keep appearing?</p>
<h3 id="heading-a-quick-history-of-mathematics-from-counting-to-infinity">A Quick History of Mathematics: From Counting to Infinity</h3>
<p>The first mathematical ideas emerged independently in ancient civilizations, such as:</p>
<ul>
<li><p>India's invention of zero</p>
</li>
<li><p>Islamic algebraic advances</p>
</li>
<li><p>Greek geometric rigor</p>
</li>
</ul>
<p>Great mathematicians developed and shared these ideas through writing and lectures. Over time, new generations built on these ideas, creating new branches of mathematics. This endless growth is why Isaac Newton wrote to Robert Hooke in 1675:</p>
<blockquote>
<p>“If I have seen further, it is by standing on the shoulders of giants.”</p>
</blockquote>
<p>He meant that by working from previous knowledge, he was able to create and (re)discover new ideas.</p>
<p>Yet, the real power of math lies in practicing it over and over and studying it more and more deeply.</p>
<p>As one of my professors once pointed out:</p>
<blockquote>
<p><em>“More important than knowing the theorems is knowing the ideas behind them and the history of how they were created.”</em></p>
</blockquote>
<p>To solve problems, it's often necessary to think from first principles, and math teaches this. Math is not just an academic topic. It’s a global language for scientists and engineers.</p>
<p>By preserving and sharing it, new math can grow from old ideas, allowing the tree to keep expanding.</p>
<h3 id="heading-foundations-of-relativity-how-einstein-used-math-to-understand-space-and-time">Foundations of Relativity: How Einstein Used Math to Understand Space and Time</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766903578928/a4102586-cb63-4410-8793-72950145726d.jpeg" alt="A satellite in space" style="display:block;margin:0 auto" width="2274" height="1506" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/gray-and-white-satellite-41006/">Pixabay</a></p>
<p>Albert Einstein developed the general and special theories of relativity, which impact:</p>
<ul>
<li><p>GPS and global communication</p>
</li>
<li><p>Satellite telecommunications</p>
</li>
<li><p>Space exploration and satellite launches</p>
</li>
</ul>
<p>And more.</p>
<p>But this was only possible by combining geometry with calculus, known as <strong>differential geometry.</strong> This field evolved over centuries, thanks to many great mathematicians. Here are a few of them, though the list is not exhaustive:</p>
<ul>
<li><p><strong>Euclid (circa 300 BCE):</strong> Contributed to geometry, laying the groundwork for later mathematical systems</p>
</li>
<li><p><strong>Archimedes (circa 287–212 BCE):</strong> Pioneered the understanding of volume, surface area, and the principles of mechanics</p>
</li>
<li><p><strong>René Descartes (1596–1650):</strong> Developed Cartesian coordinates and analytical geometry</p>
</li>
<li><p><strong>Isaac Newton (1642–1727) &amp; Gottfried Wilhelm Leibniz (1646–1716):</strong> Newton’s laws of motion and gravitation, alongside Leibniz’s development of calculus, formed the basis of classical mechanics that Einstein sought to extend and modify in his theory of relativity.</p>
</li>
<li><p><strong>Leonhard Euler (1707–1783):</strong> Contributed to the development of differential equations, which are essential in the mathematical foundations of physics.</p>
</li>
<li><p><strong>Gaspard Monge (1746–1818):</strong> The father of differential geometry and pioneer in descriptive geometry</p>
</li>
<li><p><strong>Carl Friedrich Gauss (1777–1855):</strong> Made groundbreaking advances in geometry, including the concept of curved surfaces.</p>
</li>
<li><p><strong>Bernhard Riemann (1826–1866):</strong> Introduced Riemannian geometry, a branch of differential geometry.</p>
</li>
</ul>
<p>Going back to Albert Einstein, he saw what no one else in his time saw, thanks to these great math giants and countless others.</p>
<h3 id="heading-godels-biggest-paradox-can-math-explain-itself">Gödel’s Biggest Paradox: Can Math Explain Itself?</h3>
<p>The biggest paradox in math, discovered by Kurt Gödel, is his incompleteness theorems. They show that in any consistent formal system capable of simple arithmetic, there are true statements that cannot be proven within the system.</p>
<p>This means there are limits to what can be proven as true or false. For mathematicians, this implies that some truths are beyond formal proofs, yet we assume they are true. It demonstrates that no matter how much effort or AI is used, some things remain unprovable, known only through approximations and non-exact methods.</p>
<h3 id="heading-what-about-applied-math-and-engineering">What About Applied Math and Engineering?</h3>
<p>Applied math and engineering involve adapting the pure math ideas in real-world scenarios.</p>
<p>Actually, in many cases, it’s the combination of many math ideas.</p>
<p>Let’s consider some examples:</p>
<ul>
<li><p>In <strong>harmonic analysis</strong>, Laplace, Fourier, and Z-transforms are a way to see the same thing in a new domain to get new insights. In this case, integrals are used to make this mapping possible.</p>
</li>
<li><p><strong>Principal component analysis (PCA)</strong> is a widely used tool in data science. Yet, it is a mixture of linear algebra (in PCA, eigenvalues) with optimization (order eigenvalues that represent more data with less data) in order to make datasets shorter.</p>
</li>
<li><p>In <strong>machine learning</strong>, logistic regression is a mixture of calculus with statistics and probability.</p>
</li>
<li><p>In <strong>deep learning</strong>, neural networks are just many matrices multiplying and updating themselves that adapt to model a dataset representing a system. This optimization of matrix values happens with activation functions, a gradient descent-based optimization method (tells how much values need to change), and backpropagation (applies those alterations to all matrix values).</p>
</li>
</ul>
<p>But the best example of this fusion of math in engineering is in <a href="https://www.freecodecamp.org/news/basic-control-theory-with-python/">control theory</a>. Control theory is the study of the architecture of systems. From trains to cars to airplanes, everything is based on control theory. It’s everywhere, in nearly all modern electronic devices. In electric circuits, control theory is also used heavily to guarantee circuit stability in the face of electric disturbances.</p>
<p>So as you can probably start to see, many of the tools we now have are just a mixture of many pure math ideas – like different recipes. In essence, applied math is the application of pure math as “ingredients“ in "recipes" to solve problems.</p>
<p>So, we’ve explored the structure and evolution of mathematics. But it’s important to see how we can apply these ideas in real life. Pure math makes the framework, and applied math applies that framework to solve problems. To understand this, we’ll examine two code examples that show how you can use math ideas as programming tools.</p>
<h3 id="heading-code-examples-analytical-and-numerical-approaches">Code Examples: Analytical and Numerical Approaches</h3>
<p>These code examples demonstrate a couple ways you can use Python to solve math equations.</p>
<p>In the first code example, we’ll solve the problem in the same way that kids in school solve math exercises: essentially, by hand with a pencil. In the second example, we’ll solve the problem using numerical analysis.</p>
<h4 id="heading-example-1-solve-a-problem-analytically">Example 1: Solve a Problem Analytically</h4>
<p>In this problem, we need to find the values of the variables x and y. So we’ll be moving variables from left to right to find their values.</p>
<p>When we solve math problems analytically, like we did in school, we are manipulating symbols to get exact values. Often these symbols are x, y, and z.</p>
<p>The code below solves a system of two equations with two unknowns variables, x and y.</p>
<p>We will use the <a href="https://www.sympy.org">SymPy</a> Python library to do this. It’s mainly used for symbolic mathematics.</p>
<pre><code class="language-python">from sympy import symbols, Eq, solve

x, y = symbols('x y')
eq1 = Eq(2*x + 3*y, 6)
eq2 = Eq(-x + y, 1)

solution = solve((eq1, eq2), (x, y))
print(solution)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747160359386/7a21cddc-f4ba-4f9f-afa0-d1cc11fb27d6.png" alt="Image of the equations and analytical method in Python" style="display:block;margin:0 auto" width="2080" height="1224" loading="lazy">

<p>Once again with this code we are finding the values of the variables x and y.</p>
<p>Essentially, we’re finding x and y based on this equation:</p>
<p>$$\begin{align} 2x + 3y &amp;= 6 \ -x + y &amp;= 1 \end{align}$$</p>
<p>Which gives us the following result:</p>
<pre><code class="language-python">{x: 3/5, y: 8/5}
</code></pre>
<p>Or:</p>
<ul>
<li><p>x= 0.6</p>
</li>
<li><p>y = 1.6</p>
</li>
</ul>
<p>When we say that we’re solving this analytically, it means that we’re finding an exact mathematical solution using formulas or equations.</p>
<p>But many times, problems are harder and can be solved by adding symbols to the right or left of the equation. Sometimes, there can be so many symbols and transformed versions of them, with things like derivatives and integrals, that it can become very hard to manage and takes a lot of time.</p>
<p>For example, let’s look at this partial differential equation:</p>
<p>$$\begin{cases} \frac{\partial u}{\partial t} = \alpha \frac{\partial^2 u}{\partial x^2}, &amp; 0 &lt; x &lt; L, , t &gt; 0 \ u(0,t) = 0, &amp; t &gt; 0 \ u(L,t) = 0, &amp; t &gt; 0 \ u(x,0) = f(x), &amp; 0 &lt; x &lt; L \end{cases}$$</p>
<p>It can be solved with an analytical method call separation of variables.</p>
<p>But it requires many steps, and it’s easy to make mistakes. Even engineers who learned this often struggle to remember the process later.</p>
<p>When I first encountered this type of math exercise in my electrical and computer engineering degree back in Portugal, it took me 20 to 30 minutes to solve it.</p>
<p>For this reason, there's a branch of mathematics called numerical analysis that focuses on finding approximations of existing formulas. It helps solve problems faster. This is the method we'll explore next.</p>
<h4 id="heading-example-2-solve-numerically-approximation">Example 2: Solve Numerically (Approximation)</h4>
<p>Now let’s solve a different problem: we’re going to find the values of each of the 5 variables:</p>
<p>$$\begin{bmatrix} 3 &amp; 2 &amp; -1 &amp; 4 &amp; 5 \ 1 &amp; 1 &amp; 3 &amp; 2 &amp; -2 \ 4 &amp; -1 &amp; 2 &amp; 1 &amp; 0 \ 5 &amp; 3 &amp; -2 &amp; 1 &amp; 1 \ 2 &amp; -3 &amp; 1 &amp; 3 &amp; 4 \end{bmatrix} \times \begin{bmatrix} x_1 \ x_2 \ x_3 \ x_4 \ x_5 \end{bmatrix} = \begin{bmatrix} 12 \ 5 \ 7 \ 9 \ 10 \end{bmatrix}$$</p>
<p>Solving this by hand will take some time…but with Python code, it’s very fast.</p>
<p>We’ll also use the <a href="https://scipy.org">SciPy</a> Python library for this example.</p>
<p>Let’s solve the system numerically:</p>
<pre><code class="language-python">import numpy as np
from scipy.linalg import solve

A = np.array([[3, 2, -1, 4, 5],
              [1, 1, 3, 2, -2],
              [4, -1, 2, 1, 0],
              [5, 3, -2, 1, 1],
              [2, -3, 1, 3, 4]])

b = np.array([12, 5, 7, 9, 10])

solution = solve(A, b)

print(solution)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747160347486/d1f17aa6-b288-4e41-9be7-0810c45e778c.png" alt="Image of equations and numerical method" style="display:block;margin:0 auto" width="2080" height="1764" loading="lazy">

<p>Which corresponds to this operation:</p>
<p>$$\begin{bmatrix} 3 &amp; 2 &amp; -1 &amp; 4 &amp; 5 \ 1 &amp; 1 &amp; 3 &amp; 2 &amp; -2 \ 4 &amp; -1 &amp; 2 &amp; 1 &amp; 0 \ 5 &amp; 3 &amp; -2 &amp; 1 &amp; 1 \ 2 &amp; -3 &amp; 1 &amp; 3 &amp; 4 \end{bmatrix} \times \begin{bmatrix} x_1 \ x_2 \ x_3 \ x_4 \ x_5 \end{bmatrix} = \begin{bmatrix} 12 \ 5 \ 7 \ 9 \ 10 \end{bmatrix}$$</p>
<p>Again, it takes time to solve this and it’s very easy to make a simple mistake.</p>
<p>But in this code example, this line of code:</p>
<pre><code class="language-python">solution = solve(A, b)
</code></pre>
<p>Uses the <code>solve</code> method from SciPy:</p>
<pre><code class="language-python">from scipy.linalg import solve
</code></pre>
<p>It’s a method that helps you find the values of x in an equation A⋅x=b, where A is a square grid of numbers and b is a list of numbers. That gives us the following:</p>
<pre><code class="language-python">[ 1.35022026 -0.79955947 -1.17180617  3.14317181 -0.83920705]
</code></pre>
<p>Which corresponds to:</p>
<p>$$\begin{bmatrix} x_1 \ x_2 \ x_3 \ x_4 \ x_5 \end{bmatrix} = \begin{bmatrix} 1.35022026 \ -0.79955947 \ -1.17180617 \ 3.14317181 \ -0.83920705 \end{bmatrix}$$</p>
<p>And is the same thing as:</p>
<p>$$\begin{align} x_1 &amp;= 1.35022026 \ x_2 &amp;= -0.79955947 \ x_3 &amp;= -1.17180617 \ x_4 &amp;= 3.14317181 \ x_5 &amp;= -0.83920705 \end{align}$$</p>
<h4 id="heading-why-these-two-approaches-matter">Why These Two Approaches Matter</h4>
<p>We have solved two mathematical problems in two different ways:</p>
<ul>
<li><p>Analytical: Exact solutions through algebraic manipulation</p>
</li>
<li><p>Numerical: Approximate solutions using algorithms</p>
</li>
</ul>
<p>In engineering and in AI, we are constantly choosing between these approaches.</p>
<p>When training AI models with millions of parameters, analytical solutions are impossible. This is why, in these cases, we need numerical approaches.</p>
<p>When creating math theorems, we need analytical precision to make sure it is the best possible solution.</p>
<p>This is one of the many things an engineering degree teaches you: often, in the real world, it’s better to just write some code to solve a problem than to actually solve it by hand with math. Other times, the best solution is to just think in first principles and from there create new theorems to solve a problem.</p>
<p>Now let's step out of the code examples and see how different branches of mathematics connect.</p>
<h3 id="heading-the-impact-of-a-grand-unified-theory-of-mathematics">The Impact of a Grand Unified Theory of Mathematics</h3>
<p>Is it possible to unify all math?</p>
<p>In theory, yes. This is known as the Grand Unified Theory of Mathematics. It's the idea that all different areas of math can be linked together to discover deeper patterns in mathematics.</p>
<p>The <a href="https://en.wikipedia.org/wiki/Langlands_program">Langlands program</a> is trying to make this unification possible. It’s an attempt to interconnect the largest parts of the big tree of math to uncover new patterns in math.</p>
<p>With a Grand Unified Theory of Mathematics, we would be able to understand how every branch of the tree connects with the others and all the relationships between them.</p>
<h4 id="heading-whats-the-value-of-this-big-unification-for-society">What’s the Value of this Big Unification for Society?</h4>
<p>By studying history, we can find patterns. The unification of various fields has created many massive impacts on society, such as:</p>
<ul>
<li><p>In the 19th century, James Clerk Maxwell united the fields of electricity and magnetism with his famous Maxwell equations. This allowed the creation of radios and electric grids around the globe. In turn, it served as a foundation for all technological progress in the 20th and 21st century.</p>
</li>
<li><p>In the 20th century, the unification of algebra with logic led to the rise of digital systems. In turn, digital systems gave rise to processors and the evolution of computers and the modern laptop.</p>
</li>
<li><p>Also in the 20th century, the unification of probability and communication led to information theory. This became the foundation for the internet. This unification was carried out by a great mathematician named Claude Shannon.</p>
</li>
</ul>
<p>In the end, a grand unified theory of mathematics could be one of the biggest achievements in modern society.</p>
<p>In AI, it could help unify all machine learning models in a common architecture. This would help accelerate the development of new AI models and could also open the door to new material science advances.</p>
<p>It could help reveal – with math – the deep patterns we still haven’t found in these fields. Just as uniting electricity and magnetism led to modern technology, a unified math framework would lead to a wave of innovation.</p>
<h3 id="heading-a-final-lesson-from-history">A Final Lesson From History</h3>
<p>From Greek geometry to AI, math has grown like a tree over centuries. By understanding its structure, it’s possible to see its role in finding the patterns of our universe.</p>
<p>I hope I was able to make you see math in this way. I hope you can also see that the unification of scientific fields helps lay the foundations for the creation of new innovations to help society go forward.</p>
<p>Many major societal transformations only came to be thanks to abstract math ideas. When these are shared and refined, they become the hidden architecture of progress in society. Innovation begins when disconnected ideas are united, well-linked, and widely shared.</p>
<h2 id="heading-chapter-3-the-field-of-artificial-intelligence">Chapter 3: The Field of Artificial Intelligence</h2>
<h3 id="heading-what-is-artificial-intelligence">What is Artificial Intelligence?</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765001693682/bbec3565-643f-421f-b32e-3de62285a2c0.jpeg" alt="A man playing chess against a robot" style="display:block;margin:0 auto" width="5192" height="3466" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/elderly-man-thinking-while-looking-at-a-chessboard-8438918/">Pavel Danilyuk</a></p>
<p>The term Artificial Intelligence was born from the work of John McCarthy, who is often called the "father of AI."</p>
<p>He used it when he, along with Marvin Minsky, Nathaniel Rochester, and Claude Shannon, proposed the famous Dartmouth Summer Research Project on Artificial Intelligence in 1956.</p>
<p>Artificial intelligence was defined, in the Dartmouth Conference, as:</p>
<blockquote>
<p><em>“Every aspect of learning or any other feature of intelligence can in principle be so precisely described that a machine can be made to simulate it.”</em></p>
</blockquote>
<p>Since then, the field has evolved in waves of innovation, from early rules-based systems to modern neural networks.</p>
<p>But over time, rather than creating <a href="https://en.wikipedia.org/wiki/Artificial_general_intelligence">general intelligence</a>, most AI systems have been designed to excel at narrow tasks.</p>
<p>For example:</p>
<ul>
<li><p>Chess-playing programs like Deep Blue that defeated world champion Garry Kasparov</p>
</li>
<li><p>Image recognition systems that can identify objects in photographs with impressive accuracy</p>
</li>
<li><p>Natural language processing models that can translate between languages</p>
</li>
<li><p>Game-playing AI like AlphaGo that mastered the ancient game of Go</p>
</li>
</ul>
<h4 id="heading-artificial-general-intelligence-isnt-yet-here">Artificial General Intelligence isn’t yet here</h4>
<p>Only very narrow AI models have demonstrated human-level or superhuman performance in their narrow domains.</p>
<p>In my view, and as we will see in this book, AGI will be the combination and interaction of different large language models interacting with each other and with the tools available to them.</p>
<h3 id="heading-symbolic-vs-non-symbolic-ai-whats-the-difference">Symbolic vs. Non-symbolic AI: What’s the Difference?</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755906822438/f639efd3-3f8b-45a7-ad2d-d1795d772947.png" alt="Image comparing artificial general intelligence with narrow AI and, inside narrow AI, non-symbolic AI and symbolic AI circles" style="display:block;margin:0 auto" width="1858" height="1041" loading="lazy">

<h4 id="heading-what-is-symbolic-ai">What is Symbolic AI?</h4>
<p>Symbolic AI refers to the creation of a program based on many rules and symbols to simulate how humans think.</p>
<p>It uses symbols to represent concepts (like farms and distributors) and logical rules to reason about them.</p>
<p>The specific data about your domain is called facts. Facts are the pieces of information the rules operate on. For example, a fact might be "green_acres has high water usage and good pH levels."</p>
<p>Also, imagine someone wants to optimize farm distribution logistics. The symbols would represent farms, distributors, and transport methods. Then the rules would be:</p>
<ul>
<li><p>If the farm has high water usage and good pH levels, then classify it as high-yield producer</p>
</li>
<li><p>If a high-yield producer and distributor has low demand, then prioritize direct connection</p>
</li>
<li><p>If a direct connection is needed, then select transport with lowest environmental impact</p>
</li>
</ul>
<p>The facts would be the actual data like "farm X has high water usage" or "distributor Y has low demand."</p>
<p>This way, the system combines these rules and facts through logical reasoning to make decisions. A very popular programming language we use in this field is called Prolog that was designed to create rule-based systems.</p>
<p><strong>Symbolic AI program: Manage agricultural networks with a Prolog program.</strong></p>
<p>Let’s look at an example project to understand this more clearly. The project we’ll examine is called SymbolicAIHarvest. It was part of a course at NOVA University during my undergraduate studies in Electrical and Computer Engineering. The course was titled "Modelation of Data in Engineering."</p>
<p>SymbolicAIHarvest is an AI system developed with Prolog to manage agricultural networks. <a href="https://github.com/tiagomonteiro0715/SymbolicAIHarvest">Here’s the project</a> on GitHub so you can check it out.</p>
<p>The project optimizes farm operations using rule-based reasoning. It monitors sensors for real-time data and improves route planning for machinery. It also coordinates produce movement to reduce delays and waste, enhancing productivity and sustainability.</p>
<p>Understanding the code below is not a priority for this book. I just want to show you an example of all the facts of the project:</p>
<pre><code class="language-plaintext">% FARMERS(owner)
farmer(ana).
farmer(asdrubal).
farmer(miguel).
farmer(joao).
farmer(teresinha).
farmer(victor).
farmer(carlos).
farmer(anabela).

% FARMS(name, owner, region, type)
farm(q1, ana, alentejo, vinha).
farm(q2, ana, alentejo, olival).
farm(q3, asdrubal, lisboa, cenoureira).
farm(q4, asdrubal, lisboa, milharal).
farm(q5, asdrubal, lisboa, vinha).
farm(q6, miguel, evora, trigal).
farm(q7, miguel, evora, cenoureia).
farm(q8, miguel, evora, vinha).
farm(q9, miguel, evora, morangueira).
farm(q10, joao, porto, vinha).
farm(q11, joao, porto, trigal).
farm(q12, joao, porto, cenoureira).
farm(q13, teresinha, algarve, olival).
farm(q14, teresinha, algarve, vinha).
farm(q15, victor, setubal, olival).
farm(q16, victor, setubal, vinha).
farm(q17, victor, setubal, trigal).
farm(q18, carlos, sintra, milharal).
farm(q19, carlos, sintra, vinha).
farm(q20, anabela, coina, milharal).
farm(q21, anabela, coina, olival).
farm(q22, anabela, coina, trigal).

% SENSOR READINGS(name, type, value)
sensor_reading(q1,humidity,28).
sensor_reading(q2,humidity,35).
sensor_reading(q3,humidity,42).
sensor_reading(q4,humidity,38).
sensor_reading(q5,humidity,33).
sensor_reading(q6,humidity,45).
sensor_reading(q7,humidity,30).
sensor_reading(q8,humidity,36).
sensor_reading(q9,humidity,50).
sensor_reading(q10,humidity,41).
sensor_reading(q11,humidity,40).
sensor_reading(q12,humidity,44).
sensor_reading(q13,humidity,32).
sensor_reading(q14,humidity,29).
sensor_reading(q15,humidity,47).
sensor_reading(q16,humidity,39).
sensor_reading(q17,humidity,53).
sensor_reading(q18,humidity,27).
sensor_reading(q19,humidity,24).
sensor_reading(q20,humidity,31).
sensor_reading(q21,humidity,37).
sensor_reading(q22,humidity,46).
sensor_reading(q1, temperature, 25).
sensor_reading(q2, temperature, 25).
sensor_reading(q3, temperature, 25).
sensor_reading(q4, temperature, 25).
sensor_reading(q5, temperature, 25).
sensor_reading(q6, temperature, 25).
sensor_reading(q7, temperature, 25).
sensor_reading(q8, temperature, 25).
sensor_reading(q9, temperature, 25).
sensor_reading(q10, temperature, 25).
sensor_reading(q11, temperature, 25).
sensor_reading(q12, temperature, 25).
sensor_reading(q13, temperature, 25).
sensor_reading(q14, temperature, 25).
sensor_reading(q15, temperature, 25).
sensor_reading(q16, temperature, 25).
sensor_reading(q17, temperature, 25).
sensor_reading(q18, temperature, 25).
sensor_reading(q19, temperature, 25).
sensor_reading(q20, temperature, 25).
sensor_reading(q21, temperature, 25).
sensor_reading(q22, temperature, 25).
sensor_reading(q1, water, 47000).
sensor_reading(q2, water, 52500).
sensor_reading(q3, water, 39000).
sensor_reading(q5, water, 61000).
sensor_reading(q8, water, 58000).
sensor_reading(q10, water, 43000).
sensor_reading(q13, water, 72000).
sensor_reading(q16, water, 49000).
sensor_reading(q18, water, 35000).
sensor_reading(q21, water, 66500).
sensor_reading(q1, ph, 6.5).
sensor_reading(q2, ph, 4.7).
sensor_reading(q3, ph, 8.2).
sensor_reading(q4, ph, 7.0).
sensor_reading(q5, ph, 5.1).
sensor_reading(q6, ph, 8.0).
sensor_reading(q7, ph, 4.5).

% DISTRIBUTORS (name, region, capacity, demand level)
distributor(d1, alentejo, 1000, 2).
distributor(d2, lisboa, 800, 1).
distributor(d3, evora, 1200, 3).
distributor(d4, porto, 900, 2).
distributor(d5, algarve, 700, 2).
distributor(d6, setubal, 1100, 1).
distributor(d7, sintra, 950, 2).
distributor(d8, coina, 1000, 1).

% TRANSPORTS (name, capacity, type, autonomy, region, impact)
transport(t1, 1000, fossil, 100, alentejo, 3).
transport(t2, 500, electric, 10, alentejo, 1).
transport(t3, 800, fossil, 400, algarve, 5).
transport(t4, 700, hybrid, 300, setubal, 2).
transport(t5, 150, electric, 340, coina, 1).
transport(t6, 700, fossil, 220, porto, 3).
transport(t7, 900, hybrid, 350, evora, 2).
transport(t8, 1000, electric, 170, sintra, 1).

% Connections based on graph image

% Top of the network
link(q2, d1, 5).
link(q1, d1, 7).
link(q3, d1, 6).

% Network center
link(q3, q4, 8).
link(q4, d2, 6).
link(q4, d3, 7).
link(q4, q5, 5).
link(q4, d4, 6).

% Additional connections
link(q2, d2, 8).
link(q3, d3, 7).
</code></pre>
<p>This Prolog code models an agricultural supply chain system that has:</p>
<ul>
<li><p>Farmers</p>
</li>
<li><p>Farms</p>
</li>
<li><p>Sensors Readings</p>
</li>
<li><p>Distributors</p>
</li>
<li><p>Transports</p>
</li>
</ul>
<p>In addition, in this part of the code on the facts of the system:</p>
<pre><code class="language-plaintext">% Top of the network
link(q2, d1, 5).
link(q1, d1, 7).
link(q3, d1, 6).

% Network center
link(q3, q4, 8).
link(q4, d2, 6).
link(q4, d3, 7).
link(q4, q5, 5).
link(q4, d4, 6).

% Additional connections
link(q2, d2, 8).
link(q3, d3, 7).
</code></pre>
<p>We connect farms with distributors. This way, we can see that between the farm <code>q1</code> and distributor <code>d1</code> is a distance of 7k. This makes it possible to find/create algorithms to find the shortest path between them.</p>
<p>In the end, symbolic AI just creates programs based on a context and rules applied to that context.</p>
<h4 id="heading-what-is-non-symbolic-ai">What is Non-Symbolic AI?</h4>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755906892854/197f7bc3-8c05-46f2-aa2a-99dbaa733a9a.png" alt="Non-symbolic AI with a circle titled machine learning inside. Inside the machine learning circle is another circle with the text deep learning." style="display:block;margin:0 auto" width="1711" height="951" loading="lazy">

<p>Non symbolic AI doesn’t use symbols or rules to think. Instead, it’s data driven. In other words, it learns patterns from large datasets. This is the approach used in machine learning and deep learning.</p>
<p>When we create an AI model, we can associate it with an API (Application Programming Interface) so that we can use the AI model in websites, applications, and other systems. Basically, the trained AI model is set up behind an API endpoint. An API endpoint is like a web service that lets other applications send requests to the model and get responses back.</p>
<p>For example, when you use ChatGPT in a web browser, your messages are sent through OpenAI's API to their language model, which processes your input and sends back a response.</p>
<p>An AI agent is a software program that can autonomously perform tasks by making decisions and taking actions to achieve specific goals.</p>
<p>Unlike basic chatbots that only reply to questions, AI agents can plan steps, use tools, and work towards achieving complex goals. They do this by combining language models with extra features like accessing outside data or working with other AI agents.</p>
<p><a href="https://github.com/tiagomonteiro0715/ai-content-lab">Here’s an example</a> of a non-symbolic AI agent project I worked on. I developed it using the <a href="https://www.crewai.com/">crewAI</a> Python library and the OpenAI API, one of the most popular libraries for creating AI agents.</p>
<p>In this system, five AI agents collaborate to create optimized content:</p>
<ul>
<li><p><strong>Research and Fact Checker:</strong> Conducts research to find trends and data.</p>
</li>
<li><p><strong>Audience Specialist:</strong> Analyzes audience needs for better engagement.</p>
</li>
<li><p><strong>Lead Content Writer:</strong> Writes engaging content based on research.</p>
</li>
<li><p><strong>Senior Editorial Director:</strong> Ensures content quality and consistency.</p>
</li>
<li><p><strong>SEO Specialist:</strong> Optimizes content for search engines.</p>
</li>
</ul>
<p>Using the OpenAI API, it employs chatGPT with crewAI to have these agents work for me.</p>
<h3 id="heading-before-ai-control-theory-as-the-first-ai">Before AI: Control Theory as the “First AI”</h3>
<p>Before symbolic and non symbolic AI, electrical engineering had data-driven methods. One key area that I’ve already mentioned above was control theory (which studies control systems for machines like cars and rockets). This field allows us to design systems that ensure stability despite disturbances and achieve goals beyond human capabilities.</p>
<p>Nowadays, after creating a control theory algorithm, we check if AI can improve the control system. In my experience, only some advanced deep learning methods are effective. Most machine learning methods don't outperform control theory in efficiency and security.</p>
<p>Control theory also offers better interpretability, allowing us to understand decisions, unlike advanced machine learning and deep learning.</p>
<p>Due to the historical importance of control theory, I will continue to mention its role and mathematical applications. This will help you learn AI's math foundations and understand its significance in electronic systems and AI applications in engineering beyond dataset predictions.</p>
<h2 id="heading-chapter-4-linear-algebra-the-geometry-of-data">Chapter 4: Linear Algebra - The Geometry of Data</h2>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765002362611/905a356e-7686-4212-94ac-2b4a5b359c8a.jpeg" alt="Magnifying glass pointing at a book" style="display:block;margin:0 auto" width="4272" height="2848" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/monochrome-photo-of-math-formulas-3729557/">Nothing Ahead</a>.</p>
<p>Linear algebra is like having organized containers for data.</p>
<p>Instead of playing with individual numbers, we can pack them into structured boxes that are easier to handle. These structured boxes are called matrices.</p>
<p>When you have a lot of variables like customer data, sensor readings, or images, these structured boxes are very helpful. Also, what we can do when we play around with these boxes is very valuable.</p>
<p>In AI, linear algebra is everywhere. Take matrices, for example – a key concept in Linear Algebra. LLMs perform many matrix multiplications as their core operation. The data that they take in is also organized into matrices. In image recognition, matrices are used to represent pixels of images.</p>
<p>So as you can see, this core Linear Algebra concept is important to understand. Let's start!</p>
<h3 id="heading-what-are-matrices-and-why-do-they-simplify-equations">What Are Matrices and Why Do They Simplify Equations?</h3>
<p>Very often, systems in the real world can be simplified and modeled with a system of equations.</p>
<p>Those equations are often differential equations of many orders. But to simplify, let’s choose a very simple system like the one below:</p>
<p>$$\begin{align} 2x + 3y - z &amp;= 7 \ x - 2y + 4z &amp;= -1 \ 3x + y + 2z &amp;= 10 \end{align}$$</p>
<p>When dealing with many variables and equations, writing each equation separately quickly becomes frustrating. Matrices provide a compact way to represent these systems.</p>
<p>For example, here’s the system above as a single matrix equation:</p>
<p>$$\begin{bmatrix} 2 &amp; 3 &amp; -1 \ 1 &amp; -2 &amp; 4 \ 3 &amp; 1 &amp; 2 \end{bmatrix} \begin{bmatrix} x \ y \ z \end{bmatrix} = \begin{bmatrix} 7 \ -1 \ 10 \end{bmatrix}$$</p>
<p>By seeing systems of equations as matrices, we can use linear algebra techniques to understand how the system behaves.</p>
<p>Some of these techniques are:</p>
<ul>
<li><p>Linear Independence, Dependence, and Rank</p>
</li>
<li><p>Determinants</p>
</li>
<li><p>Eigenvalues and Eigenvectors</p>
</li>
</ul>
<p>So to summarize:</p>
<ol>
<li><p>A real world system can be represented as a system of equations</p>
</li>
<li><p>A system of equations can be compressed in a structured manipulable form called a matrix.</p>
</li>
<li><p>With matrices and linear algebra techniques, we can understand how the system works.</p>
</li>
</ol>
<p>This way, we can study the basic behavior of a system with Linear Algebra.</p>
<p>For complex systems like a rocket, Linear Algebra is still the foundation. More advanced tools from control theory are used, but understanding simpler systems is essential for modeling and creating complex ones.</p>
<h3 id="heading-vectors-and-transformations-moving-in-multiple-directions">Vectors and Transformations: Moving in Multiple Directions</h3>
<p>Vectors are matrices <strong>with a single row or a single column.</strong> You can also think of them as the building blocks of AI. They represent things like data points, model parameters, and much more.</p>
<p>For example, every data input (like an image or sentence) becomes a vector that the model can processes.</p>
<p>Here are two examples of vectors:</p>
<p>$$\mathbf{A} = \begin{bmatrix} 4 &amp; -2 &amp; 7 &amp; 1 &amp; 5 \end{bmatrix}$$</p>
<p>And:</p>
<p>$$\mathbf{B} = \begin{bmatrix} 3 \ -1 \ 8 \ 0 \ -4 \end{bmatrix}$$</p>
<p>All operations that you can perform on matrices can also be performed on vectors.</p>
<p>In Python, we can represent this by:</p>
<pre><code class="language-plaintext">import numpy as np

# Define vectors A and B
A = np.array([4, -2, 7, 1, 5])
B = np.array([3, -1, 8, 0, -4])
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756171163870/4fa7dc5d-5b68-4baf-a211-3db0c3915781.png" alt="Python code image representing the code above. Defining two NumPy arrays." style="display:block;margin:0 auto" width="2080" height="844" loading="lazy">

<p>We’re using the <a href="https://numpy.org/">NumPy</a> library because it makes math with arrays easy and fast.</p>
<p>As a simplification of a system of equations, a vector with a single row represents:</p>
<p>$$\mathbf{A} = \begin{bmatrix} 4 &amp; -2 &amp; 7 &amp; 1 &amp; 5 \end{bmatrix}$$</p>
<p>And this represents this system of equations:</p>
<p>$$4x_1 - 2x_2 + 7x_3 + x_4 + 5x_5 = k$$</p>
<p>A vector with a single column represents:</p>
<p>$$\mathbf{B} = \begin{bmatrix} 3 \ -1 \ 8 \ 0 \ -4 \end{bmatrix}$$</p>
<p>Which represents this system of equations:</p>
<p>$$\begin{align} x_1 &amp;= 3 \ x_2 &amp;= -1 \ x_3 &amp;= 8 \ x_4 &amp;= 0 \ x_5 &amp;= -4 \end{align}$$</p>
<p>Now let’s see some matrix operations.</p>
<p>For example:</p>
<p>$$\mathbf{A} + \mathbf{B}^T = \begin{bmatrix} 4 &amp; -2 &amp; 7 &amp; 1 &amp; 5 \end{bmatrix} + \begin{bmatrix} 3 &amp; -1 &amp; 8 &amp; 0 &amp; -4 \end{bmatrix} = \begin{bmatrix} 7 &amp; -3 &amp; 15 &amp; 1 &amp; 1 \end{bmatrix}$$</p>
<pre><code class="language-plaintext">vector_addition = A + B
print("A + B =", vector_addition)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756171174149/62309c55-a5c5-4f69-aef6-e8ab341b5926.png" alt="Python code image representing the code above. Adding two NumPy arrays." style="display:block;margin:0 auto" width="2080" height="572" loading="lazy">

<p>Which gives the result of the equation above.</p>
<p>Often, vector addition is used to combine features. For example, adding many user preference vectors creates a profile of a user.</p>
<p>Here’s a <strong>scalar multiplication:</strong></p>
<p>$$3\mathbf{A} = 3\begin{bmatrix} 4 &amp; -2 &amp; 7 &amp; 1 &amp; 5 \end{bmatrix} = \begin{bmatrix} 12 &amp; -6 &amp; 21 &amp; 3 &amp; 15 \end{bmatrix}$$</p>
<pre><code class="language-plaintext">scalar_mult = 3 * A
print("3 * A =", scalar_mult)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756171180976/17e260a4-baab-4866-ba30-fc12e090b87a.png" alt="Python code image representing the code above. Multiplying a NumPy array with a scalar." style="display:block;margin:0 auto" width="2080" height="572" loading="lazy">

<p>Which gives the result of the equation above.</p>
<p>In AI, scaling vectors is usually done to adjust relevancy. For example, if we do a scalar product multiplication of a vector by 100, it means we are increasing its value. If it is by 0.3, it means we are reducing its importance.</p>
<p>Here's an outer product multiplication:</p>
<p>$$\mathbf{A} \otimes \mathbf{B} = \begin{bmatrix} 4 \ -2 \ 7 \ 1 \ 5 \end{bmatrix} \times \begin{bmatrix} 3 &amp; -1 &amp; 8 &amp; 0 &amp; -4 \end{bmatrix} = \begin{bmatrix} 12 &amp; -4 &amp; 32 &amp; 0 &amp; -20 \ -6 &amp; 2 &amp; -16 &amp; 0 &amp; 8 \ 21 &amp; -7 &amp; 56 &amp; 0 &amp; -28 \ 3 &amp; -1 &amp; 8 &amp; 0 &amp; -4 \ 15 &amp; -5 &amp; 40 &amp; 0 &amp; -20 \end{bmatrix}$$</p>
<p>And here’s a <strong>dot product multiplication</strong> (also called a <strong>dot product</strong>):</p>
<p>$$\mathbf{A} \cdot \mathbf{B}^T = \begin{bmatrix} 4 &amp; -2 &amp; 7 &amp; 1 &amp; 5 \end{bmatrix} \cdot \begin{bmatrix} 3 &amp; -1 &amp; 8 &amp; 0 &amp; -4 \end{bmatrix}$$</p>
<p>$$= 4 \cdot 3 + (-2) \cdot (-1) + 7 \cdot 8 + 1 \cdot 0 + 5 \cdot (-4) = 50$$</p>
<p>We mainly use dot products when we want to measure similarity, or alignment between two vectors.</p>
<p>In machine learning, in one simple phrase, it gives us a measure of similarity.</p>
<pre><code class="language-plaintext">import numpy as np

dot_product = np.dot(A, B)
print("A · B =", dot_product)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756171200508/ee7b9e61-c1cb-497d-b038-b6a672c6d24b.png" alt="Python code image representing the code above. Multiplying a NumPy array via dot product." style="display:block;margin:0 auto" width="2080" height="752" loading="lazy">

<p>Which gives the result of the equation above.</p>
<h3 id="heading-linear-independence-dependence-and-rank-why-it-matters">Linear Independence, Dependence, and Rank: Why It Matters</h3>
<p>A lot of times, matrices can be made smaller and simpler. So it’s a good practice to reduce a matrix to its simplest form before we start to analyze its properties.</p>
<p>When each row of a matrix can be made with other rows, then that matrix is linearly dependent. This means the matrix can be further modified.</p>
<p>This way, a matrix&nbsp; has the property of linear independence when its rows cannot be created by combining each other.</p>
<p>For example, when we have a complex matrix like this one:</p>
<p>$$C = \begin{bmatrix} 1 &amp; 2 &amp; 3 &amp; 4 \ 2 &amp; 4 &amp; 6 &amp; 8 \ 1 &amp; 3 &amp; 5 &amp; 7 \ 0 &amp; 1 &amp; 2 &amp; 3 \end{bmatrix}$$</p>
<p>We can, with calculations, convert to this:</p>
<p>$$C_{\text{reduced}} = \begin{bmatrix} 1 &amp; 0 &amp; -1 &amp; -2 \ 0 &amp; 1 &amp; 2 &amp; 3 \ 0 &amp; 0 &amp; 0 &amp; 0 \ 0 &amp; 0 &amp; 0 &amp; 0 \end{bmatrix}$$</p>
<p>if you are not familiar with row reduction, I recommend <a href="https://www.youtube.com/watch?v=eDb6iugi6Uk">this YouTube video</a>.</p>
<p>The above simplified matrix is the same thing as this:</p>
<p>$$C_{\text{reduced}} = \begin{bmatrix} 1 &amp; 0 &amp; -1 &amp; -2 \ 0 &amp; 1 &amp; 2 &amp; 3 \end{bmatrix}$$</p>
<p>This way, we conclude that the C matrix has a <strong>rank</strong> of 2.</p>
<p>In other words, since the simplest form of the matrix has only 2 rows with numbers, it has a rank of 2.</p>
<p>From this, we can conclude that the reduced version of the matrix is <strong>linearly independent</strong>. This is because no row or column can be made from the existing rows or column. It’s the simplest possible matrix.</p>
<p>The original matrix C is linearly dependent because some rows are just multiples or combinations of other rows. For example, row 2 of the original matrix C is exactly row 1 multiplied by 2.</p>
<p>Another way of seeing this is that we have 4 rows in the original matrix and the rank of matrix C is 2. Since they are not equal, C is linearly dependent.</p>
<h4 id="heading-why-are-these-concepts-important">Why are these concepts important?</h4>
<p>Linear independence and rank are important in engineering because they show whether equations, represented as matrices, give unique information. In electrical circuits and control systems, knowing that equations, represented as matrices, are independent ensures that you have unique solutions and avoids confusion.</p>
<p>The matrix rank shows the maximum number of independent equations that can exist. This help engineers model the simplest possible form of the systems.</p>
<p>In LLMs like ChatGPT, Gemini, Grok, and Claude, linear independence, dependence, and rank are used in a very important technique called LoRA (Low-Rank Adaptation).</p>
<p>LoRA (Low-Rank Adaptation) is widely used to calibrate these models to make sure they adapt efficiently to new tasks or domains without retraining the full model. Also, there are variants of this technique, like Quantized LoRA. This way, in many data centers, LoRA saves energy, water for cooling, and so many other things.</p>
<h3 id="heading-determinants-measuring-space-and-scaling">Determinants: Measuring Space and Scaling</h3>
<p>Why are determinants important?</p>
<p>Determinants tell us if a system of equations has infinite solutions, no solutions, or if it has a unique solution without having to solve the whole system.</p>
<p>This way, instead of immediately trying to solve a complex system, we can first use the determinant to find out if it is even worth solving in the first place.</p>
<p>Many engineers don’t really understand the importance of the determinant. The only thing they know is the formula and how to apply it.</p>
<p>So now let’s learn, with some examples, what exactly the determinant is and why it matters.</p>
<p>A determinant is just a number. It’s always calculated from a square matrix. By calculating the determinant, we can find certain properties about the system it represents.</p>
<p>The determinant of a given matrix A:</p>
<p>$$A = \begin{bmatrix} a &amp; b \ c &amp; d \end{bmatrix}.$$</p>
<p>can be represented by two notations:</p>
<p>$$\det(A) = ad - bc$$</p>
<p>or</p>
<p>$$|A| = ad - bc$$</p>
<p>Both are the same thing.</p>
<p>Let's see how to calculate a determinant:</p>
<p>$$|A| = \begin{vmatrix} 2 &amp; 3 \ 1 &amp; 4 \end{vmatrix} = (2)(4) - (3)(1) = 8 - 3 = 5.$$</p>
<p>Let’s see how to do this in Python:</p>
<pre><code class="language-plaintext">import numpy as np

# Define the matrix
A = np.array([
    [2, 3],
    [1, 4]
])

# Calculate the determinant
det_A = np.linalg.det(A)

print("Determinant of A:", det_A)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756233259727/feea57a3-5a33-49b9-a74a-979eba5ec7fe.png" alt="Python code image representing the code above. Finding the determinant." style="display:block;margin:0 auto" width="2080" height="1472" loading="lazy">

<h4 id="heading-the-same-calculation-works-for-other-matrices">The same calculation works for other matrices!</h4>
<p>Here's the determinant formula for a 3×3 matrix:</p>
<p>For a 3 by 3 matrix:</p>
<p>$$|B|= \begin{vmatrix} a &amp; b &amp; c \ d &amp; e &amp; f \ g &amp; h &amp; i \end{vmatrix} = aei + bfg + cdh - ceg - bdi - afh.$$</p>
<p>Now let’s apply the formula to an example:</p>
<p>$$|B| = \begin{vmatrix} 1 &amp; 2 &amp; 3 \ 0 &amp; 4 &amp; 5 \ 1 &amp; 0 &amp; 6 \end{vmatrix} = (1)(4)(6) + (2)(5)(1) + (3)(0)(0) - (3)(4)(1) - (2)(0)(6) - (1)(5)(0)$$</p>
<p>Assessing each term:</p>
<p>$$= (1)(4)(6) + (2)(5)(1) - (3)(4)(1) = 4 \cdot 6 + 2 \cdot 5 - ( 3 \cdot 4) = 24+10-12 = 22$$</p>
<p>In Python code:</p>
<pre><code class="language-plaintext">import numpy as np

# Define the matrix
B = np.array([
    [1, 2, 3],
    [0, 4, 5],
    [1, 0, 6]
])

# Calculate the determinant
det_B = np.linalg.det(B)

print("Determinant of B:", det_B)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756233606615/4e333b35-4714-480a-8a3b-62db799614e1.png" alt="Python code image representing the code above. Finding a 3 by 3 determinant." style="display:block;margin:0 auto" width="2080" height="1564" loading="lazy">

<p>Now, let’s visualize matrix A by plotting its column vectors. Each column will become a vector: (3,1) and (-2,4). This shows us geometrically what the matrix is actually doing.</p>
<p>In a geogebra graph, it gives us this:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756235393476/6b5c38ea-7b27-4e3d-8ad4-346417d35e77.png" alt="Representation of 2 vectors in a Cartesian plane." style="display:block;margin:0 auto" width="1320" height="1003" loading="lazy">

<p>As we can see, the vectors define how each variable influences the system. By visualizing what the matrices are doing, we can find patterns that are harder to find just by looking at formulas.</p>
<p><strong>What does this mean visually?</strong></p>
<p>It means that in the space, this is what our matrix looks like. It’s also how our system of equations is represented.</p>
<p>C1 represents the “force“ or the impact the variable x1 has. And C2 does the same thing for the variable x2.</p>
<p>Now we’ll focus on a 3D matrix example. This matrix D represents a system of three equations with three variables:</p>
<p>$$D = \begin{bmatrix} 2 &amp; -1 &amp; 3 \ 4 &amp; 0 &amp; -2 \ -1 &amp; 5 &amp; 1 \end{bmatrix}$$</p>
<p>$$\begin{align} 2x_1 - x_2 + 3x_3 &amp;= p \ 4x_1 + 0x_2 - 2x_3 &amp;= q \ -x_1 + 5x_2 + x_3 &amp;= r \end{align}$$</p>
<p>Each column can be described as a separate vector:</p>
<p>$$\begin{equation} D = \left[ D_1 \mid D_2 \mid D_3 \right] = \left[ \begin{bmatrix} 2 \ 4 \ -1 \end{bmatrix} \mid \begin{bmatrix} -1 \ 0 \ 5 \end{bmatrix} \mid \begin{bmatrix} 3 \ -2 \ 1 \end{bmatrix} \right] \end{equation}$$</p>
<p>As we can see, D was decomposed in 3 new column vectors:</p>
<p>$$\begin{equation} D_1 = \begin{bmatrix} 2 \ 4 \ -1 \end{bmatrix} \end{equation}$$</p>
<p>and:</p>
<p>$$\begin{equation} D_2 = \begin{bmatrix} -1 \ 0 \ 5 \end{bmatrix} \end{equation}$$</p>
<p>and:</p>
<p>$$\begin{equation} D_3 = \begin{bmatrix} 3 \ -2 \ 1 \end{bmatrix} \end{equation}$$</p>
<p>In a geogebra graph, it gives us this:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756236913078/8d8a3d48-20a9-423b-bfb8-4368d92ec340.png" alt="Representation of 3 vectors in a 3D Cartesian plane." style="display:block;margin:0 auto" width="1525" height="1141" loading="lazy">

<p>In 3D, each vector points in its own direction. Together, they organize three planes. Where all three planes touch is the solution to the system.</p>
<p>This is a key advantage of matrices and linear algebra. They help us visualize both simple and complex systems, enhancing systems thinking and first principles thinking.</p>
<p>The determinant is directly connected to these visualizations. For example, in 2D it measures the area that the vectors stretch over. Now we’ll see how that’s possible.</p>
<p>Let's use matrix A and see what its determinant looks like in geometric terms:</p>
<p>$$A = \begin{bmatrix} 2 &amp; 3 \ 1 &amp; 4 \end{bmatrix}$$</p>
<p>Which can be decomposed into 2 vectors <code>u</code> and <code>v</code>:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756241016899/ded47498-b030-4fa1-a4fe-07153d138a7f.png" alt="Representation of 2 vectors (matrix A) in a Cartesian plane." style="display:block;margin:0 auto" width="859" height="835" loading="lazy">

<p>It gives us this determinant:</p>
<p>$$|A| = \begin{vmatrix} 2 &amp; 3 \ 1 &amp; 4 \end{vmatrix} = (2)(4) - (3)(1) = 8 - 3 = 5.$$</p>
<p>Now let’s see the determinant visually.</p>
<p>From (2,1) and (3,4), we can draw vectors parallel to u and and v. These are called u' and v' and have the same magnitude. They meet at (5,5), and we have a parallelogram that’s completed with these points: (0,0),(2,1),(3,4),(5,5)</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756241586617/d825b8e2-d839-4b15-bdd0-d9b5efd80942.png" alt="Representation of the 4 vectors being used in the determinant" style="display:block;margin:0 auto" width="1063" height="1048" loading="lazy">

<p>The area of the parallelogram is the determinant:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756241692073/deb2e0cd-32a3-4a1a-90e7-e556f5039169.png" alt="Illustrating that the area limited by the 4 vectors is the determinant." style="display:block;margin:0 auto" width="1062" height="976" loading="lazy">

<p>Let’s see another example.</p>
<p>Let’s use a matrix F and see what it truly is:</p>
<p>$$F = \begin{bmatrix} 1 &amp; 2 \ 2 &amp; 4 \end{bmatrix}$$</p>
<p>It gives us this determinant:</p>
<p>$$|F| = \begin{vmatrix} 1 &amp; 2 \ 2 &amp; 4 \end{vmatrix} = (1)(4) - (2)(2) = 4 - 4 = 0$$</p>
<p>In geogebra, we can see that:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756242215981/d88f2e80-04ba-46b9-979d-d7684f161210.png" alt="Representation of the 2 vectors being used in the determinant" style="display:block;margin:0 auto" width="778" height="1072" loading="lazy">

<p>Now let’s try to see the determinant visually:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756242340382/46551578-69a5-4ef9-ab86-9149e7fb4aaa.png" alt="Illustrating that the area limited by the 2 vectors is the determinant and that it does not exist. So the determinant is zero." style="display:block;margin:0 auto" width="721" height="991" loading="lazy">

<p>We can conclude that the area is 0.</p>
<p>Now let’s use a matrix G and see what it truly is:</p>
<p>$$G = \begin{bmatrix} 1 &amp; 5 \ 2 &amp; 3 \end{bmatrix}$$</p>
<p>It gives us this determinant:</p>
<p>$$|G| = \begin{vmatrix} 1 &amp; 5 \ 2 &amp; 3 \end{vmatrix} = (1)(3) - (5)(2) = 3 - 10 = -7$$</p>
<p>In geogebra, we can see that:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756242987960/d182b725-81ba-4042-81e1-6b0232e09ffb.png" alt="Representation of the 2 vectors being used to find the determinant" style="display:block;margin:0 auto" width="1411" height="976" loading="lazy">

<p>Now let’s try to see the determinant visually.</p>
<p>From (1,2) and (5,3), we can draw vectors parallel to u and and v. These are called u' and v' and have the same magnitude. They meet at (6,5). A parallelogram is completed with these points: (0,0),(1,2),(5,3),(6,5)</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756243098714/881693d4-7a84-4b72-bb87-3fb48b25fe4b.png" alt="Representation of 4 vectors being used to find the determinant before showing the area" style="display:block;margin:0 auto" width="1201" height="1030" loading="lazy">

<p>Again, the area of the parallelogram is the determinant:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756243316071/ce8fa65b-6370-4ada-9fe6-cdf20ab4546d.png" alt="Illustrating that the area limited by the 4 vectors is the determinant." style="display:block;margin:0 auto" width="1167" height="1023" loading="lazy">

<p>We just saw that the determinant is the area of a parallelogram formed by the vectors. When the determinant is 0, there is no area. In other cases, there is an area. But what does this mean, and why do we care about these different values?</p>
<p><strong>When the det = 0:</strong></p>
<ul>
<li><p>The vectors are linearly dependent (one can be written as a combination of the others)</p>
</li>
<li><p>They lie on the same line or one is a scaled version of the other</p>
</li>
<li><p>The parallelogram collapses to a line, hence zero area</p>
</li>
<li><p>This tells us the matrix has no inverse</p>
</li>
<li><p><strong>Systems of equations either have no solution or infinitely many solutions</strong></p>
</li>
</ul>
<p><strong>When the det ≠ 0 (det &gt; 0 or det &lt; 0):</strong></p>
<ul>
<li><p>The vectors form a proper parallelogram with an area</p>
<ul>
<li><p>If det &gt; 0, the area is positive and transformation preserves orientation</p>
</li>
<li><p>If det &lt; 0, the area is negative and the orientation is flipped</p>
</li>
</ul>
</li>
<li><p>The vectors are linearly independent</p>
</li>
<li><p><strong>Systems of equations have exactly one solution</strong></p>
</li>
</ul>
<p>In electrical engineering, determinants help verify if a control system is controllable and observable.</p>
<p>Control systems use matrices a lot. For this reason, checking if their determinants are zero or non-zero tells engineers:</p>
<ul>
<li><p>If it is controllable, it means the system is reachable, which helps in stabilization and performance optimization.</p>
</li>
<li><p>If it is observable, it means the system is measurable, which helps in fault detection and system monitoring.</p>
</li>
</ul>
<p>In finite element analysis, a very popular math tool to solve partial differential equations, determinants helps figure out quickly if the calculations will give reliable results.</p>
<p>This way, with finite element analysis, we can design safer buildings, optimize aircraft wings, and simulate medical implants – all of which have a large impact on human lives and safety.</p>
<p>In machine learning, determinants are crucial to understanding data transformations. In these methods, if a determinant with a value of zero shows up, it means you are losing information and can't recover original data.</p>
<p>Also in deep learning, it’s used to decide the first parameters of neural networks (weight initialization) to prevent problems like the vanishing/exploding gradients.</p>
<p>In a 3×3 matrix, the determinant represents the volume of a parallelepiped (a 3D "box") formed by three vectors in 3D space.</p>
<ul>
<li><p>If det = 0: The three vectors lie in the same plane, so they don't span any 3D volume</p>
</li>
<li><p>If det ≠ 0: The vectors form a proper 3D shape with actual volume</p>
</li>
</ul>
<p>The absolute value |det| gives you the exact volume of that <a href="https://en.wikipedia.org/wiki/Parallelepiped">parallelepiped</a>.</p>
<p>For example, if you have vectors a, b, and c, the determinant tells you how much 3D space they "fill up" when you use them as the edges of a box.</p>
<p>This is where it gets fascinating:</p>
<ul>
<li><p>4×4 matrix: The determinant represents the "hypervolume" of a 4D parallelepiped formed by four vectors in 4-dimensional space.</p>
</li>
<li><p>1000×1000 matrix: The determinant represents the hypervolume in 1000-dimensional space!</p>
</li>
</ul>
<p>So, to summarize, the determinant tells us easily if there are no solutions, infinite solutions, or exactly one solution in a system of equations, represented by a compact matrix.</p>
<h3 id="heading-what-are-mathematical-spaces-and-how-do-they-simplify-calculations">What Are Mathematical Spaces and How Do They Simplify Calculations?</h3>
<p>We now have a great foundation to understand the rest of this chapter on linear algebra.</p>
<p>Now, we will see see how a linearly independent matrix create something called a basis. Also, we will see that a basis is just a a set of building blocks for mathematical spaces!</p>
<p>The row vectors of a linearly independent matrix form a basis.</p>
<p>For example in matrix A, which is linearly independent:</p>
<p>$$A = \begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 \ 0 &amp; 1 &amp; 0 &amp; 0 \ 0 &amp; 0 &amp; 1 &amp; 0 \ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}$$</p>
<p>forms this set:</p>
<p>$$((1,0,0,0), (0,1,0,0), (0,0,1,0), (0,0,0,1))$$</p>
<p>In this case, since matrix A is linearly independent, the set of matrix rows is called a <strong>basis</strong>. From this basis, you can create endless linear combinations of any other vector. The collection of all these possible combinations is called a <strong>mathematical space</strong>.</p>
<p>A mathematical space is an infinite set where all linear combinations of a basis exist. Its called a basis because these vectors <strong>form the base</strong> to express any vector in the space as a linear combination.</p>
<p>This matrix B is linearly independent:</p>
<p>$$B = \begin{bmatrix} 1 &amp; 0 \ 0 &amp; 1 \ \end{bmatrix}$$</p>
<p>And forms this set:</p>
<p>$$((1, 0), (0, 1))$$</p>
<p>And from this come all possible points in this cartesian coordinate system:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756247201687/a847b8c0-5678-431c-b446-e1897afdffc6.png" alt="Showing in the Cartesian plane where the point (2, 3) is" style="display:block;margin:0 auto" width="1084" height="1114" loading="lazy">

<p>For example, mathematically, we can get the point (2,3) by:</p>
<p>$$(x=2, y=3) = 2(1, 0) + 3(0, 1) = (2, 0) + (0, 3) = (2, 3)$$</p>
<p>Note: There are other bases for the cartesian coordinate plane. I chose this one because it’s the easiest to understand.</p>
<h3 id="heading-eigenvalues-and-eigenvectors-unlocking-hidden-patterns">Eigenvalues and Eigenvectors: Unlocking Hidden Patterns</h3>
<p>Eigenvalues and eigenvectors, in my opinion, are far simpler than what mathematics professors make them out to be at university:</p>
<ul>
<li><p>Eigenvalues tell you how much a matrix stretches or shrinks things.</p>
</li>
<li><p>Eigenvectors tell you which directions stay unchanged when the matrix transforms them.</p>
</li>
</ul>
<p>This way, a matrix may have one or many eigenvalues which in turn result in many eigenvectors.</p>
<p>Let’s see an example:</p>
<p>For a square matrix A, eigenvalue λ, and eigenvector v:</p>
<p>$$Av=λv$$</p>
<p>The easiest way to find the eigenvalue is to calculate this:</p>
<p>$$det(A−λI)=0$$</p>
<p>or:</p>
<p>$$|A−λI|=0$$</p>
<p>Again, we have different notations for the determinant, but they’re the same thing.</p>
<p>Anyway, let’s define a very simple matrix A:</p>
<p>$$A = \begin{bmatrix} 2 &amp; 0 \ 0 &amp; 3 \end{bmatrix}$$</p>
<p>Now let’s make some calculations.</p>
<p>This formula:</p>
<p>$$det(A−λI)=0$$</p>
<p>Can be decomposed into:</p>
<p>$$det(\begin{bmatrix} 2 &amp; 0 \ 0 &amp; 3 \end{bmatrix} - λ \times \begin{bmatrix} 1 &amp; 0 \ 0 &amp; 1 \end{bmatrix}) = 0$$</p>
<p>Which is the same has:</p>
<p>$$det(\begin{bmatrix} 2 &amp; 0 \ 0 &amp; 3 \end{bmatrix} - \begin{bmatrix} λ &amp; 0 \ 0 &amp; λ \end{bmatrix}) = 0$$</p>
<p>Which gives us:</p>
<p>$$det(\begin{bmatrix} 2-λ &amp; 0 \ 0 &amp; 3-λ \end{bmatrix}) = 0$$</p>
<p>By the calculations we made above on the determinant, we can conclude that:</p>
<p>$$(2-λ) \times (3-λ) = 0$$</p>
<p>Which is the same has:</p>
<p>$$2-\lambda = 0 \text{ or } 3-\lambda = 0$$</p>
<p>Which gives us these eigenvalues:</p>
<p>$$\lambda_1 = 2, \quad \lambda_2 = 3$$</p>
<p>And these eigenvectors:</p>
<p>$$\mathbf{v_1} = \begin{bmatrix} 1 \ 0 \end{bmatrix}, \quad \mathbf{v_2} = \begin{bmatrix} 0 \ 1 \end{bmatrix}$$</p>
<p>This means that in the Cartesian coordinate system:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756321668969/949a5a4b-12ff-4490-bbff-1cc032bc5705.png" alt="Showing how the eigenvectors are related to the vectors in matrix A visually. Both have the same directions but different scalar values." style="display:block;margin:0 auto" width="997" height="988" loading="lazy">

<p>By applying the eigenvectors, we can see that:</p>
<ul>
<li>The eigenvalue 2 is associated with the eigenvector v1:</li>
</ul>
<p>$$A\mathbf{v_1} = \begin{bmatrix} 2 &amp; 0 \ 0 &amp; 3 \end{bmatrix}\begin{bmatrix} 1 \ 0 \end{bmatrix} = \begin{bmatrix} 2 \ 0 \end{bmatrix} = 2\begin{bmatrix} 1 \ 0 \end{bmatrix}$$</p>
<ul>
<li>The eigenvalue 3 is associated with the eigenvector v2:</li>
</ul>
<p>$$A\mathbf{v_2} = \begin{bmatrix} 2 &amp; 0 \ 0 &amp; 3 \end{bmatrix}\begin{bmatrix} 0 \ 1 \end{bmatrix} = \begin{bmatrix} 0 \ 3 \end{bmatrix} = 3\begin{bmatrix} 0 \ 1 \end{bmatrix}$$</p>
<p>Here is the Python code to calculate this:</p>
<pre><code class="language-plaintext">import numpy as np

# Define matrix A
A = np.array([[2, 0],
              [0, 3]])

# Calculate eigenvalues and eigenvectors
eigenvalues, eigenvectors = np.linalg.eig(A)

print("Eigenvalues:")
print(eigenvalues)

print("Eigenvectors (columns):")
print(eigenvectors)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756322044095/bc76f0ec-1d13-4845-b0f3-2847118860a3.png" alt="Python code, with NumPy array, showing how to find the eigenvalues" style="display:block;margin:0 auto" width="2080" height="1744" loading="lazy">

<p>Eigenvalues and eigenvectors are key tools in engineering and machine learning because they reveal a matrix's fundamental behavior. Although a matrix transformation might seem complex, in reality:</p>
<ul>
<li><p>Eigenvalues show how much stretching or compression occur.</p>
</li>
<li><p>Eigenvectors identify the special directions where this stretching happens most naturally.</p>
</li>
</ul>
<p>In machine learning, we can use Principal Component Analysis (PCA) to make datasets smaller.</p>
<p>So, for example, let's say you’re building a machine learning application to predict heart disease. You have 100 data categories and 1 target variable telling whether a person has it or not.</p>
<p>With PCA, you can convert the 100 categories into, say, 40 categories. This way, you can make a smaller machine learning model and save computational resources.</p>
<p>PCA uses eigenvectors of covariance matrices to find important directions in data with many variables. It reduces data size without losing much detail, helping machine learning algorithms focus on key features and ignore unnecessary information.</p>
<h3 id="heading-applications-of-linear-algebra-in-ai-and-control-theory">Applications of Linear Algebra in AI and Control Theory</h3>
<p>‌Linear algebra serves as the mathematical foundation for all engineering fields.</p>
<p>In addition, the principles of matrices and linear transformations provide the computational foundation that makes modern AI possible while enabling the control of complex systems.</p>
<p>All LLMs, from ChatGPT and Claude to Gemini and Grok, rely on linear operations.</p>
<p>All these systems carry out huge matrix multiplications to handle and create human language. So, when you type something into ChatGPT, probably millions of matrix multiplications are happening as you wait for a response!</p>
<p>In control theory, especially in an area called state-space control theory, matrices make it possible to create complex controllers. Linear algebra helps engineers design controllers for things like aircraft autopilots and robotic systems, among other applications</p>
<p>For example, when a rocket adjusts its trajectory or a drone maintains stable flight, many matrix multiplications are happening to determine the best way to guarantee the system’s stability.</p>
<p>Thanks to GPUs, linear algebra matrices are very efficient to compute. Also, any new matrix multiplication algorithms or special hardware for faster linear operations can greatly enhance AI and control systems.</p>
<p>In the end, linear algebra is the hidden mathematical engine powering the current AI revolution.</p>
<h2 id="heading-chapter-5-multivariable-calculus-change-in-many-directions">Chapter 5: Multivariable Calculus -&nbsp;Change in Many Directions</h2>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765002238157/a377cdc6-7e85-491b-90b8-8b3243618288.jpeg" alt="Photo of a women writing a calculus equation in a board" style="display:block;margin:0 auto" width="7804" height="5205" loading="lazy">

<p><a href="https://www.pexels.com/photo/woman-writing-on-a-whiteboard-3862130/">Photo by ThisIsEngineering</a></p>
<h3 id="heading-limits-and-continuity-understanding-smooth-change">Limits and Continuity: Understanding Smooth Change</h3>
<p>Calculus is one of the most valuable areas of mathematics and it focus on the study of continuous change.</p>
<p>Before we start learning a topic that makes many people give up on engineering degrees, I want to once again assure you that this chapter is very easily explained with a lot of images and code examples.</p>
<p>Also, just like linear algebra, many concepts in calculus are components of tools that have helped create billion-dollar industries.</p>
<h4 id="heading-what-is-continuity">What is continuity?</h4>
<p>Before going and explaining topics like derivatives and integrals, we need to understand continuity.</p>
<p>In simple terms, continuity means that a function has no breaks, jumps, or holes.</p>
<p>Essentially, you can draw it without lifting your pencil from the paper.</p>
<p>For example, this function is continuous:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756402257225/f9cfc4f3-a6f1-4fb9-9ed1-f690c4ffffc4.png" alt="Example of a function that is continuous" style="display:block;margin:0 auto" width="634" height="901" loading="lazy">

<p>You can draw this graph without taking the pencil off the paper.</p>
<p>The above graph is represented by this function:</p>
<p>$$y = x^2 - 4x + 3$$</p>
<p>But the below function is <strong>not</strong> continuous:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756402337970/b5a65748-572d-4342-9685-9472babde38a.png" alt="Example of a function that is not continuous" style="display:block;margin:0 auto" width="1315" height="1084" loading="lazy">

<p>This one, you <strong>can’t</strong> draw without taking the pencil off the paper.</p>
<p>It’s represented by this piecewise function:</p>
<p>$$y = \begin{cases} 1.5 + \frac{1}{x+1} &amp; \text{if } -1 &lt; x &lt; 2 \ 2 + \frac{2}{(x-1)^2} &amp; \text{if } x &gt; 2 \end{cases}$$</p>
<p>This piecewise function is essentially two individual functions for two different intervals of numbers. Since calculus is the study of continuous change, we can only realistically use it in continuous functions.</p>
<h4 id="heading-how-do-limits-guarantee-continuity">How do limits guarantee continuity?</h4>
<p>We can only use tools like derivatives and integrals if a function is continuous.</p>
<p>How can we describe mathematically that a function is continuous – like drawing it without lifting our pencil from the paper?</p>
<p>Limits solve that problem.</p>
<p>When we take the limit of a function at a given point, we're asking: what value does a function approach as we get close to that point?</p>
<p>Let's look at some examples of this function at these points and also understand the notation used in limits:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756403511442/de3450f2-dcf9-40e3-a04e-846334abeebd.png" alt="Example of a function that is continuous and its various points" style="display:block;margin:0 auto" width="759" height="1104" loading="lazy">

<ol>
<li><strong>What is the limit of the point x=0?</strong></li>
</ol>
<p>It is 3. It actually crosses the y axis.</p>
<p>In mathematical notation,</p>
<p>$$\begin{align} \lim_{x \to 0} (x^2 - 4x + 3) &amp;= (0)^2 - 4(0) + 3 \ &amp;= 0 - 0 + 3 \ &amp;= 3 \end{align}$$</p>
<p>In this notation, we're asking what the value of the y function is as x gets very close to 0. Think of x as being at 0.00000000000001 or -0.00000000000001. It gets so close that we can consider it near enough.</p>
<ol>
<li><strong>What is the limit of the point x=1?</strong></li>
</ol>
<p>Le’s see another example:</p>
<p>In this case, it’s 0.</p>
<p>$$\begin{align} \lim_{x \to 1} (x^2 - 4x + 3) &amp;= (1)^2 - 4(1) + 3 \ &amp;= 1 - 4 + 3 \ &amp;= 0 \end{align}$$</p>
<p>In this notation, we're asking what the value of the y function is as x gets very close to 1. Think of x as being at 0.99999999999999 or 1.00000000000001. It gets so close that we can consider it near enough.</p>
<ol>
<li><strong>What is the limit of the point x=2?</strong></li>
</ol>
<p>Le’s see another example</p>
<p>Here, it’s -1.</p>
<p>$$\begin{align} \lim_{x \to 2} (x^2 - 4x + 3) &amp;= (2)^2 - 4(2) + 3 \ &amp;= 4 - 8 + 3 \ &amp;= -1 \end{align}$$</p>
<p>Some more quick examples:</p>
<ol>
<li><strong>What is the limit of the point x=3?</strong></li>
</ol>
<p>In this notation, we're asking what the value of the y function is as x gets very close to 1. Think of x as being at 1.99999999999999 or 2.00000000000001. It gets so close that we can consider it near enough.</p>
<ol>
<li><strong>What is the limit of the point x=4?</strong></li>
</ol>
<p>It is 0.</p>
<ol>
<li><strong>What is the limit of the point x=5?</strong></li>
</ol>
<p>It is 3.</p>
<p>Now let’s see another example:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756403617161/b67b2977-8ae4-4c06-8156-d7c6a64ee2e1.png" alt="Example of a function that is not continuous at a point of x=2" style="display:block;margin:0 auto" width="1315" height="1084" loading="lazy">

<p>In the point x=2, it’s not well defined</p>
<ul>
<li><p>If we draw with a pencil from the left to x=2, we end up with 1.83333</p>
</li>
<li><p>If we draw with a pencil from the right to x=2, we end up with 4</p>
</li>
</ul>
<h3 id="heading-why-are-limits-important-to-understand-derivatives-and-integrals">Why are limits important to understand derivatives and integrals?</h3>
<p>As we have seen, when we talk about limits, we are talking about a value that symbolizes the value that a function approaches as it comes toward a particular point.</p>
<p>It’s critical to note that we're not looking at the value of that point itself. We’re looking at what happens as we get so near to it that we can pin down what value the function is approaching.</p>
<p>I will now show a very simple example to demonstrate this concept using mathematical notation.</p>
<p>I know that limits can be a difficult concept to understand at first. But if you understand limits very well, then you'll be well-prepared to understand derivatives and integrals.</p>
<p>And, as you’ll see, derivatives are responsible for modern AI and integrals are important parts of tolls widely used in billion-dollar industries.</p>
<p>I want you to understand the <strong>intuition</strong> behind this.</p>
<p>The function z(x) is continuous:</p>
<p>$$z(x) = \frac{3x + 7}{x + 2}$$</p>
<p><strong>So to what value does this expression converge as x approaches infinity?</strong></p>
<p>If you have a background in math, you might see why. But here for those who aren’t sure:</p>
<ul>
<li>It converges to 3.</li>
</ul>
<p>This time, the limit will be approaching infinity instead of a constant:</p>
<p>$$\begin{align} \lim_{x \to \infty} \frac{3x + 7}{x + 2} \end{align}$$</p>
<p>Let’s solve this in a very simple way:</p>
<ul>
<li>For x = 1:</li>
</ul>
<p>$$f(1) = \frac{3(1) + 7}{1 + 2} = \frac{10}{3} \approx 3.333...$$</p>
<ul>
<li>For x = 5:</li>
</ul>
<p>$$f(5) = \frac{3(5) + 7}{5 + 2} = \frac{22}{7} \approx 3.143...$$</p>
<ul>
<li>For x = 10:</li>
</ul>
<p>$$f(10) = \frac{3(10) + 7}{10 + 2} = \frac{37}{12} \approx 3.083...$$</p>
<ul>
<li>For x = 50:</li>
</ul>
<p>$$f(50) = \frac{3(50) + 7}{50 + 2} = \frac{157}{52} \approx 3.019...$$</p>
<ul>
<li>For x = 100:</li>
</ul>
<p>$$f(100) = \frac{3(100) + 7}{100 + 2} = \frac{307}{102} \approx 3.010...$$</p>
<ul>
<li>For x = 1000:</li>
</ul>
<p>$$f(1000) = \frac{3(1000) + 7}{1000 + 2} = \frac{3007}{1002} \approx 3.001...$$</p>
<ul>
<li>For x = 10000:</li>
</ul>
<p>$$f(10000) = \frac{3(10000) + 7}{10000 + 2} = \frac{30007}{10002} \approx 3.0001...$$</p>
<p>As x gets bigger and bigger, we get closer and closer to 3.</p>
<p>This is the main idea of limits: Describe the value a function approaches as the input approaches some point.</p>
<p>This same idea applies to derivatives: they’re just limits that measure rates of change (slopes of tangent lines).</p>
<p>And as well, Integrals are just limits that measure accumulated quantities (areas under curves)..</p>
<p>Let’s now see how derivatives work in depth.</p>
<h3 id="heading-derivatives-how-things-change-and-how-fast">Derivatives: How Things Change and How Fast</h3>
<p>As I said before, derivatives are just limits that measure rates of change (slopes of tangent lines).</p>
<p>But what does this actually mean?</p>
<p>Let’s see an example:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756755419750/75b36254-0f4a-4395-8dd4-14ac16399ff3.png" alt="Example of a function" style="display:block;margin:0 auto" width="1263" height="1005" loading="lazy">

<p><strong>What is the rate of change in the point A?</strong></p>
<p>Hard question right? Let’s think how to answer this with limits.</p>
<p>We can find the limit of the rate of change in point A(0.72, 0.66), also called the instantaneous rate of change.</p>
<p>Let’s do that:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756755680672/40f94361-55c7-4a9e-bfaf-b2b855fa0712.png" alt="Example of a function and choosing two points (B and C) to find the rate of change in point A" style="display:block;margin:0 auto" width="1437" height="957" loading="lazy">

<p>To find the slope, we take the coordinates of the points B(0.2, 0.2) and C(1.6, 1):</p>
<p>$$\text{slope} = \frac{1 - 0.2}{1.6 - 0.2} = \frac{0.8}{1.4} = \frac{4}{7} \approx 0.571$$</p>
<p>This gives us a rate of change:</p>
<p>$$y=0.571x + 0.084$$</p>
<p>Let's approximate more:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756756069833/3a4a1991-4983-4751-a68e-68bd6780300d.png" alt="Example of a function and choosing two points (B and C) to find the rate of change in point A. But B and C are closer to A." style="display:block;margin:0 auto" width="1492" height="1027" loading="lazy">

<p>Let’s also zoom in:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756756131072/f96b7f82-a4ed-4720-8c87-fd2936bae9d9.png" alt="Example of a function and choosing two points (B and C) to find the rate of change in point A. But B and C are closer to A, and we have to zoom in." style="display:block;margin:0 auto" width="1569" height="1134" loading="lazy">

<p>To find the slope, we use the coordinates of the points B(0.58, 0.55) and C(0.85, 0.75):</p>
<p>$$\text{slope} = \frac{0.85- 0.58}{0.75 - 0.55} = \frac{0.27}{0.2} = \frac{2.7}{2} \approx 1.35$$</p>
<p>It gives us a rate of change:</p>
<p>$$y=1.35x + 0.11$$</p>
<p>Now let's approximate a lot:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756756879223/11d26af3-06ec-4419-b631-10308b4cadef.png" alt="Example of a function and choosing two points (B and C) to find the rate of change in point A. But B and C are closer to A, and we have to zoom in." style="display:block;margin:0 auto" width="1513" height="1098" loading="lazy">

<p>To find the slope, we use the coordinates of the points B(0.7242549, 0.6625776) and C(0.7242884, 0.66260026):</p>
<p>$$\text{slope} = \frac{0.66260026- 0.6625776}{0.7242884- 0.7242549} = \frac{0.0000226}{0.0000335} = \frac{0.226}{0.335} \approx 0.674$$</p>
<p>Now let’s zoom out:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756757322888/a6f58b41-d6ff-44fd-b18f-06fb1f8f0e06.png" alt="Rate of change at point C" style="display:block;margin:0 auto" width="1195" height="907" loading="lazy">

<p>As we can see, we are so close that we can consider the limit of the rate of change to be 0.65.</p>
<p>It gives us the rate of change:</p>
<p>$$y=0.674x + 0.12$$</p>
<p><strong>This way, the limit of a rate of change is called a derivative.</strong></p>
<p>To recap, here is an animation:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756766733257/a1754b47-7c57-4387-8b4c-886ed7b8f80a.gif" alt="GIF animation based on previous images" style="display:block;margin:0 auto" width="1195" height="907" loading="lazy">

<p>Here’s a Python code example that lets you find the derivative in point A:</p>
<pre><code class="language-python">import sympy as sp

x = sp.symbols('x')
f = sp.sin(x)

# Derivative of sin(x)
derivative_of_sin = sp.diff(f, x)

# Evaluate at x = 0.72 and x = 0.66
val = f_prime.subs(x, 0.72).evalf()

print("Derivative of sin(x) at x=0.72:", val)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756758436107/3bda58c5-96d6-4834-a2ec-ab8fedc4cb56.png" alt="Image of code example to find the derivative of the function sin(x)" style="display:block;margin:0 auto" width="2080" height="1564" loading="lazy">

<p>The function that had the point A is called a sine wave.</p>
<p>We convert it to its derivative function. From there we have our rate of change at point 0.72.</p>
<p>When we do math by hand, <strong>we usually have many rules to convert a function to its derivative, and from these find the rate of change for a given point.</strong></p>
<p>Before seeing it, let’s look at a very simple example to understand the definition of a derivative:</p>
<p>$$\frac{d}{dx}f(x) \approx \frac{f(\textcolor{green}{x + h}) - f(\textcolor{red}{x - h})}{\textcolor{green}{x + h} - \textcolor{red}{x - h}} = \frac{f({x + h}) - f({x - h})}{2h}$$</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756767749954/87486d8c-9437-460c-b556-e9333b1590c5.png" alt="Image showing in derivative definition how each component is related visually to a line representing the rate of change" style="display:block;margin:0 auto" width="1513" height="1098" loading="lazy">

<p><code>h</code> represents a small difference.</p>
<p>The derivative is the slope of the function’s small change near a point. In other words, it’s the limit of the rate of change of a given point.</p>
<p>A simple derivative transformation might look like this one:</p>
<p>$$\frac{d}{dx}x^n = nx^{n-1}$$</p>
<p>Two examples are:</p>
<p>$$\frac{d}{dx}x^3 = 3x^2$$</p>
<p>And:</p>
<p>$$\frac{d}{dx}x^5 = 5x^4$$</p>
<p>There are many more. But we won’t go into deep detail on this topic.</p>
<h4 id="heading-where-and-why-are-derivatives-so-important">Where and why are derivatives so important?</h4>
<p>Derivatives are one of the most important math tools out there. They serve as the foundation for understanding change across nearly all fields of STEM.</p>
<p>In physics (classical mechanics), derivatives are very important to find new information that draws on information that’s already made available.</p>
<p>For example, knowing how a body's position changes over time allows us to use derivatives to find its velocity and acceleration. This is crucial for self-driving cars, trains, rockets, and more.</p>
<p>Also, derivatives are the foundation of understanding how electricity works in depth. Without derivatives, there would’ve been no electromagnetic theory. Without electromagnetic theory, modern technology would not exist.</p>
<p>In machine learning, derivatives are so important that they served to create the algorithm that is one of the most important components of ChatGPT and others AI models. (backpropagation).</p>
<p>Backpropagation is in fact so important that its creators, John Hopfield and Geoffrey Hinton, won the 2024 Nobel Prize in Physics for it.</p>
<p>Also, autonomous vehicles like Tesla and Waymo use AI models called neural networks that depend on backpropagation to work.</p>
<p>It’s awesome that a math concept created in the 17th century is now one of the foundations of the current AI revolution.</p>
<h3 id="heading-what-about-integral-calculus">What About Integral Calculus?</h3>
<p>Before explaining derivatives further, I will ask you a question:</p>
<p>How can we find the area of the below shape?</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764401826343/2583b3b0-0bcd-4204-921e-300b27c9fc3d.png" alt="Image of a finite integral of the function sin(x)" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>In other words how can we find the integral of the function in the given interval?</p>
<p>Let’s see how to do it step by step.</p>
<p>First, we’ll try using 2 rectangles to approximate the area behind the curve:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764402058848/5023772e-ed0d-4efc-a5cd-3e1a856f6d69.png" alt="Using 2 rectangles to try to find the area under the curve" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Now the area of the rectangles is 6.282573.</p>
<p>But there is still a lot of error…</p>
<p>As we can see, the left rectangle does not cover completely the curve and the right rectangle covers too much.</p>
<p>So we’ll add more smaller rectangles so that we can better approximate the curve.</p>
<p>Now let’s try using 4 rectangles:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764483444354/c06cd1c2-0f92-4728-898e-fbaf1534d57f.png" alt="Using 4 rectangles to try to find the area under the curve" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Now the area is 6.497481. But there’s still some error.</p>
<p>As we can see, the error is getting smaller. In other words, the 4 rectangles cover the area of the curve better than just the 2 rectangles. But there’s still a lot of room to make it better.</p>
<p>Let’s try using 8 rectangles:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764402069389/e9ad0576-dd9d-4535-bf3a-4c4bcd77db98.png" alt="Using 8 rectangles to try to find the area under the curve" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Now the area is 6.604935.</p>
<p>How about using 16 rectangles?</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764402075078/6ad6278f-4b71-411b-8552-2554152a04cb.png" alt="Using 16 rectangles to try to find the area under the curve" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Now the area is 6.658662.</p>
<p>Let’s try using 32 rectangles:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764402079649/4e673391-7e7a-4ca3-b07a-22508c5b058e.png" alt="Using 32 rectangles to try to find the area under the curve" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Now the area is 6.685525.</p>
<p>Now how about using 64 rectangles:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764402084920/4851d710-ff9d-4562-ba7d-9b759473f577.png" alt="Using 64 rectangles to try to find the area under the curve" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Now the area is 6.698957.</p>
<p>And using 128 rectangles:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764402090280/bd5b139c-58e1-4a7a-869d-5107b7eff345.png" alt="Using 128 rectangles to try to find the area under the curve" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Now the area is 6.705673.</p>
<p>What about using 256 rectangles:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764402098061/3ee50020-0143-42b1-aea7-8c762aa33e53.png" alt="Using 256 rectangles to try to find the area under the curve" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Now the area is 6.709031. And the error has reached 0.0000!</p>
<p>Now let’s see an animation of this:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764402052869/e9a54332-75b5-4e46-90cc-3bc09e636ad3.gif" alt="GIF animation of the rectangles from 2 to 256 to represent the finite integral" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>As you can see, we can approximate the area by having a limit to infinity to the number of rectangles to approximate the area.</p>
<p>This way, we can conclude that:</p>
<p>$$F(x) = \int_0^{3.14} f(x) , dx = \int_0^{3.14} (\sin(x) + 1.5) , dx = 6.71$$</p>
<p>This means that the area between 0 and 3.14, limited by the math equation, is 6.71!</p>
<p>Or, mathematically, the integral of f(x) in the interval 0 and 3.14 is 6.71.</p>
<h4 id="heading-where-and-how-is-this-applied">Where and how is this applied?</h4>
<p>In electrical engineering, integrals calculate total energy use in circuits by integrating power over time. For example, when designing a power supply for a device, engineers integrate the power to determine total energy costs and heat absorption requirements.</p>
<p>In other words, they see the area over time and how much power is used.</p>
<p>Let's see an example:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764832775180/911672dd-05ff-47c7-ac5f-81f4933c96ff.png" alt="Image of integral" style="display:block;margin:0 auto" width="1500" height="900" loading="lazy">

<p>Imagine that in the image above:</p>
<ul>
<li><p>The X axis can be the time in months.</p>
</li>
<li><p>The Y axis is the power used in Watts (Joules per second).</p>
</li>
</ul>
<p>We can conclude that in 3.14 months(3 months and 4 days) the total amount of energy is 6.71 watt-months.</p>
<p>Here is the code to find that out:</p>
<pre><code class="language-plaintext"># Import libraries
import numpy as np
import matplotlib.pyplot as plt

# Create Function
x = np.linspace(0, 3.14, 100)
y = np.sin(x) + 1.5

# Find the area under the function
area = np.trapezoid(y, x)

# Show the final image
plt.fill_between(x, y)
plt.title(f'Area = {area:.2f}')
plt.show()
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765435075995/defc251b-812c-44ae-8b67-9a323c0af040.png" alt="Code to find finite integral of the function sin between two points" style="display:block;margin:0 auto" width="2080" height="1384" loading="lazy">

<p>In this code, we import the libraries, create the function, and find the area and plot it.</p>
<p>We used numpy.trapezoid to find the area, because it’s a numerical approximation to quickly find the integral of a function between two x values.</p>
<p>numpy.trapezoid uses a numerical approximation method called the <strong>composite trapezoidal rule.</strong></p>
<p>The basic idea of the composite trapezoidal rule is to divide the area under the curve into many trapezoids and sum all of them.</p>
<p>If you want to learn more about this, I recommend reading the <a href="https://numpy.org/doc/stable/reference/generated/numpy.trapezoid.html">NumPy documentation on this method</a>.</p>
<p>From this value, we can convert to other units:</p>
<ul>
<li><p>52,400,000 joules</p>
</li>
<li><p>14.6 kWh</p>
</li>
</ul>
<p>By converting to other units, we can more easily compare this device with other devices and see if it obeys any technical standards and laws.</p>
<p><strong>This is a real-life application of integrals in engineering.</strong></p>
<p>In my degree, I used this a lot in classes related to power engineering. In simple words, power engineering is a subfield of electrical engineering focused on working with electricity with very high voltage values and electric motors.</p>
<p>In audio compression, the Fourier transform (built on integrals) decomposes sound waves into frequency components. MP3 encoders use this to identify and remove frequencies humans can't hear. This reduces file sizes while preserving quality.</p>
<p>Medical imaging relies on the Radon transform, which uses integrals to reconstruct 3D images from 2D X-ray projections. When you get a CT scan, the machine takes hundreds of X-ray "slices" at different angles. During this process, integrals combine "slices" into a detailed cross-sectional image of your body.</p>
<h3 id="heading-applications-in-ai-and-control-theory-calculus-in-action">Applications in AI and Control Theory: Calculus in Action</h3>
<p>Modern AI depends on derivatives that use the backpropagation algorithm.</p>
<p>When training a neural network, the system calculates partial derivatives of the error with respect to millions of parameters. This way, find out how to adjust each weight to improve performance. Without this, large language models like ChatGPT couldn't learn from data.</p>
<p>PID controllers, which stabilize the temperature in your oven or maintain altitude in aircraft autopilot systems, combine calculus ideas:</p>
<ul>
<li><p>The proportional term responds to the current error.</p>
</li>
<li><p>The integral term accumulates past errors to eliminate steady-state drift.</p>
</li>
<li><p>The derivative term predicts future trends to prevent overshooting.</p>
</li>
</ul>
<p>And these are just some of the applications of calculus!</p>
<h2 id="heading-chapter-6-probability-amp-statistics-learning-from-uncertainty">Chapter 6: Probability &amp; Statistics - Learning from Uncertainty</h2>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765002445093/b606e188-969e-49d8-9be9-9c15330a2939.jpeg" alt="Many purple dice together" style="display:block;margin:0 auto" width="6016" height="4000" loading="lazy">

<p><a href="https://www.pexels.com/photo/purple-dices-with-different-geometrical-shape-on-a-white-surface-3649115/">Photo by Armando Are</a></p>
<p>It’s thanks to probabilities and statistics that many industries have grown so much. With statistics, we can make informed decisions and optimize many different processes. With probabilities, we can understand and model uncertainty in systems and, in this way, solve or even avoid problems.</p>
<p>While you may be familiar with some of the key concepts like median and mean, we’ll start with some basics to build up your intuition on more advanced stuff like the central limit theorem, Bayes’ theorem, and Markov chains.</p>
<h3 id="heading-mean-median-mode-measuring-central-tendency">Mean, Median, Mode: Measuring Central Tendency</h3>
<p>Let's imagine you are a data scientist working in research. You’re going to work with data to optimize the output of farms in the Central Valley in California.</p>
<p>The idea is to take in a bunch of data, and by studying it, you can help farmers make better decisions.</p>
<p>Here’s the data from one year of activity:</p>
<table>
<thead>
<tr>
<th>Farm</th>
<th>Yield (tons/ha)</th>
<th>Fertilizer Used (kg/ha)</th>
<th>Rainfall (mm)</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>4.2</td>
<td>150</td>
<td>280</td>
</tr>
<tr>
<td>B</td>
<td>5.8</td>
<td>220</td>
<td>420</td>
</tr>
<tr>
<td>C</td>
<td>3.9</td>
<td>120</td>
<td>230</td>
</tr>
<tr>
<td>D</td>
<td>6.1</td>
<td>250</td>
<td>480</td>
</tr>
<tr>
<td>E</td>
<td>4.7</td>
<td>200</td>
<td>340</td>
</tr>
<tr>
<td>F</td>
<td>5.3</td>
<td>200</td>
<td>390</td>
</tr>
</tbody></table>
<p>We have 6 farms in our dataset. For each farm, we know:</p>
<ul>
<li><p>How much yield was obtained in tons per hectare</p>
</li>
<li><p>How much fertilizer was used in kilograms per hectare</p>
</li>
<li><p>How much rainfall happened during a year of activity</p>
</li>
</ul>
<p>Now, let’s answer some questions we might have about the data to understand the <strong>mean</strong>, <strong>mode</strong> and <strong>median</strong>:</p>
<h4 id="heading-1-what-is-the-average-yield-during-one-year-of-activity">1. What is the average yield during one year of activity?</h4>
<p>To find the average, we just need to sum all the yield values and divide by the number of farms. Like this:</p>
<p>$$\text{Mean} = \frac{4.2 + 5.8 + 3.9 + 6.1 + 4.7 + 5.3}{6} = \frac{30}{6} = 5$$</p>
<p>This is what is called the mean. The mean is just the sum of all values divided by how many values there are.</p>
<p>In Python, we can do the following to calculate the mean:</p>
<pre><code class="language-plaintext">def calculate_mean(values):
    return sum(values) / len(values)

# Example usage
data = [4.2, 5.8, 3.9, 6.1, 4.7, 5.3]
result = calculate_mean(data)
print(f"Mean: {result}")
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763102054838/b5619d92-95ca-4c50-bb32-39d6e8e7ba7b.png" alt="Python code in an image showing how to find the mean" style="display:block;margin:0 auto" width="2080" height="1024" loading="lazy">

<h4 id="heading-2-what-is-the-mode-of-fertilizer-used">2. What is the mode of fertilizer used?</h4>
<p>The mode is just the most popular value in a given dataset. In our case, it’s <strong>200</strong> since that’s the most common value that appears in our farm dataset.</p>
<p>In Python, we can do this to calculate the mode:</p>
<pre><code class="language-plaintext">import statistics

def calculate_mode(values):
    return statistics.mode(values)

# Example usage
data = [150, 220, 120, 250, 200, 200]
result = calculate_mode(data)
print(f"Mode: {result}")
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763102576660/3ca71e03-f762-44ad-85c3-8ccb4cb1db54.png" alt="Python code in an image showing how to find the mode" style="display:block;margin:0 auto" width="2080" height="1204" loading="lazy">

<h4 id="heading-3-what-is-the-median-of-the-yield">3. What is the median of the yield?</h4>
<p>The median is just the value in the middle of a set of numbers. If the number of elements in the list is even, we take the mean of the two middle numbers. Here are our current yield values:</p>
<p>$$4.2, 5.8, 3.9, 6.1, 4.7, 5.3$$</p>
<p>First, we sort the values:</p>
<p>$$3.9, 4.2, 4.7, 5.3, 5.8, 6.1$$</p>
<p>Since we have 6 values (even number), the median is the average of the two middle values:</p>
<p>$$\text{Median} = \frac{4.7 + 5.3}{2} = \frac{10}{2} = 5$$</p>
<p>In Python we can do this to calculate the median:</p>
<pre><code class="language-plaintext">import statistics

def calculate_median(values):
    return statistics.median(values)

# Example usage
data = [4.2, 5.8, 3.9, 6.1, 4.7, 5.3]
result = calculate_median(data)
print(f"Median: {result}")
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763102389405/52e5009b-6bc8-42c5-b8da-efe8c372fe96.png" alt="Python code in an image showing how to find the median" style="display:block;margin:0 auto" width="2080" height="1204" loading="lazy">

<h3 id="heading-variance-and-standard-deviation-measuring-spread">Variance and Standard Deviation: Measuring Spread</h3>
<p>Knowing the mean, mode, and median of data is helpful. But it’s also important to know how far away data points are from each other.</p>
<p>That’s where measures of <a href="https://en.wikipedia.org/wiki/Statistical_dispersion">dispersion</a> come in. Variance tells us, on average, how far numbers are from the mean.</p>
<p>Let’s see an example of how to calculate this.</p>
<p>Given yield data from the table:</p>
<p>$$4.2, 5.8, 3.9, 6.1, 4.7, 5.3$$</p>
<p>The first step is the calculate the mean:</p>
<p>$$\bar{x} = \frac{4.2 + 5.8 + 3.9 + 6.1 + 4.7 + 5.3}{6} = \frac{30}{6} = 5$$</p>
<p>The second step is to calculate the variance with the sample variance formula:</p>
<p>$$s^2 = \frac{\sum_{i=1}^{n}(x_i - \bar{x})^2}{n-1}$$</p>
<p>Let's apply the formula little by little to understand how it works.</p>
<p>We will first we will calculate the variance of each yield data point:</p>
<p>$$\begin{align*} (4.2 - 5.0)^2 &amp;= (-0.8)^2 = 0.64 \ (5.8 - 5.0)^2 &amp;= (0.8)^2 = 0.64 \ (3.9 - 5.0)^2 &amp;= (-1.1)^2 = 1.21 \ (6.1 - 5.0)^2 &amp;= (1.1)^2 = 1.21 \ (4.7 - 5.0)^2 &amp;= (-0.3)^2 = 0.09 \ (5.3 - 5.0)^2 &amp;= (0.3)^2 = 0.09 \end{align*}$$</p>
<p>Then we will sum all the squared differences:</p>
<p>$$\sum(x_i - \bar{x})^2 = 0.64 + 0.64 + 1.21 + 1.21 + 0.09 + 0.09 = 3.88$$</p>
<p>Now, we will finally find the variance:</p>
<p>$$s^2 = \frac{3.88}{6-1} = \frac{3.88}{5} = 0.776$$</p>
<p>The standard deviation is just the square root of the variance.</p>
<p>$$s = \sqrt{s^2} = \sqrt{0.776} \approx 0.881 tons/ha$$</p>
<p>Why is this useful?</p>
<p>It puts the spread back into the same units as the data, making it easier to interpret.</p>
<p>A small standard deviation means the data huddles close to the mean, while a large one means it’s widely scattered.</p>
<p>And here is a code example of how to calculate both:</p>
<pre><code class="language-plaintext">import statistics

def calculate_variance_and_std(values):
    variance = statistics.variance(values)
    std_dev = statistics.stdev(values)
    return variance, std_dev

# Example usage
data = [4.2, 5.8, 3.9, 6.1, 4.7, 5.3]
variance, std_dev = calculate_variance_and_std(data)
print(f"Variance: {variance}")
print(f"Standard Deviation: {std_dev}")
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763102806607/a8236667-e4b0-48a5-9171-544c4b94096e.png" alt="Python code in an image showing how to find the variance and standard deviation" style="display:block;margin:0 auto" width="2148" height="1472" loading="lazy">

<h3 id="heading-what-is-the-normal-distribution-the-bell-curve-of-life">What Is the Normal Distribution? The Bell Curve of Life</h3>
<p>The normal distribution tells us how data naturally converges around the average value. Most values are focused on the center, and extreme values are more to the edges. This creates a bell curve.</p>
<p>By understanding this distribution, we can understand other distributions and also the central limit theorem.</p>
<p>To understand what normal distribution is, let’s look at it:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763529094535/f90ffdb8-543e-4d1f-9627-335e8f356512.png" alt="Image representing the normal distribution" style="display:block;margin:0 auto" width="582" height="426" loading="lazy">

<p>The normal distribution looks like like a mountain.</p>
<p>As you can see, most values are around the mean. Also, in and around the mean is the peak. Toward the extremes, the curve gets lower and lower. This means that in the extremes there are fewer and fewer values.</p>
<p>Normal distribution also has a formula associated with it:</p>
<p>$$f(x) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left( -\frac{(x-\mu)^2}{2\sigma^2} \right)$$</p>
<p>I won’t go in depth into how the formula works here. I just want you to understand the main idea behind the concept.</p>
<p>There are many other distributions besides the normal distribution. Some of the most common are:</p>
<ul>
<li><p>Chi-squared distribution</p>
</li>
<li><p>Student’s t distribution</p>
</li>
<li><p>Bernoulli distribution</p>
</li>
<li><p>Binomial distribution</p>
</li>
<li><p>Poisson distribution</p>
</li>
</ul>
<p>Each distribution can model different events and phenomenons. For example the Chi-squared distribution is widely used to find the correlation between two phenomenons (sunburns and skin cancer, for example).</p>
<p>The Poisson distribution is also used in modeling counts of events, like the number of clients that enter a store per hour or the number of data packets that are transmitted in a Ethernet cable.</p>
<p>But it’s also possible to approximate a lot of distributions to the normal distribution using one of the most important theorems in all of mathematics: the central limit theorem. This is what we will explore next.</p>
<h3 id="heading-how-the-central-limit-theorem-helps-approximate-the-world">How the Central Limit Theorem Helps Approximate the World</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766902263857/9a03bb38-a7b9-4ef0-93f2-a7e0d80bd249.jpeg" alt="Person holding a small version of the world in their hand" style="display:block;margin:0 auto" width="5184" height="3456" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/person-holding-world-globe-facing-mountain-346885/">Porapak Apichodilok</a></p>
<p>The main idea of the central limit theorem is very simple:</p>
<ul>
<li>Most distributions can be approximated to become the normal distribution.</li>
</ul>
<p>This is just like pouring sand into a funnel. Grains may fall randomly, but over time the pile of sand will&nbsp;always begin to form the shape of a mountain.</p>
<p>This way, we can take many data points and average them. Over time, it will converge to become a normal distribution.</p>
<p>In other words, when independent random variables are all summed together, their sum tends toward a normal distribution.</p>
<p>Here is the formula:</p>
<p>$$\bar{X} \approx N\left(\mu, \frac{\sigma^2}{n}\right) \quad \text{or equivalently} \quad Z = \frac{\bar{X} - \mu}{\sigma/\sqrt{n}} \approx N(0, 1)$$</p>
<p>You don’t need to understand in depth what it means. Just understand that it’s a theorem that approximates other distributions to the normal distribution.</p>
<h4 id="heading-and-why-is-this-important">And why is this important?</h4>
<p>Because this theorem makes many billion-dollar industries possible.</p>
<p>Instead of testing every single possible scenario, we can test for a smaller amount of scenarios and assume that if it works for the smaller one, it will work for the bigger one.</p>
<p>For example, in telecommunications, instead of testing every possible phone call or data transmission, we can just test a few connections. If it works for those few connections, we can assume it will work for millions of phone and data transmissions.</p>
<p>For clinical trials, instead of testing a drug on millions of people, we can just test a smaller number of patients. If it works for a (relative) few patients, we can assume it will work on most people with the same condition.</p>
<p>Without this idea, clinical trials would not be possible. The same with telecommunications and so many other areas of engineering.</p>
<h3 id="heading-bayes-theorem-learning-from-evidence">Bayes Theorem: Learning from Evidence</h3>
<p>Now we’ll start looking at probability more in depth based on the data table we have been using.</p>
<p>Here’s the table again so that you can reference it more easily:</p>
<table>
<thead>
<tr>
<th>Farm</th>
<th>Yield (tons/ha)</th>
<th>Fertilizer Used (Kg/ha)</th>
<th>Rainfall (mm)</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>4.2</td>
<td>150</td>
<td>280</td>
</tr>
<tr>
<td>B</td>
<td>5.8</td>
<td>220</td>
<td>420</td>
</tr>
<tr>
<td>C</td>
<td>3.9</td>
<td>120</td>
<td>230</td>
</tr>
<tr>
<td>D</td>
<td>6.1</td>
<td>250</td>
<td>480</td>
</tr>
<tr>
<td>E</td>
<td>4.7</td>
<td>200</td>
<td>340</td>
</tr>
<tr>
<td>F</td>
<td>5.3</td>
<td>200</td>
<td>390</td>
</tr>
</tbody></table>
<p>Now there are a lot of ideas and formulas related to probabilities. But here, I want to explain to you the core ones that are applied in AI and give you a high-level definition of things.</p>
<p>We’ll start with conditional probability, which is foundational to understanding Bayes’ theorem. Then we’ll get to the extended Bayes’ theorem formula.</p>
<p>So, let's get started!</p>
<h4 id="heading-what-is-conditional-probability">What is Conditional Probability?</h4>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766903189931/420cc60a-71cd-4c37-ab0a-f8aebe825ca7.jpeg" alt="Image of a person playing chess with the black pieces" style="display:block;margin:0 auto" width="6000" height="4000" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/black-and-yellow-chess-pieces-3830671/">KOUSHIK BALA</a></p>
<p>Conditional probability is the probability that an event will happen given that another event has already taken place.</p>
<p>Confused? Don't worry! Let's see an example:</p>
<p>Let’s say that:</p>
<ul>
<li><p>A = Farm has rainfall above or equal 400 mm</p>
</li>
<li><p>B = Farm has a yield above or equal to 5.0 tons/ha</p>
</li>
</ul>
<p>Here is the formula for Conditional Probability:</p>
<p>$$P(A|B) = \frac{P(A \cap B)}{P(B)}$$</p>
<p>Now let’s see this formula more in detail:</p>
<p>$$P(A)$$</p>
<p>This represents the probability that a farm has rainfall above or equal to 400 mm.</p>
<p>We have 6 farms, and 2 of them (farm B and D) have a rainfall above or equal to 400 mm.</p>
<p>So, the probability that a farm has rainfall above or equal to 400 mm is:</p>
<p>$$P(A) = \frac {2}{6} = \frac {1}{3} ≈ 0.33$$</p>
<p>Now let’s see for event B:</p>
<p>$$P(B)$$</p>
<p>This represents the probability that a farm has a yield above or equal to 5.0 tons/ha.</p>
<p>We have 6 farms and 3 of them (farm B, D and F) have a yield above or equal to 5.0 tons/ha.</p>
<p>So, the probability that a farm has a yield above or equal to 5.0 tons/ha is:</p>
<p>$$P(B) = \frac {3}{6} = \frac {1}{2} = 0.5$$</p>
<p>What about if we want to see both conditions’ probabilities at the same time?</p>
<p>$$P(A \cap B)$$</p>
<p>This refers to the probability of A and B being both true.</p>
<p>In our example, in means the probability that a farm both has a rainfall above or equal to 400 mm <strong>and</strong> a yield above or equal to 5.0 tons/ha.</p>
<p>We have:</p>
<ul>
<li><p>6 farms and 2 of them (farm B and D) have a rainfall above or equal 400 mm</p>
</li>
<li><p>6 farms and 3 of them (farm B, D and F) have a yield above or equal to 5.0 tons/ha</p>
</li>
</ul>
<p>For A and B to be true, only 2 farms (farm B and D) have both conditions.</p>
<p>This way:</p>
<p>$$P(A \cap B) = \frac {2}{6} = \frac {1}{3} ≈ 0.33$$</p>
<p>Now we’re ready to find out the conditional probability:</p>
<p>$$P(A|B)$$</p>
<p>This means the probability of A, knowing that B is true.</p>
<p>In our example, we can conclude that:</p>
<p>$$P(A|B) = \frac{P(A \cap B)}{P(B)} = \frac{0.33}{0.5} = 0.66$$</p>
<p>So, the probability that a farm has rainfall above or equal 400 mm – knowing that it has a yield above or equal to 5.0 tons/ha – is 0.66</p>
<h4 id="heading-bayes-theorem">Bayes’ Theorem</h4>
<p>This is one of the most important theorems in mathematics.</p>
<p>Bayes’ theorem is a formula that tells us how to change the probability of a prediction when new verified data becomes available.</p>
<p>In other words, it’s like a rule that tells us how to update our beliefs when new evidence appears.</p>
<p>Now, based on what we already know, let’s see how Bayes’ Theorem works.</p>
<p>Here is its formula:</p>
<p>$$P(B|A) = \frac{P(A|B) \cdot P(A)}{P(B)}$$</p>
<p>Now, based on the previous values, we can very easily find the probability of B, given that A is true.</p>
<p>In other words, the probability that a farm has a yield above or equal to 5.0 tons/ha given that is has a rainfall above or equal to 400 mm.</p>
<p>Let’s find the answer:</p>
<p>$$P(B|A) = \frac{P(A|B) \cdot P(A)}{P(B)}= \frac{0.66 \cdot 0.33}{0.5}=0.44$$</p>
<p>So, the probability that a farm has a a yield above or equal to to 5.0 tons/ha, knowing it rained equal to or more than 400 mm, is 44%.</p>
<p>Now that we’ve gone through this formula step by step, hopefully it doesn’t feel as complex.</p>
<h4 id="heading-where-is-this-applied-in-real-life">Where is this applied in real life?</h4>
<p>As with many math ideas in this book, Bayes' Theorem has applications in many business sectors.</p>
<p>For example, what is the best way to make a control system for a self-driving car, robot, or really any other device?</p>
<p>One effective approach is to use a <a href="https://en.wikipedia.org/wiki/Kalman_filter">Kalman filter</a>. Kalman filters rely heavily on Bayes' Theorem to handle control systems with incomplete data.</p>
<p>Kalman filters have a lot of applications in engineering. For example, thanks to Kalman filters, commercial jets can fly safely on autopilot.</p>
<p>So as you can see, Bayes’ Theorem is the foundation of many control systems used in risky industries.</p>
<h3 id="heading-what-are-markov-models-predicting-the-next-step-one-step-at-a-time">What Are Markov Models? Predicting the Next Step, One Step at a Time</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766902389612/c80d7118-f13d-4f9b-a149-861db3f2037d.jpeg" alt="Image of the hand of a person throwing dice into the air" style="display:block;margin:0 auto" width="6000" height="4000" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/person-about-to-catch-four-dices-1111597/">lil artsy</a></p>
<p>How do you predict the future with math? Markov chains allow you to do this to a certain degree.</p>
<p>For this reason, Markov chains are widely used in science, engineering, economics, and many other areas.</p>
<p>In addition to this, Markov decision processes are a very important foundation for reinforcement learning. Reinforcement learning is a branch of AI where agents learn to make decisions by interacting with an environment to maximize rewards.</p>
<p>In this section, I’ll introduce you to Markov chains and decision processes with an analogy, a plain English explanation, and a code example.</p>
<p>If you want to dive in further, I recommend my <a href="https://www.freecodecamp.org/news/what-is-a-markov-chain/">freeCodeCamp article on the subject</a>.</p>
<h4 id="heading-markov-chain-analogy">Markov Chain Analogy</h4>
<p>Imagine that you want to predict the weather tomorrow, and it <strong>only</strong> depends on the weather today. The weather can be either sunny or rainy.</p>
<p>Here are the probabilities:</p>
<ul>
<li><p>If it's sunny today, there's an 80% chance that it will be sunny again tomorrow, and a 20% chance that it will be rainy.</p>
</li>
<li><p>If it's rainy today, there's a 50% chance that it will be sunny tomorrow, and a 50% chance that it will be rainy.</p>
</li>
</ul>
<p>In this scenario, we can predict future states of the weather based on current states using probabilities.</p>
<p>This idea of predicting the future based solely on probabilities of the present is called a Markov chain.</p>
<p>Here, the states are either sunny or rainy and the probabilities describe the chances of the weather changing based on the current state.</p>
<h4 id="heading-markov-chain-explained-in-plain-english">Markov Chain Explained in Plain English</h4>
<p>A Markov chain describes random processes where systems move between states, and a new state only depends on the current state, not on how it got there.</p>
<p>Mathematically, Markov chains are called stochastic models because they model (simulate) real life events that are random by nature (stochastic).</p>
<p>Markov chains are popular because they are easy to implement and efficient at modeling complex systems.</p>
<p>Another key advantage is their "memoryless" property. This makes it faster to run on computers, and powerful to study random processes and make predictions based on current conditions.</p>
<h4 id="heading-applications-of-markov-chains">Applications of Markov Chains</h4>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766902558494/8129d378-5cd8-4fdc-be48-8ba0a34181b7.jpeg" alt="Image of a white square with a dark star inside it, surrounded by many other dark squares" style="display:block;margin:0 auto" width="3840" height="2160" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/shapes-on-a-dark-background-25630338/">Google DeepMind</a></p>
<p>At some level, almost all real-life events are stochastic. In other words, they involve randomness and uncertainty.</p>
<p>This is exactly why they are so widely used.</p>
<p>They can predict the behavior of systems based on current conditions:</p>
<ul>
<li><p>In finance, they are used to detect changes in credit ratings for forecasting market regimes.</p>
</li>
<li><p>In genetics, they help understand how proteins change over time (which is important when studying genetic variations).</p>
</li>
</ul>
<p>These real life examples show how effective Markov chains can be used to solve real problems in different fields.</p>
<p>In AI, Markov chains are used to model an environment like a factory or home. Modeling an environment with Markov chains is called a Markov decision process.</p>
<p>Using a Markov decision process, it’s possible to use reinforcement learning to create and optimize agents to act in the environment.</p>
<p>Of course, new and better variants of the Markov decision process have appeared over the years. But the key idea here is that it is thanks to Markov decision processes that the basis for reinforcement learning exists.</p>
<p>Reinforcement learning is widely used in advertising systems, logistics, robotics, video games, and many more applications.</p>
<h4 id="heading-types-of-markov-chains">Types of Markov Chains</h4>
<p>There are many types of Markov chains. In this section, we'll only discuss the most important variants.</p>
<ol>
<li>Discrete-Time Markov Chains (DTMCs)</li>
</ol>
<p>In DTMCs, the system changes state at specific time steps. They are called discrete because the state transitions occur at distinct, separate time intervals.</p>
<p>They are used in queuing theory (study of the behavior of waiting lines), genetics, and economics because they are simple to analyze.</p>
<ol>
<li>Continuous-Time Markov Chains (CTMCs)</li>
</ol>
<p>CTMCs differ from DTMCs in that state transitions can occur at any continuous time point, not at fixed intervals.</p>
<p>This makes them stochastic models where state changes happen continuously. This is important in chemical reactions and reliability engineering.</p>
<ol>
<li>Reversible Markov Chains</li>
</ol>
<p>Reversible Markov chains are special. The process of state change is the same whether the direction is forwards or backwards, like rewinding a video and playing it again.</p>
<p>This property makes it easier to know when a system is stable and study how a system behaves over time. They are widely used in statistical physics and economics</p>
<ol>
<li>Doubly Stochastic Markov Chains</li>
</ol>
<p>Doubly stochastic Markov chains are defined by a transition probability matrix. In the matrix, the sum of the probabilities in each row and each column equals 1.</p>
<p>This means each row and each column represent a valid probability distribution. In other words, each row and column represent a list of chances for different outcomes.</p>
<p>This property is crucial in quantum computing and statistical mechanics.</p>
<p>Thanks to Doubly stochastic Markov chains, systems change in a way that preserves probabilities and symmetry, making the modeling and analysis of quantum computing systems far more accurate.</p>
<h4 id="heading-hidden-markov-chains-code-example">Hidden Markov Chains Code Example</h4>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766903059652/ad8c6509-87ae-4978-8b64-24146161d1cb.jpeg" alt="Image of glasses, a MAC computer, and blurry code in it" style="display:block;margin:0 auto" width="3353" height="2514" loading="lazy">

<p>Photo by <a href="https://www.pexels.com/photo/data-codes-through-eyeglasses-577585/">Kevin Ku</a></p>
<p>Before we jump into code examples, let’s first understand what Hidden Markov Chains are.</p>
<p>The main idea behind hidden Markov chains is to model systems that have hidden states (states for which we don’t know their values) which can only be discovered through observable events.</p>
<p>In other words, hidden Markov chains allow us to predict the behavior of a system by:</p>
<ul>
<li><p>Considering the likelihood of moving from one state to another.</p>
</li>
<li><p>Knowing the probability of observing a certain event from each state</p>
</li>
</ul>
<p>We can understand this by observing how the states change from an indirect point of view.</p>
<p>We may not know the states’ original values. But by knowing the way they change, we can predict what their values will be in the future.</p>
<p>This way, hidden Markov chains are flexible in modeling sequences, capturing both the transitions between hidden states and the observable outcomes.</p>
<p>Because of this, hidden Markov models are used in fields such as engineering, financial modeling, speech recognition, bioinformatics, and many more.</p>
<h4 id="heading-code-example">Code Example:</h4>
<p>In this code example, we’ll see a simple example with synthetic data.</p>
<p>Here is the full code:</p>
<pre><code class="language-python">import numpy as np
from hmmlearn import hmm

# Set random seed for reproducibility
np.random.seed(42)

# Define the HMM parameters
n_components = 2  # Number of states
n_features = 1    # Number of observation features

# Create a Gaussian HMM
model = hmm.GaussianHMM(n_components=n_components, covariance_type="diag")

# Define transition matrix (rows must sum to 1)
model.startprob_ = np.array([0.6, 0.4])
model.transmat_ = np.array([[0.7, 0.3],
                            [0.4, 0.6]])

# Define means and covariances for each state
model.means_ = np.array([[0.0], [3.0]])
model.covars_ = np.array([[0.5], [0.5]])

# Generate synthetic observation data
X, Z = model.sample(100)  # 100 samples

# Create a new HMM instance
new_model = hmm.GaussianHMM(n_components=n_components, covariance_type="diag", n_iter=100)

# Fit the model to the data
new_model.fit(X)

# Print the learned parameters
print("Transition matrix:")
print(new_model.transmat_)
print("Means:")
print(new_model.means_)
print("Covariances:")
print(new_model.covars_)

# Predict the hidden states for the observed data
hidden_states = new_model.predict(X)

print("Hidden states:")
print(hidden_states)
</code></pre>
<img src="https://cdn-media-0.freecodecamp.org/2024/06/1.png" alt="Full code example of HMM (Hidden Markov Chain)" style="display:block;margin:0 auto" width="2000" height="2528" loading="lazy">

<p>Now let’s break the code down block by block:</p>
<p><strong>Import libraries and set random seed:</strong></p>
<pre><code class="language-python">import numpy as np
from hmmlearn import hmm

np.random.seed(42)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763529887680/2440547e-ccf4-4067-83c2-20fafb16f045.png" alt="Code example of HMM (Hidden Markov Chain) - Import libraries and set random seed" style="display:block;margin:0 auto" width="2080" height="772" loading="lazy">

<p>In this block of code, we imported two Python libraries:</p>
<ul>
<li><p><a href="https://numpy.org/">NumPy</a>: For numerical operations.</p>
</li>
<li><p><a href="https://hmmlearn.readthedocs.io/en/latest/index.html">hmmlearn</a>: For hidden Markov model implementation.</p>
</li>
</ul>
<p>Next we defined a random seed with the NumPy library. A random seed is a value used to start a pseudorandom number generator.</p>
<p>With a fixed random seed, we can ensure that the sequence of pseudorandom numbers generated is always the same. This allows us to duplicate experiments and verify results.</p>
<p>The specific value of the seed doesn’t matter as long as it remains consistent.</p>
<p><strong>Define the HMM parameters and create a Gaussian HMM:</strong></p>
<pre><code class="language-python">n_components = 2  # Number of states
n_features = 1    # Number of observation features

model = hmm.GaussianHMM(n_components=n_components, covariance_type="diag")
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763529894398/094ac272-2788-4856-a984-b1f687464e90.png" alt="Code example of HMM (Hidden Markov Chain) - Define the HMM parameters and create a Gaussian HMM" style="display:block;margin:0 auto" width="2988" height="772" loading="lazy">

<p>In this code block, we created an HMM with two hidden states and a single observed variable.</p>
<p><code>covariance_type "diag"</code> means the matrices that represent covariance (how two variables change together) are diagonal. In other words, each row and column is assumed to be independent of the others.</p>
<p>This implies that the probability distributions of each row and column are independent of each other.</p>
<p>But there is still something strange when we defined the hidden Markov chain:</p>
<p><strong>What does “Gaussian“ mean?</strong></p>
<p>This is a very big topic in statistics, but in a few words, Markov chains can only be created when we specify the transition probabilities (chances of moving from one state to another in a Markov chain) and an initial probability distribution.</p>
<p>A Gaussian HMM assumes events are initially modeled by a Gaussian distribution, also called a normal distribution!</p>
<p>And recall, we have already seen before what a normal distribution is.</p>
<p>Here is it again:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763529107399/e51cb7a3-e751-45c7-8164-c07795ad32e1.png" alt="Code example of HMM (Hidden Markov Chain) - Image of normal distribution" style="display:block;margin:0 auto" width="582" height="426" loading="lazy">

<p>From a normal distribution and other components, we can create a hidden Markov chain. And hidden Markov chains serve as a foundation for systems that affect millions of lives.</p>
<p><strong>Define transition matrix, means, and covariances for each state:</strong></p>
<pre><code class="language-python">model.startprob_ = np.array([0.6, 0.4])
model.transmat_ = np.array([[0.7, 0.3],
                            [0.4, 0.6]])

model.means_ = np.array([[0.0], [3.0]])
model.covars_ = np.array([[0.5], [0.5]])
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763529901607/53442504-bcec-46d0-8114-fcd627947576.png" alt="Code example of HMM (Hidden Markov Chain) - Define transition matrix, means, and covariances for each state" style="display:block;margin:0 auto" width="2080" height="952" loading="lazy">

<pre><code class="language-python">model.startprob_ = np.array([0.6, 0.4])
</code></pre>
<p>This line sets the initial state probabilities for a Hidden Markov Model (HMM). It points out that there is a 60% probability of starting in state 0 and a 40% probability of starting in state 1.</p>
<pre><code class="language-python">model.transmat_ = np.array([[0.7, 0.3], [0.4, 0.6]])
</code></pre>
<p>This line of code sets the state transition probability matrix for the HMM.</p>
<p>The matrix specifies the probabilities of moving from one state to another:</p>
<ul>
<li><p>From state 0, there is a 70% chance of staying in state 0 and a 30% chance of transitioning to state 1.</p>
</li>
<li><p>From state 1, there is a 40% chance of transitioning to state 0 and a 60% chance of staying in state 1.</p>
</li>
</ul>
<pre><code class="language-python">model.means_ = np.array([[0.0], [3.0]])
</code></pre>
<p>This line sets the mean values for the observation distributions in each state.</p>
<p>It indicates that the observations are normally distributed with a mean of 0.0 in state 0 and a mean of 3.0 in state 1.</p>
<pre><code class="language-python">model.covars_ = np.array([[0.5], [0.5]])
</code></pre>
<p>This line sets the covariance values for the observation distributions in each state.</p>
<p>It specifies that the variance (covariance in this 1-dimensional case) of the observations is 0.5 for both state 0 and state 1.</p>
<p><strong>Create data, new HMM instance, and fit the model with the data:</strong></p>
<pre><code class="language-python">X, Z = model.sample(100)  # 100 samples

new_model = hmm.GaussianHMM(n_components=n_components, covariance_type="diag", n_iter=100)

new_model.fit(X)

print("Transition matrix:")
print(new_model.transmat_)
print("Means:")
print(new_model.means_)
print("Covariances:")
print(new_model.covars_)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763529906427/009804bc-40db-4979-99dd-564935b175cc.png" alt="Code example of HMM (Hidden Markov Chain) - Create data, new HMM instance, and fit the model with the data" style="display:block;margin:0 auto" width="2000" height="845" loading="lazy">

<p>In this code, we created a model with 100 samples, iterated it 100 times, and printed the new state transition matrix, means, and covariances.</p>
<p>In other words, we:</p>
<ol>
<li><p>Generated 100 samples from the original model</p>
</li>
<li><p>Fitted a new HMM to these samples.</p>
</li>
<li><p>Printed the learned parameters of this new model.</p>
</li>
</ol>
<p>What do X and Z mean here?</p>
<p>X means the observed data samples generated by the original model, while Z means the hidden state sequences corresponding to the observed data samples generated by the original model.</p>
<p>The transition matrix prints out:</p>
<pre><code class="language-python">[[0.8100804  0.1899196 ]
 [0.49398918 0.50601082]]
</code></pre>
<p>Which means that the model tends to stay in state 0 and has nearly equal chances of switching or staying when in state 1.</p>
<p>The means print out:</p>
<pre><code class="language-python">[[0.01577373]
 [3.06245496]]
</code></pre>
<p>Which means that the average observed value is approximately 0.016 in state 0 and 3.062 in state 1.</p>
<p>The covariances print out:</p>
<pre><code class="language-python">[[[0.41987084]]
 [[0.53146802]]]
</code></pre>
<p>Which means that the observed values vary by about 0.420 in state 0 and 0.531 in state 1.</p>
<p>This way, we may never know the exact values of the states, but we know their average observed value and how they vary and tend to change with each other.</p>
<p><strong>Predict the hidden states for the observed data:</strong></p>
<pre><code class="language-python">hidden_states = new_model.predict(X)

print("Hidden states:")
print(hidden_states)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763529913530/f81b3dbf-f517-4857-ac92-4732a524a621.png" alt="Code example of HMM (Hidden Markov Chain) - Predict the hidden states for the observed data" style="display:block;margin:0 auto" width="2080" height="772" loading="lazy">

<p>In this code, based on the X observed data samples, we predicted the new states of the Markov model.</p>
<p>The hidden states print out:</p>
<pre><code class="language-python">[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 1 1 0 1 1 0 1 0 0 0 1
 1 1 1 1 0 0 0 1 1 0 0 1 1 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 0 0 0 0
 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0]
</code></pre>
<p>Which means that the hidden states switch between state 0 and state 1, showing how the system changes states over time.</p>
<h3 id="heading-applications-in-ai-and-control-theory-making-decisions-under-uncertainty"><strong>Applications in AI and Control Theory: Making Decisions Under Uncertainty</strong></h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765002495967/325e5ee4-df14-4adc-a520-0764d89fe8c8.jpeg" alt="Image of many flight instruments in an airplane" style="display:block;margin:0 auto" width="5074" height="3325" loading="lazy">

<p><a href="https://www.pexels.com/photo/gray-airplane-control-panel-3402846/">Photo by capt.sopon</a></p>
<p>I have been giving you a high-level overview of the field of probabilities and statistics. As I explained before, I wanted to make the explanations simple to understand.</p>
<p>As someone with a bachelor's degree in electrical and computer engineering, I can assure you that while this chapter seems simple, in probabilities and statistics, things can get very complicated very quickly.</p>
<p>Many more concepts like:</p>
<ul>
<li><p>p-values</p>
</li>
<li><p>Advanced Monte Carlo methods</p>
</li>
<li><p>Bayesian networks</p>
</li>
<li><p>Statistical hypotheses</p>
</li>
</ul>
<p>Are not as straightforward as the ideas I’ve just told you about.</p>
<p>But as it is, probability and statistics are the starting points for making decisions where uncertainty exists in AI and control theory.</p>
<p>For example, the Bayes’ theorem, besides being the foundation of the Kalman filter, is also the foundation of many probabilistic models in the field of AI. Probabilistic models are usually used in quant firms and banks to model risk.</p>
<p>In control theory, probabilities and statistics are widely used to design robust control systems (as is the case with Kalman filters).</p>
<p>So as you can see, the application of probabilities and statistics, as with calculus and linear algebra, is the foundation for many tools that impact millions of lives and move billions of dollars in the global economy.</p>
<h2 id="heading-chapter-7-optimization-theory-teaching-machines-to-improve">Chapter 7: Optimization Theory - Teaching Machines to Improve</h2>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765002637327/9dea740c-4582-42bf-95a6-1230b7e9092d.jpeg" alt="Black and white image of many railways originating from a single one" style="display:block;margin:0 auto" width="2560" height="1920" loading="lazy">

<p><a href="https://www.pexels.com/photo/railroad-tracks-in-city-258510/">Photo by Pixabay</a></p>
<p>This is the most advanced math chapter of the book. To truly understand it, it’s very important that you’ve first read the other chapters first.</p>
<p>We’re going to examine a few machine learning methods, and I’ll show you some recipes of how machine learning is just the use of linear algebra, calculus, probabilities and statistics, and optimization theory.</p>
<p>Just like making a cake!</p>
<h3 id="heading-what-is-optimization-theory">What is Optimization Theory?</h3>
<p>In AI, optimization theory is responsible for the algorithms that optimize data-driven AI models.</p>
<p>Often, big companies invest millions in research to create or refine algorithms that make training AI models faster.</p>
<p>This way, companies save far more money than the upfront research costs when scaling to train multiple large AI models.</p>
<p>It is thanks to optimization theory that deep learning was able to scale efficiently, eventually leading to the creation of ChatGPT and many other large language models.</p>
<p><strong>But why is that?</strong></p>
<p>In all data-driven machine learning models, there is a learning phase that has to happen. That is, there’s a period where the algorithms make predictions that are not correct and then need to change some parameters to make sure the next predictions are correct – or at least closer to being correct.</p>
<p>Without optimization, machine learning algorithms don't get anywhere on their learning path to the right solution. Without optimization, they spend too much time on a learning path that won’t increase their ability to predict things the right way.</p>
<p>So, let’s start learning!</p>
<h3 id="heading-why-optimization-drives-learning-in-ai">Why Optimization Drives Learning in AI</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766903297889/4075d065-9b55-42e2-a6f6-8aae02de940f.jpeg" alt="Image of a very cute white robot" style="display:block;margin:0 auto" width="4896" height="3264" loading="lazy">

<p><a href="https://www.pexels.com/photo/high-angle-photo-of-robot-2599244/">Photo by Alex Knight</a></p>
<p>Optimization theory is the mathematical foundation that allows algorithms to improve their performance over many iterations.</p>
<p>When we combine an algorithm with a path to change its parameters to meet a certain objective (done with an optimization method), it’s called a machine learning algorithm.</p>
<p>This learning process always involves minimizing or maximizing a certain objective. For example, for many machine learning algorithms, the main objective is to minimize errors. To do this, over many iterations, the optimization methods "tells" the internal components of an algorithm what to change after receiving feedback on how well it’s performing.</p>
<p>It’s like someone first learning how to drive a car. The first few times, it may be complicated. But after a while and some practice, the driver learns how to drive properly and not make the same mistakes they once did in the past with the help of the instructor.</p>
<p>The same applies to optimization methods when optimizing algorithms.</p>
<h4 id="heading-types-of-optimization-theory-methods-in-ml-and-deep-learning">Types of Optimization Theory Methods in ML and Deep Learning</h4>
<p>The field of optimization theory is huge! Just as with many fields of mathematics, it is constantly growing every year.</p>
<p>But for the purposes of this book, there are three main categories of optimization methods:</p>
<ol>
<li><strong>First-Order Methods</strong></li>
</ol>
<p>These are the most used in deep learning and in all LLM models like Gemini, Grok, and others.</p>
<p>They are called first-order methods because they all use the first derivative of functions. The first derivative of a function measures how much a function's output changes when its input changes very little. The most widely used in deep learning are advanced variants of gradient descent.</p>
<p>While there are many variants, here are some popular examples:</p>
<ul>
<li><p>Standard batch gradient descent</p>
</li>
<li><p>Stochastic gradient descent</p>
</li>
<li><p>Mini-batch gradient descent</p>
</li>
<li><p>RMSprop</p>
</li>
<li><p><strong>Adam</strong></p>
</li>
</ul>
<p>In this chapter, we will look in depth at one of these methods called <strong>Adam</strong> (below).</p>
<ol>
<li><strong>Second-Order Methods</strong></li>
</ol>
<p>They are called second-order methods because they use information from second derivatives for better updates. There are many methods, like:</p>
<ul>
<li><p>BFGS</p>
</li>
<li><p>L-BFGS</p>
</li>
<li><p>Newton's method</p>
</li>
</ul>
<p>But these are not often used in machine and deep learning. While they optimize with fewer iterations, for the type of optimization problems algorithms in AI create (high-dimensional problems), they’re very computationally expensive.</p>
<p>So they’re not widely used like first-order optimization methods.</p>
<ol>
<li><strong>Zeroth-Order and Other Methods</strong></li>
</ol>
<p>These methods do not require derivatives to optimize algorithms. Some examples of algorithms where derivatives are not used are:</p>
<ul>
<li><p>Genetic algorithms</p>
</li>
<li><p>Dynamic programming algorithms</p>
</li>
<li><p>Particle swarm optimization methods</p>
</li>
</ul>
<p>The problem with these algorithms is that they are often very slow for many variables.</p>
<p>But in certain AI contexts, they can help optimize the architecture of deep learning models to improve AI models from an architectural point of view (instead of a parameter point of view).</p>
<h4 id="heading-how-does-optimization-theory-connect-with-linear-algebra-calculus-and-probability-and-statistics">How does optimization theory connect with linear algebra, calculus, and probability and statistics?</h4>
<p>Essentially:</p>
<ul>
<li><p>Calculus teaches you derivatives, which help you understand optimization theory.</p>
</li>
<li><p>Linear algebra teaches you matrices, which help you understand how different states relate and transform.</p>
</li>
<li><p>Probability and statistics teach you concepts like covariance and correlation, which help you understand how variables are connected with each other.</p>
</li>
</ul>
<p>This way, with linear algebra and probability and statistics, you gain the knowledge necessary to understand the algorithms. With calculus you gain the basis to understand optimization theory and how it changes certain parameters of the fundamental algorithms to minimize/maximize a certain objective.</p>
<h3 id="heading-simple-optimization-techniques-how-machines-learn-step-by-step">Simple Optimization Techniques: How Machines Learn Step by Step</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765002727335/a265939c-dea8-4763-8861-7c7a0dbe1081.jpeg" alt="Image of a Star Wars blue and white robot" style="display:block;margin:0 auto" width="4608" height="3072" loading="lazy">

<p><a href="https://www.pexels.com/photo/star-wars-r2-d2-2085831/">Photo by LJ Checo</a></p>
<p>Now, we’re going to see examples of machine learning algorithms used for optimization and deconstruct them so that you can understand how these areas of mathematics apply to them.</p>
<p>In each example, I will explain their main idea with an analogy as well as how each math area is used in each algorithm.</p>
<h4 id="heading-linear-regression">Linear Regression</h4>
<p>Imagine that you are solving a puzzle. To complete the puzzle, you need to arrange the pieces in the right design/order.</p>
<p>The same idea applies to linear regression.</p>
<p>We have matrices (linear algebra) that represent the parameters of the linear regression model and the data that flow into it.</p>
<p>And we can see over time how well the line is fitting the numbers, as well as its error (probabilities and statistics).</p>
<p>To find the best line for the linear regression, we need to know how much the parameters of the model need to change (calculus) and actually apply that change to the parameters (optimization theory).</p>
<p>This way, calculus tells us which direction to change the parameters, and optimization theory tells us how much to actually change them.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764295886800/0c5efd95-9368-4b68-b945-ff911632ca4c.gif" alt="GIF animation of linear regression working over many iterations" style="display:block;margin:0 auto" width="1037" height="856" loading="lazy">

<p>Let’s see how to code the linear regression above:</p>
<pre><code class="language-python">import numpy as np

np.random.seed(42)
X = np.linspace(0, 10, 50)
y_true = 3 * X + 2
noise = np.random.normal(0, 2, 50)
y = y_true + noise

w = 0.1 
b = 0.5
learning_rate = 0.01
iterations = [0, 1, 2, 3, 4, 5]
saved_states = []

for epoch in range(max(iterations) + 1):
    y_pred = w * X + b
    error = np.mean((y - y_pred) ** 2)
    
    if epoch in iterations:
        saved_states.append({
            'epoch': epoch,
            'w': w,
            'b': b,
            'y_pred': y_pred.copy(),
            'error': error
        })
    
    dw = -2 * np.mean(X * (y - y_pred))
    db = -2 * np.mean(y - y_pred)
    
    w = w - learning_rate * dw
    b = b - learning_rate * db
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765335029715/f77be0d9-ea3d-48f1-8cb5-f4806d1295e6.png" alt="Linear regression code example - full code example" style="display:block;margin:0 auto" width="2080" height="3272" loading="lazy">

<p>Let’s see the code block by block:</p>
<p><strong>Import library:</strong></p>
<pre><code class="language-plaintext">import numpy as np
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765335026504/94989760-bb16-4469-947e-eba7bd25b5be.png" alt="Linear regression code example - Import library" style="display:block;margin:0 auto" width="2080" height="528" loading="lazy">

<p>For this problem, we’ll import one of the most used Python libraries: NumPy (which we’ve worked with earlier in the book).</p>
<p><strong>Create data points:</strong></p>
<pre><code class="language-python">np.random.seed(42)
X = np.linspace(0, 10, 50)
y_true = 3 * X + 2
noise = np.random.normal(0, 2, 50)
y = y_true + noise
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765335038511/59e01c3d-27bf-4e6c-8500-9178f1ff569f.png" alt="Linear regression code example - Create data points" style="display:block;margin:0 auto" width="2080" height="844" loading="lazy">

<p>In this code, we define a base line that will help in generating the data points:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765336338665/caa859d0-92cb-424e-8eb2-292093c24355.png" alt="Linear regression code example - image of green base line that will help in generating the data points" style="display:block;margin:0 auto" width="753" height="565" loading="lazy">

<pre><code class="language-python">X = np.linspace(0, 10, 50)
y_true = 3 * X + 2
</code></pre>
<p>After this green line has been created, we will add noise to it to create the data points:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765336395290/80849617-9489-471d-88f6-fb2aaea5b385.png" alt="Linear regression code example - image of a green baseline that will help in generating the data points with blue dots added by introduced noise" style="display:block;margin:0 auto" width="756" height="580" loading="lazy">

<pre><code class="language-plaintext">noise = np.random.normal(0, 2, 50)
y = y_true + noise
</code></pre>
<p>This is how we defined the data points for the line dataset.</p>
<p><strong>Initializing linear regression parameters and others:</strong></p>
<pre><code class="language-python">w = 0.1 
b = 0.5
learning_rate = 0.01
iterations = [0, 1, 2, 3, 4, 5]
saved_states = []
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765335044810/72a775ee-9929-488d-b05e-ab5d32d6b031.png" alt="Linear regression code example - Initializing linear regression parameters and others" style="display:block;margin:0 auto" width="2080" height="844" loading="lazy">

<p>In this block of code, we initialize:</p>
<ul>
<li><p>Linear regression parameters: Weight to be 0.1 and bias to be 0.5</p>
</li>
<li><p>One hyperparameter: Learning rate</p>
</li>
<li><p>How many iterations we are going to use to improve the linear regression</p>
</li>
<li><p>An array called saved_states to store values to later create graphs</p>
</li>
</ul>
<p>This way, we start with this red line:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765336283612/d7bb34b5-aefc-4565-bed2-d2819bc449df.png" alt="Linear regression code example - initializing linear regression parameters and line to fit data points starting with near zero slope" style="display:block;margin:0 auto" width="735" height="575" loading="lazy">

<p><strong>Making the linear regression learn with the data:</strong></p>
<pre><code class="language-python">for epoch in range(max(iterations) + 1):
    y_pred = w * X + b
    error = np.mean((y - y_pred) ** 2)
    
    if epoch in iterations:
        saved_states.append({
            'epoch': epoch,
            'w': w,
            'b': b,
            'y_pred': y_pred.copy(),
            'error': error
        })
    
    dw = -2 * np.mean(X * (y - y_pred))
    db = -2 * np.mean(y - y_pred)
    
    w = w - learning_rate * dw
    b = b - learning_rate * db
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765335055978/2395671a-d873-4bd1-bfa0-349cc6c7be65.png" alt="Linear regression code example - Making the linear regression learn with the data" style="display:block;margin:0 auto" width="2080" height="2012" loading="lazy">

<p>It may appear complicated, but let’s see in smaller blocks:</p>
<ul>
<li>For loop</li>
</ul>
<pre><code class="language-python">for epoch in range(max(iterations) + 1):
</code></pre>
<ul>
<li>Making an prediction and seeing its error</li>
</ul>
<pre><code class="language-python">y_pred = w * X + b
error = np.mean((y - y_pred) ** 2)
</code></pre>
<p>In this block of the code, we find the values predicted for the current parameters and see its error from the real values.</p>
<ul>
<li>Saving current iteration values for future statistics</li>
</ul>
<pre><code class="language-plaintext">if epoch in iterations:
     saved_states.append({
         'epoch': epoch,
         'w': w,
         'b': b,
         'y_pred': y_pred.copy(),
         'error': error
     })
</code></pre>
<p>Here we are juts storing in the saved_states array the values of the current iteration to later compute images.</p>
<ul>
<li>Finding the gradients</li>
</ul>
<pre><code class="language-plaintext">dw = -2 * np.mean(X * (y - y_pred))
db = -2 * np.mean(y - y_pred)
</code></pre>
<p>In this block of code, we find the gradients values for the current prediction.</p>
<p>In other words, for the weight and bias, we find out how much they need to change in order to approximate better the values of the parameters to the data points.</p>
<ul>
<li>Updating the parameters values</li>
</ul>
<pre><code class="language-plaintext">w = w - learning_rate * dw
b = b - learning_rate * db
</code></pre>
<p>Finally, we update the weight and the bias with the new values so that the line better approximates the data points:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765335279159/97e4914a-ed8a-4cf7-8155-e7cde0fa7edd.gif" alt="GIF animation of linear regression working over many iterations" style="display:block;margin:0 auto" width="1037" height="856" loading="lazy">

<h4 id="heading-neural-networks">Neural Networks</h4>
<p>The same puzzle idea applies to neural networks. Neural networks are algorithmic models inspired by the brain that learn patterns from data. They are part of a machine learning field called deep learning, which uses neural networks to learn complex patterns.</p>
<p>Neural networks are important because they power modern AI applications like:</p>
<ul>
<li><p>Image recognition</p>
</li>
<li><p>Language translation</p>
</li>
<li><p>Chatbots</p>
</li>
</ul>
<p>For example, ChatGPT means Chat Generative Pre-trained Transformer. A transformer is an architecture of neural networks.</p>
<p>If you understand neural networks, you’ll understand the foundations that make ChatGPT work.</p>
<ul>
<li><p>We have matrices (linear algebra) that represent the parameters of the neural network model and the data that flow into it.</p>
</li>
<li><p>And we can know over time how well the neural network model is converging to the dataset, fitting the numbers, and see its error (probabilities and statistics).</p>
</li>
<li><p>Calculus will tell us in which direction the parameters of the neural network need to change.</p>
</li>
<li><p>Optimization theory will tell us how much they need to change.</p>
</li>
</ul>
<p>For example, this is a neural network:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764296443948/e1f46e04-d508-407c-8da6-de8e267a2ba7.png" alt="Image example of a simple neural network" style="display:block;margin:0 auto" width="655" height="391" loading="lazy">

<p>This model has in total 13 parameters:</p>
<ul>
<li><p>It has 10 lines(connections between circles). These are called weights.</p>
</li>
<li><p>It has 2 circles in the hidden layer and 1 in the output layer. Each circle has one bias.</p>
</li>
</ul>
<p><strong>Big question:</strong></p>
<p>Imagine you work in a bank. You are in charge of deciding who gets credit cards or not. For that, you create the neural network above that takes 4 inputs:</p>
<ul>
<li><p>Income</p>
</li>
<li><p>Credit score</p>
</li>
<li><p>Debt ratio</p>
</li>
<li><p>Bankruptcy history</p>
</li>
</ul>
<p>With this neural network well optimized, you can figure it out!</p>
<p>Very simply, without going into things like activation functions, the network processes the 4 inputs through its weights and biases.</p>
<p>Each connection multiplies the input by its weight. After that, each node adds its bias.</p>
<p>The final output is a number between 0 and 1:</p>
<ul>
<li><p>Numbers close to 0 mean "Not approved"</p>
</li>
<li><p>Numbers close to 1 mean "Approved"</p>
</li>
</ul>
<p>For example, a high income figure, a good credit score, and no bankruptcy history data flow through the neural networks and produce 0.92. This means that it should be approved.</p>
<p>But a low income figure with a history of bankruptcy may produce 0.15, which results in a not approved.</p>
<p>In reality, bank systems and others have neural networks that take far more well-chosen parameters and decide this automatically.</p>
<p>This is precisely how AI can be used for credit approval.</p>
<p>But a question remains: What is the best way to know how much the parameters need to change?</p>
<p>In the next part, we are going to see the most famous optimization theory algorithm that will help us decide that.</p>
<h3 id="heading-what-is-adam-the-most-popular-way-ai-models-finds-the-best-learning-path">What is Adam? The Most Popular Way AI Models Finds the Best Learning Path</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766902926221/0b6fbbee-dfda-4a55-bd5d-21215ea33074.jpeg" alt="Image of a mountain" style="display:block;margin:0 auto" width="6000" height="4000" loading="lazy">

<p><a href="https://www.pexels.com/photo/green-leafed-trees-during-fog-time-167684/">Photo by Lum3n</a></p>
<p>To optimize neural network based AI models, one of the most popular methods is called Adam, which means Adaptive Moment Estimation.</p>
<p>The paper that introduced the method is one of the most influential in the 21st century in machine learning, with thousands of citations. As with all ideas in non-symbolic AI, Adam is a mixture of different math concepts.</p>
<p>It's composed of the ideas of two other optimization methods:</p>
<ul>
<li><p>Momentum Gradient Descent: Accumulates velocity from previous gradients to move faster in consistent directions</p>
</li>
<li><p>Root Mean Square Propagation (RMSProp): Adapts learning rates based on recent gradient magnitudes</p>
</li>
</ul>
<p><strong>Let's understand them with an analogy.</strong></p>
<p>Imagine that you are riding a bicycle down a mountain little by little. You already know the direction thanks to calculus.</p>
<p>But how do you descend safely without losing control or going too slowly?</p>
<p>First, you need to build up speed gradually using past momentum. This is one of the main ideas of momentum gradient descent.</p>
<p>It's also important that you adjust your speed based on the terrain's elevation. This is the main idea of RMSProp.</p>
<p>This way, you can safely accelerate and brake appropriately.</p>
<p>When optimizing a model with Adam, this is the same concept. With Adam, we want to optimize a model in a fast and stable way.</p>
<p>The momentum gradient descent ensures the fast part, and the RMSProp ensures the secure part.</p>
<p>Nowadays, for LLMs, which once again are just very big neural network models, a variant of Adam called AdamW is more often used.</p>
<p>Now, let's build a code example of using Adam.</p>
<h4 id="heading-code-example">Code example:</h4>
<p>Using Adam, we are going to optimize this neural network based on fake data.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765148552889/28101efb-529f-4828-bb7e-adfbf5202d7f.png" alt="Image of a neural network" style="display:block;margin:0 auto" width="655" height="391" loading="lazy">

<p>It will take 4 features:</p>
<ul>
<li><p>Income</p>
</li>
<li><p>Credit score</p>
</li>
<li><p>Debt ratio</p>
</li>
<li><p>Bankruptcy history</p>
</li>
</ul>
<p>And it will tell us if we should or should not approve credit for a given person.</p>
<p>Also, since this book is an introduction to the math of AI, I will not, in this code example, discuss hyperparameter optimization, regularization techniques, and other more advanced topics and good practices.</p>
<p>I want to show why this neural network fails with this data and explain the importance of using great data.</p>
<p>Here is the whole code (and we’ll see each part more in-depth below):</p>
<pre><code class="language-python">import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader, random_split
import pytorch_lightning as pl
import matplotlib.pyplot as plt

torch.manual_seed(42)
x = torch.randn(10000, 4)
y = torch.randint(0, 2, (10000, 1)).float()
dataset = TensorDataset(x, y)

train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32)

class CreditApprovalNet(pl.LightningModule):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(4, 2)
        self.relu = nn.ReLU()
        self.output = nn.Linear(2, 1)
        self.sigmoid = nn.Sigmoid()
        self.loss_fn = nn.BCELoss()
        self.train_losses = []
    
    def forward(self, x):
        x = self.relu(self.hidden(x))
        return self.sigmoid(self.output(x))
    
    def training_step(self, batch, batch_idx):
        x, y = batch
        y_pred = self(x)
        loss = self.loss_fn(y_pred, y)
        self.log('train_loss', loss)
        self.train_losses.append(loss.item())
        return loss
    
    def configure_optimizers(self):
        return optim.Adam(self.parameters(), lr=0.0001)

model = CreditApprovalNet()
trainer = pl.Trainer(max_epochs=100, logger=False, enable_checkpointing=False)
trainer.fit(model, train_loader, val_loader)

# 
plt.plot(model.train_losses)
plt.xlabel('Training Step')
plt.ylabel('Loss')
plt.title('Credit Approval Training')
plt.grid(True, alpha=0.3)
plt.show()
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765150336432/8bb2eab8-60a1-4a01-babf-1b5b11d9187a.png" alt="Code example of training a neural network - Full code" style="display:block;margin:0 auto" width="3096" height="5252" loading="lazy">

<p>Now let’s break it down:</p>
<p><strong>Importing libraries:</strong></p>
<pre><code class="language-python">import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader, random_split
import pytorch_lightning as pl
import matplotlib.pyplot as plt
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765151014087/80097a4b-6bf2-4af0-94da-7f929cf35d2c.png" alt="Code example of training a neural network - Importing libraries" style="display:block;margin:0 auto" width="2732" height="932" loading="lazy">

<p>In this block of code, we are importing code from 3 Python libraries:</p>
<ul>
<li><p><a href="https://pytorch.org/">PyTorch</a>: One of the most popular python libraries to create new AI models in AI research</p>
</li>
<li><p><a href="https://lightning.ai/docs/pytorch/stable/">PyTorch Lightning</a>: A PyTorch wrapper that organizes training code and handles repetitive tasks automatically</p>
</li>
<li><p><a href="https://matplotlib.org/">Matplotlib</a>: One of the most popular python libraries to make graphs from data</p>
</li>
</ul>
<p><strong>Creating data:</strong></p>
<pre><code class="language-python">torch.manual_seed(42)
x = torch.randn(10000, 4)
y = torch.randint(0, 2, (10000, 1)).float()
dataset = TensorDataset(x, y)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765151040691/a2405e15-8ed0-4988-8b78-724f1bd60347.png" alt="Code example of training a neural network - creating data" style="display:block;margin:0 auto" width="2080" height="752" loading="lazy">

<p>In this part, we define a seed to make the random numbers reproducible. In other words, when we run the code many times, the same random numbers will be generated.</p>
<p>Next, we will create 10,000 applications for credit with 4 features in X and their approval decisions in y. After that, we unify everything in the dataset variable.</p>
<p>We’ll use TensorDataset because it allows us to have the 4 features and the target paired together. This way, the data does not get mixed up during training.</p>
<p><strong>Dividing data:</strong></p>
<pre><code class="language-python">train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765151063358/8325f2eb-3cf9-4900-909d-545637e20608.png" alt="Code example of training a neural network - Dividing data" style="display:block;margin:0 auto" width="2988" height="664" loading="lazy">

<p>In this block of code, we divide the data into a training dataset and a validation dataset.</p>
<p>This way, we have one dataset that’s being used to train and find the parameters while comparing results with the validation dataset.</p>
<p>As we can see, 80% of the data will be training data, and 20% of the data will be validation data.</p>
<p><strong>Loading data:</strong></p>
<pre><code class="language-python">train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765151090966/a80b2483-0bc3-4693-9b58-36765e4b2da2.png" alt="Code example of training a neural network - Loading data" style="display:block;margin:0 auto" width="2768" height="572" loading="lazy">

<p>Here, we load the data into data loaders for the AI model to use.</p>
<p>This way, we have the data automatically split into small batches and shuffled. So instead of processing all 10,000 data points, the model will be trained on one batch, improved, then another batch, then improved again, and so forth. That makes training go faster.</p>
<p><strong>Creating AI model and training process:</strong></p>
<pre><code class="language-python">class CreditApprovalNet(pl.LightningModule):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(4, 2)
        self.relu = nn.ReLU()
        self.output = nn.Linear(2, 1)
        self.sigmoid = nn.Sigmoid()
        self.loss_fn = nn.BCELoss()
        self.train_losses = []
    
    def forward(self, x):
        x = self.relu(self.hidden(x))
        return self.sigmoid(self.output(x))
    
    def training_step(self, batch, batch_idx):
        x, y = batch
        y_pred = self(x)
        loss = self.loss_fn(y_pred, y)
        self.log('train_loss', loss)
        self.train_losses.append(loss.item())
        return loss
    
    def configure_optimizers(self):
        return optim.Adam(self.parameters(), lr=0.0001)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765151116959/d75bd178-24bb-4e5d-b043-c504e280f500.png" alt="Code example of training a neural network - Creating AI model and training process" style="display:block;margin:0 auto" width="2296" height="2552" loading="lazy">

<p>This code block appears to be complicated, but let’s see each method block by block:</p>
<ul>
<li><strong>Creating the class with inheritance:</strong></li>
</ul>
<pre><code class="language-python">class CreditApprovalNet(pl.LightningModule):
</code></pre>
<p>This way, in one line, we can import everything we need to define both the model and how it will be trained.</p>
<ul>
<li><strong>init: Builds the model's layers and components:</strong></li>
</ul>
<pre><code class="language-python">    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(4, 2)
        self.relu = nn.ReLU()
        self.output = nn.Linear(2, 1)
        self.sigmoid = nn.Sigmoid()
        self.loss_fn = nn.BCELoss()
        self.train_losses = []
</code></pre>
<p>In this section of the code, we are defining the architecture of the AI model.</p>
<ul>
<li><strong>forward: Processes input data through the network to make predictions:</strong></li>
</ul>
<pre><code class="language-python">    def forward(self, x):
        x = self.relu(self.hidden(x))
        return self.sigmoid(self.output(x))
</code></pre>
<p>In this part of the code, we are defining how data will flow in the AI model based on the architecture defined.</p>
<ul>
<li><strong>training_step: Calculates loss for each batch during training:</strong></li>
</ul>
<pre><code class="language-python">    def training_step(self, batch, batch_idx):
        x, y = batch
        y_pred = self(x)
        loss = self.loss_fn(y_pred, y)
        self.log('train_loss', loss)
        self.train_losses.append(loss.item())
        return loss
</code></pre>
<p>Here, we are defining how the model will be trained. In other words, how we will find the best parameters for the model to predict well.</p>
<ul>
<li><strong>configure_optimizers: Sets the Adam optimizer with learning rate:</strong></li>
</ul>
<pre><code class="language-python">    def configure_optimizers(self):
        return optim.Adam(self.parameters(), lr=0.0001)
</code></pre>
<p>Finally, here we are defining what optimizer we are going to use to, step by step, improve the AI model parameters.</p>
<p><strong>Training AI model:</strong></p>
<pre><code class="language-python">model = CreditApprovalNet()
trainer = pl.Trainer(max_epochs=100, logger=False, enable_checkpointing=False)
trainer.fit(model, train_loader, val_loader)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765151149824/33cb6ad3-3a5d-4964-ab45-ccfd68cd0521.png" alt="Code example of training a neural network - Training AI model" style="display:block;margin:0 auto" width="3096" height="752" loading="lazy">

<p>In this block of code:</p>
<ul>
<li><p>We create the neural network model in the first line</p>
</li>
<li><p>In the 2nd and 3rd line, we prepare the training settings and train the model for 100 epochs</p>
</li>
</ul>
<p>This way, in the command line, this appears:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765152230535/3a5a6a13-12b1-4f31-8bec-cfbc830510a6.png" alt="Code example of training a neural network - training an AI model - command line showing number of layers and parameters" style="display:block;margin:0 auto" width="602" height="306" loading="lazy">

<p>The PyTorch code is essentially telling us the number of parameters in the AI model!</p>
<p><strong>Seeing results and understanding why they are not good:</strong></p>
<pre><code class="language-python">
plt.plot(model.train_losses)
plt.xlabel('Training Step')
plt.ylabel('Loss')
plt.title('Credit Approval Training')
plt.grid(True, alpha=0.3)
plt.show()
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765151210074/3cbecda5-616e-4c3b-a942-2512f81697a1.png" alt="Code example of seeing results and understanding why they are not good:" style="display:block;margin:0 auto" width="2080" height="1024" loading="lazy">

<p>Using the Matplotlib library, we plot the results:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765152336092/6cfce900-ffb6-449f-9d5d-827ff71735bb.png" alt="Code example of training a neural network - Plot the training done over time." style="display:block;margin:0 auto" width="1536" height="916" loading="lazy">

<p><strong>The AI model is not converging.</strong></p>
<p>We can see that because the loss is nearly 0.7 (70%) over time.</p>
<p>The main reason the model is not converging well is that there is little to no relationship between the 4 features and the target variable.</p>
<p>In other words, we do not have good data.</p>
<p>The code works perfectly, but this shows the <strong>most important rule in machine learning</strong>: when we create an AI model, the MOST IMPORTANT thing is data.</p>
<p>It does not matter if you use a simple linear regression or a neural network based on transformers or whatever. If you do not have high quality data, the model is not going to perform well.</p>
<p>Even if we use a good optimizer, like Adam, it will not solve the data problem.</p>
<p><strong>Next steps: Common beginner mistakes</strong></p>
<p>I also wrote this exact code example to show you something very important: neural networks are not always the best models to use.</p>
<p>This is a very common beginner mistake. You may start with neural networks for everything, when often machine learning methods with little data preprocessing do the job well.</p>
<p>For this type of problem, the solution is to first try machine learning methods instead of going to neural networks.</p>
<p>There are many reasons for this, but the main ones are:</p>
<ul>
<li><p>Machine learning methods are simpler and often quicker to train than neural networks</p>
</li>
<li><p>Machine learning methods are simpler to understand how they make decisions. In other words, we can understand how the machine learning model thought to make a prediction.</p>
</li>
<li><p>With computational learning, we can guess with certain machine learning models how well they will predict in the future and provide theoretical guarantees about their performance.</p>
</li>
</ul>
<p>Another common mistake is not dividing the data.</p>
<p>To simplify, I created only a training and validation division of the data</p>
<p>In a serious project, you should always divide it into 3 parts: training, validation, and testing.</p>
<p>With training, you create the model. With validation, you test the model based on the data it was trained on. With the test dataset part, you compare if the loss of the model is similar to the validation or different. If they are very different, it means that the AI model converged to the validation dataset but not the test dataset.</p>
<p>I challenge you to think further about how you could improve this code and to try to make the synthetic data more correlated in order to improve its quality.</p>
<h3 id="heading-applications-in-ai-and-control-theory-of-optimization-theory">Applications in AI and Control Theory of&nbsp;Optimization Theory</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765002780396/5aaf78bb-a06a-4d09-b681-a604a323d430.jpeg" alt="Image of a robot hand touching a web" style="display:block;margin:0 auto" width="6177" height="4118" loading="lazy">

<p><a href="https://www.pexels.com/photo/robot-pointing-on-a-wall-8386440/">Photo by Tara Winstead</a></p>
<p>Optimization theory serves as the engine behind AI and control systems that shape our lives.</p>
<p>From unlocking your phone with facial recognition to autopilot systems guiding planes, optimization algorithms are constantly at work.</p>
<p>When you ask ChatGPT a question, optimization theory determines the values of billions of parameters during training.</p>
<p>The same is true for all other LLMs like Gemini, Claude, Grok, DeepSeek, and others. All of them contain millions and millions of parameters. The only way to find the best combination of the parameters to achieve a certain objective is with optimization theory.</p>
<p>In control theory, many systems like Model Predictive Control (MPC) and adaptive control systems only work thanks to optimization methods that balance how internal components of the control system should work together</p>
<p>Beyond training neural networks and controlling physical systems, optimization powers recommendation systems, resource allocation, and so many other systems.</p>
<p>Some examples are:</p>
<ul>
<li><p>Netflix movie recommendation system</p>
</li>
<li><p>Spotify's song suggestion system</p>
</li>
<li><p>Google systems to reduce data center cooling costs</p>
</li>
<li><p>Quantitative trading firms high-frequency trading systems</p>
</li>
</ul>
<p>To end this final chapter, I’ll share this:</p>
<p><strong>It is optimization theory that makes math models into AI models that impact the lives of millions worldwide.</strong></p>
<h2 id="heading-conclusion-where-mathematics-and-ai-meet">Conclusion: Where Mathematics and AI Meet</h2>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765002962447/8cdbc79a-5d9c-406d-bad6-2f2e49566b36.jpeg" alt="Pyramids of Egypt with a camel sitting" style="display:block;margin:0 auto" width="5563" height="3709" loading="lazy">

<p><a href="https://www.pexels.com/photo/a-camel-lying-in-the-ground-on-the-background-of-pyramids-18991572/">Photo by AXP Photography</a></p>
<p>When ancient civilizations first carved numbers into clay tablets, they likely didn’t imagine that these symbols would one day allow humanity to create the scientific, technological, and medical marvels we have today.</p>
<p>Yet here we are.</p>
<p>We’re in an era where mathematical ideas developed over many centuries – even millennia – have converged to create artificial intelligence.</p>
<p>Throughout this book, we've traced a path from the most basic math concepts to the cutting edge of AI. We have seen how:</p>
<ul>
<li><p>Matrices compress complex systems into simple forms</p>
</li>
<li><p>Derivatives measure change</p>
</li>
<li><p>Probability helps us navigate uncertainty</p>
</li>
<li><p>Optimization guides algorithms toward better decisions to learn faster.</p>
</li>
</ul>
<p>We’ve also learned how each math field has helped create tools that are responsible for many of the things we take for granted today.</p>
<h3 id="heading-mathematics-is-the-foundation-of-ai">Mathematics is the Foundation of AI</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766902825228/e14431de-44da-4e26-a646-5d277c16b073.jpeg" alt="Board with an integral equation in it" style="display:block;margin:0 auto" width="5060" height="3358" loading="lazy">

<p><a href="https://www.pexels.com/photo/person-writing-on-white-board-3781338/">Photo by Jeswin Thomas</a></p>
<p>Always remember this: AI is not pure magic or a "being" we don't understand. It’s just the combination of many math ideas working very well together.</p>
<p>When you ask a question of ChatGPT or any other LLM, it generates a response. And in the process of generating that response, there are millions of matrix multiplications happening in seconds.</p>
<p>Or, for example, when a self-driving car decides to stop moving because it’s coming up to a crosswalk, there are a lot of math computations (related to calculus and probability and statistics) working very fast to ensure safety.</p>
<p>The great thing about mathematics is that it’s a common, standard language of logic. No matter the backgrounds of people or where they were born, a derivative will always be a derivative, and the same thing goes for key AI concepts.</p>
<p>This way, scientists and engineers worldwide can improve each other's work because everyone understands the same language.</p>
<h3 id="heading-the-future-on-device-ai-and-the-democratization-of-ai">The Future: On Device AI and the Democratization of AI</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766902760109/02b3f00d-a8df-4546-bf41-c1791cdc5f18.jpeg" alt="Image of an chip" style="display:block;margin:0 auto" width="4500" height="3000" loading="lazy">

<p><a href="https://www.pexels.com/photo/abstract-image-of-a-microchip-with-heatmap-colors-28767589/">Photo by Steve Johnson</a></p>
<p>One shift happening now is the move toward edge AI. That is, AI that runs locally on your phone, computer, and really in all your devices (rather than in distant data centers).</p>
<p>This way, privacy is guaranteed because it runs locally. Waiting times for AI models decrease because no data needs to be sent. AI can be used offline, and costs decrease.</p>
<p>And what about the massive data centers being built all over the world? Those will be used for more products that will help improve the lives of millions of people.</p>
<p>As AI becomes more local and more processing power is freed up from big data centers, new AI innovations will appear, and more benefits will come.</p>
<p>The same way that in the past century every computer got its own networking chip, every device will have (and in some cases, already has) AI accelerators.</p>
<p>And much of it will be thanks to the math you learned in this book.</p>
<h3 id="heading-final-reflections">Final Reflections</h3>
<p>Isaac Newton wrote, "If I have seen further, it is by standing on the shoulders of giants."</p>
<p>Every algorithm you use, every model you train, and every new theorem you learn stands on centuries of mathematical progress. You now stand on those same shoulders of these giants!</p>
<p>Thank you for reading, and happy learning.</p>
<p>Here’s the full book <a href="https://github.com/tiagomonteiro0715/The-Math-Behind-Artificial-Intelligence-A-Guide-to-AI-Foundations">GitHub repository with all the code</a>.</p>
<h3 id="heading-acknowledgements">Acknowledgements</h3>
<p>First and foremost, I would like to thank <a href="https://www.linkedin.com/in/guilherme-mendes-a416b7206/"><strong>Guilherme Mendes</strong></a>, currently a Master’s student in Electrical and Computer Engineering at NOVA University, specializing in Control Theory, for reviewing the mathematical and technical details of the 1st version of this book.</p>
<p>I am also grateful to the organizations that gave me opportunities to grow:</p>
<ul>
<li><p><a href="https://www.fct.unl.pt/en">NOVA School of Science and Technology</a></p>
</li>
<li><p><a href="https://ieee-pt.org/">IEEE Portugal Section</a></p>
</li>
<li><p><a href="https://www.siliconvalleyfellowship.com/">Silicon Valley Fellowship</a></p>
</li>
<li><p><a href="https://www.northeastern.edu/">Northeastern University</a></p>
</li>
<li><p><a href="https://best.eu.org/index.jsp">BEST and BEST Almada</a></p>
</li>
<li><p><a href="https://magmastudio.pt/">Magma Studio</a></p>
</li>
</ul>
<p>A special thank you goes to the freeCodeCamp editorial team**,** especially Abigail Rennemeyer, for their patience and for reviewing every chapter of this book.</p>
<p>I would also like to thank all the professors at NOVA FCT who have taught and guided me throughout my academic journey, especially those from the Department of Electrical and Computer Engineering.</p>
<h2 id="heading-about-the-author">About the Author</h2>
<ul>
<li><p>LinkedIn: <a href="https://www.linkedin.com/in/tiago-monteiro-/">https://www.linkedin.com/in/tiago-monteiro-</a></p>
</li>
<li><p>GitHub: <a href="https://github.com/tiagomonteiro0715">https://github.com/tiagomonteiro0715</a></p>
</li>
<li><p>Email: <a href="mailto:monteiro.t@northeastern.edu">monteiro.t@northeastern.edu</a></p>
</li>
</ul>
<p>My name is Tiago Monteiro, and I’m now pursuing a master's degree in Artificial Intelligence at Northeastern University in the Silicon Valley Campus (San Jose) on a merit-based scholarship.</p>
<p>I’m not from the United States. I am a Portuguese national, born and raised in the district of Lisbon.</p>
<p>In Portugal, I completed a bachelor's degree in electrical and computer engineering at NOVA University, one of Portugal's best universities.</p>
<p>I have authored over 20 articles for freeCodeCamp, which have accumulated more than 240,000 views over the years, and completed the Deep Learning Specialization from DeepLearningAI, taught by Andrew Ng.</p>
<p>Also, I had the privilege of participating in the winter 2025 batch of the renowned Silicon Valley Fellowship program.</p>
<h4 id="heading-why-did-i-choose-electrical-and-computer-engineering">Why did I choose electrical and computer engineering?</h4>
<p>After finishing the Portuguese national math exam in 12th grade, I chose Electrical and Computer Engineering (ECE) to challenge myself and learn new math on my own.</p>
<p>The ECE degree combined:</p>
<ul>
<li><p>Advanced Mathematics</p>
</li>
<li><p>Programming (from Assembly to Python)</p>
</li>
<li><p>Physics (classical mechanics, electromagnetism)</p>
</li>
</ul>
<h4 id="heading-what-did-i-gain-exactly">What did I gain exactly?</h4>
<p>I mastered the skills needed to quickly understand AI research, particularly after completing Andrew Ng's Deep Learning Specialization.</p>
<p>In Portugal, I also studied advanced STEM areas including, for example:</p>
<ul>
<li><p><strong>Partial Differential Equations</strong> for modeling real-world phenomena</p>
</li>
<li><p><strong>Harmonic analysis</strong> (Fourier/Laplace transforms) for signal processing and alternative problem perspectives</p>
</li>
<li><p><strong>Complex analysis</strong> involving derivatives and integrals in the complex domain</p>
</li>
<li><p><strong>Numerical methods</strong> for approximating mathematical solutions computationally</p>
</li>
<li><p><strong>Signal/control theory</strong> for ensuring system stability in dynamic environments</p>
</li>
<li><p><strong>Physics classes</strong> in classical mechanics and electromagnetism fundamentals</p>
</li>
</ul>
<p>While not directly applied to AI, these studies enhanced my systems thinking and ability to independently learn complex STEM concepts.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The State of Bluetooth in 2025: What’s New, What’s Possible, and How to Use It ]]>
                </title>
                <description>
                    <![CDATA[ Introduction: Why Bluetooth Still Matters You probably don’t even think about Bluetooth anymore. It’s just there, quietly doing its job every single day. It’s what keeps your earbuds connected, your smartwatch synced, your car infotainment system tal... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-state-of-bluetooth-whats-new-whats-possible-and-how-to-use-it/</link>
                <guid isPermaLink="false">690e2801500cb51e735b5a9c</guid>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ connectivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Fri, 07 Nov 2025 17:10:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762533537259/3f9dec8a-690b-4fd8-a0a7-8e6b2667e55c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-introduction-why-bluetooth-still-matters">Introduction: Why Bluetooth Still Matters</h2>
<p>You probably don’t even think about Bluetooth anymore. It’s just there, quietly doing its job every single day. It’s what keeps your earbuds connected, your smartwatch synced, your car infotainment system talking to your phone, and your warehouse sensors awake and reporting.</p>
<p>The funny thing is, while most of us stopped paying attention, Bluetooth never stopped evolving. It just kept getting smarter.</p>
<p>Now it’s 2025, and Bluetooth has grown into something much bigger than a way to stream music. It has become a core ecosystem that connects nearly everything around us. From audio gear and IoT sensors to industrial automation and secure building access, Bluetooth is everywhere.</p>
<p>The newest versions, Bluetooth 5.4 and 6.0, completely redefine how devices talk to each other. We’re talking about encrypted broadcasts, smarter advertising, centimeter-level distance tracking, and a level of scalability that feels closer to magic than engineering.</p>
<p>In this article, we’ll take a tour through the newest Bluetooth technologies and see what’s happening under the hood. You’ll get a feel for what’s new, how these features work in real projects, and how developers can actually take advantage of them.</p>
<p>Grab your favorite dev board, and let’s dive in.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-the-evolution-from-classic-to-low-energy-to-60">The Evolution: From Classic to Low Energy to 6.0</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deep-dive-technical-enhancements">Deep Dive: Technical Enhancements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-world-applications-in-2025">Real-World Applications in 2025</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-developer-guide-getting-started">Developer Guide: Getting Started</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-challenges-and-trade-offs">Challenges and Trade-Offs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-road-ahead-bluetooth-61-and-beyond">The Road Ahead: Bluetooth 6.1 and Beyond</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-the-evolution-from-classic-to-low-energy-to-60">The Evolution — From Classic to Low Energy to 6.0</h2>
<p>If you’ve been around Bluetooth for a while, you probably remember the early days when pairing a headset felt like solving a riddle. Back then, Bluetooth Classic ruled the scene, focused mainly on short-range audio and simple data links. Over the years, though, the story changed completely.</p>
<p>Today, Bluetooth has transformed from a simple cable-replacement protocol into a flexible framework for everything from earbuds to industrial robots. Each new version added fresh layers of intelligence, speed, and energy efficiency. The table below gives a quick timeline of how that evolution unfolded.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Version</strong></td><td><strong>Year</strong></td><td><strong>Key Features</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>2.0 + EDR</strong></td><td>2004</td><td>Faster data rate (3 Mbps)</td></tr>
<tr>
<td><strong>4.0</strong></td><td>2010</td><td>BLE introduced for low power</td></tr>
<tr>
<td><strong>5.0</strong></td><td>2016</td><td>2× speed, 4× range, 8× advertising capacity</td></tr>
<tr>
<td><strong>5.1</strong></td><td>2019</td><td>Direction Finding (AoA/AoD)</td></tr>
<tr>
<td><strong>5.2</strong></td><td>2020</td><td>LE Audio / Isochronous Channels</td></tr>
<tr>
<td><strong>5.3 – 5.4</strong></td><td>2021-2023</td><td>Encrypted Advertising, PAwR</td></tr>
<tr>
<td><strong>6.0</strong></td><td>2024</td><td>Channel Sounding, Decision-Based Filtering</td></tr>
<tr>
<td><strong>6.1</strong></td><td>2025</td><td>Minor updates on efficiency &amp; range</td></tr>
</tbody>
</table>
</div><p>The journey tells a bigger story. What started as a way to connect two devices for audio has turned into a foundation for massive IoT networks. Each revision introduced smarter physical layers, better energy profiles, and new roles for devices that once had very limited capability.</p>
<p><img src="https://www.mdpi.com/sensors/sensors-25-00996/article_deploy/html/images/sensors-25-00996-g003.png" alt="Sensors 25 00996 g003" width="600" height="400" loading="lazy"></p>
<p><em>Source: MDPI Sensors (2025), Bluetooth Core Specification Summary.</em></p>
<p>Above figure provides a visual snapshot of how Bluetooth has evolved across its major versions. It shows a clear chronological progression of features—from the launch of Bluetooth Low Energy (BLE) in version 4.0, to the introduction of secure connections, long-range PHYs, and direction-finding capabilities, all the way up to the latest breakthroughs like Channel Sounding and decision-based filtering in Bluetooth 6.0. The color-coded timeline highlights how each version refined both the physical and logical layers of communication, gradually expanding Bluetooth’s reach from simple peripherals to high-precision industrial and spatial applications. In essence, it maps Bluetooth’s transformation from a short-range wireless cable into a sophisticated, context-aware connectivity fabric that underpins modern audio, IoT, and automation ecosystems.</p>
<p>If you zoom out a bit, you’ll notice a clear pattern: Bluetooth keeps finding new neighborhoods to move into. From cars and headphones to factories and hospitals, the technology now feels less like a cable replacement and more like an invisible nervous system for the modern world.</p>
<h2 id="heading-whats-new-in-bluetooth-54-and-60">What’s New in Bluetooth 5.4 and 6.0</h2>
<p>When you hear that Bluetooth has a “new version,” it’s easy to shrug it off. After all, your headphones already work, right? But the jump from 5.3 to 5.4 and then 6.0 isn’t just a tiny step. It’s more like Bluetooth quietly taking on Wi-Fi’s job in certain places and pulling it off surprisingly well.</p>
<p>Let’s break it down by version so it’s easier to see what’s going on.</p>
<h3 id="heading-bluetooth-54-building-the-iot-backbone"><strong>Bluetooth 5.4: Building the IoT Backbone</strong></h3>
<p>This release might not have made flashy headlines, but engineers loved it. It focuses on letting thousands of low-power devices talk to a single gateway without choking the airwaves.</p>
<p>Let’s look at some of the key features and why they matter:</p>
<h4 id="heading-periodic-advertising-with-responses-pawr">Periodic Advertising with Responses (PAwR)</h4>
<p>Think of it as Bluetooth’s group chat for sensors. Devices can broadcast messages and still get short replies, all without the full connection setup that usually drains batteries. It’s perfect for large sensor networks like smart warehouses or retail stores with electronic shelf labels.</p>
<p><img src="https://devzone.nordicsemi.com/resized-image/__size/1296x466/__key/communityserver-blogs-components-weblogfiles/00-00-00-00-28/7607.pastedimage1698068932789v3.png" alt="Periodic Advertising with Responses (PAwR): A practical guide - Software -  nRF Connect SDK guides - Nordic DevZone" width="1296" height="464" loading="lazy"></p>
<p>Source: Nordic Semiconductor Developer Zone (2024)</p>
<p>Above diagram illustrates the timing structure of Bluetooth 5.4’s Periodic Advertising with Responses (PAwR) mechanism. Along the horizontal axis, it shows a repeating sequence of PAwR events separated by the overall <em>periodic advertising interval</em>. Within each PAwR event are several <em>subevents</em>—labeled #0, #1, #2, #3, and so on—each representing a defined window of time during which specific sensors or devices are allowed to communicate. The figure highlights that every subevent occurs at a fixed <em>periodic advertising subevent interval</em>, meaning devices can wake up only during their assigned slot, transmit or receive data, and then return to sleep. This predictable scheduling dramatically reduces radio collisions and power consumption, allowing a single gateway to coordinate thousands of low-power nodes such as electronic shelf labels or environmental sensors within a shared advertising cycle.</p>
<h4 id="heading-encrypted-advertising-data">Encrypted Advertising Data</h4>
<p>Broadcasts used to be open for anyone to sniff. Now they can be private and secure, which is essential for medical monitors and retail beacons carrying sensitive info.</p>
<p><img src="https://www.raytac.com/upload/news_m/ceac2577d996eda7e0197ec0ff7be7c8.png" alt="Raytac Corporation 勁達國際電子股份有限公司" width="600" height="400" loading="lazy"></p>
<p>Source: Raytac Technology (2024)</p>
<p>Above diagram breaks down the structure of the <strong>Encrypted Data Advertising Data (AD) type</strong> introduced in Bluetooth 5.4. It visually shows how encrypted advertising payloads are organized within a broadcast packet. At the top, the full advertising payload is represented, which includes the length (Len), Encrypted Data (ED Tag), and flags. Inside the encrypted section, the fields are expanded to show the <strong>Randomizer</strong>, <strong>Payload</strong>, and <strong>Message Integrity Check (MIC)</strong>. The payload itself may contain various elements such as the <strong>Electronic Shelf Label (ESL) Tag</strong>, <strong>ESL Payload</strong>, <strong>Local Name (LN Tag)</strong>, or other advertising segments. The color-coding differentiates which parts are encrypted (blue) versus unencrypted (gray or yellow), highlighting how Bluetooth 5.4 secures sensitive data while retaining key advertising identifiers for discovery. This layout helps engineers understand where encryption is applied within the advertising packet and how privacy and integrity are preserved during broadcast communication.</p>
<h4 id="heading-electronic-shelf-labels-esl-support">Electronic Shelf Labels (ESL) Support</h4>
<p>Bluetooth 5.4 was practically written with supermarkets in mind. Imagine thousands of digital price tags blinking updates at once, all running for months on coin-cell batteries.</p>
<p><img src="https://www.danidatasystems.com/wp-content/uploads/2023/10/ESL-work.jpg" alt="Electronic Shelf Label - Dani Data Systems India Pvt. Ltd." width="600" height="400" loading="lazy"></p>
<p>Source: Dani Data Systems (2023)</p>
<p>Above image illustrates the working architecture of a Bluetooth-based <strong>Electronic Shelf Label (ESL)</strong> system. On the left, a computer running ESL management software is shown, which allows retail staff to configure product data, prices, and display templates. The software communicates over a TCP/IP network connection with a <strong>Base Station</strong> positioned in the center of the diagram. This base station acts as a Bluetooth gateway, wirelessly transmitting the updated price and product information to numerous shelf labels throughout the store. On the right, a digital ESL display is shown featuring a price tag for a product labeled “Kaju Katali,” complete with product details, QR codes for mobile payments, and expiry dates. The blue wireless icon between the base station and ESL tag symbolizes Bluetooth communication. Together, the components demonstrate how Bluetooth 5.4 enables synchronized, low-power, and remotely managed price updates across thousands of retail shelf labels.</p>
<p>In short, 5.4 was the version that said, “Sure, we can handle massive IoT networks.”</p>
<h3 id="heading-bluetooth-60-the-game-changer"><strong>Bluetooth 6.0: The Game Changer</strong></h3>
<p>Bluetooth 6.0 feels like the point where the technology matured from “just wireless” into “smart wireless.” This version brings features that start blurring the line between Bluetooth and more advanced location systems.</p>
<h4 id="heading-channel-sounding">Channel Sounding</h4>
<p>This is a big one. Instead of using signal strength (which can be messy), Bluetooth 6.0 measures phase differences in radio waves to calculate distance. That means centimeter-level accuracy (enough for digital keys), precise tracking, and even AR interactions.</p>
<p><img src="https://amaldev.blog/wp-content/uploads/2025/01/BLEChannelSounding.png" alt="TechExplained: Bluetooth Channel Sounding - The Tech Blog" width="600" height="400" loading="lazy"></p>
<p>Source: Bluetooth SIG (2025)</p>
<p>Above image explains the concept of <strong>Bluetooth Channel Sounding</strong>, a new feature introduced in Bluetooth 6.0 that enables precise distance measurement between devices. The top half of the diagram compares three levels of spatial awareness—presence detection through advertising, coarse distance estimation using RSSI (Received Signal Strength Indicator), and fine-grained ranging achieved with Channel Sounding. It also shows how Direction Finding complements these methods by determining angular orientation. On the left, a smartphone (the initiator) communicates with a smart lock (the reflector), demonstrating how Bluetooth can estimate distance and direction simultaneously. The bottom portion visualizes two measurement techniques. The <strong>Phase-Based Ranging</strong> chart shows how two signals of different frequencies experience measurable phase shifts that correspond to distance. The <strong>Round Trip Time (RTT)</strong> diagram on the right depicts packets traveling between the initiator and reflector, with the elapsed time between transmission and reception used to calculate distance. Together, these visuals illustrate how Bluetooth 6.0 achieves centimeter-level accuracy for applications like digital keys, indoor navigation, and spatially aware IoT systems.</p>
<h4 id="heading-decision-based-advertising-filtering">Decision-Based Advertising Filtering</h4>
<p>Bluetooth devices now decide which advertisements to process and which to ignore, saving both power and bandwidth. It’s like teaching scanners to pay attention only when it’s worth it.</p>
<p><img src="https://www.bluetooth.com/wp-content/uploads/2024/08/Bluetooth_Core_6_Figure_11.png" alt="Bluetooth_Core_6_Figure_11" width="9534" height="6609" loading="lazy"></p>
<p>Source: Bluetooth SIG (2024)</p>
<p>Above diagram illustrates the architecture of <strong>Decision-Based Advertising Filtering</strong>, a new Bluetooth 6.0 feature that allows observers to process only relevant broadcast packets, reducing power consumption and unnecessary data handling. The figure depicts two parallel host–controller stacks: the <strong>Observer</strong> on the left and the <strong>Advertiser</strong> on the right. Each side includes an Application layer, Host Controller Interface (HCI), and Controller. On the advertiser side, the application generates <strong>Decision Data</strong> that passes through the HCI to the controller’s advertising engine, where it’s embedded into extended advertising packets known as <em>Decision PDUs</em>. On the observer side, incoming advertising data passes through a <strong>Filter Policy</strong> module in the controller, which selects or rejects packets according to preconfigured decision criteria before forwarding only the relevant <strong>Advertising Reports</strong> to the host application. Blue arrows show configuration and report flows, while the yellow HCI bands highlight the host–controller boundary. Together, the components show how Bluetooth 6.0 empowers devices to make intelligent, context-aware filtering decisions at the controller level, improving efficiency in dense radio environments.</p>
<h4 id="heading-advertiser-monitoring">Advertiser Monitoring</h4>
<p>Gateways can now keep tabs on the state of nearby advertisers, which is critical when hundreds of devices are broadcasting at once.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762412492836/223de7c4-c659-4c43-8514-8a505070a129.png" alt="223de7c4-c659-4c43-8514-8a505070a129" class="image--center mx-auto" width="3218" height="1906" loading="lazy"></p>
<p>Source: Bluetooth SIG (2024)</p>
<p>Above image depicts the fundamental interaction between two Bluetooth Low Energy (BLE) device roles — <strong>advertising</strong> and <strong>scanning</strong>. On the left, a smartphone icon represents the scanning device, which actively listens for nearby Bluetooth broadcasts. On the right, a small sensor or tag icon represents the advertising device, periodically transmitting packets that announce its presence, capabilities, or data updates. Blue concentric rings radiate outward from both devices, symbolizing the propagation of radio signals and the overlapping wireless coverage area where scanning and advertising events intersect. The minimalist design highlights the asymmetric nature of BLE communication: the advertiser periodically transmits small bursts of information, while the scanner remains receptive to detect, filter, or connect with those broadcasts — forming the foundation of all Bluetooth discovery, pairing, and data exchange processes.</p>
<h4 id="heading-negotiable-inter-frame-spacing">Negotiable Inter-Frame Spacing</h4>
<p>This lets devices adjust timing between packets to improve throughput and avoid interference in noisy environments.</p>
<p><img src="https://www.bluetooth.com/wp-content/uploads/2024/08/Bluetooth_Core_6_Figure_26.png" alt="Bluetooth_Core_6_Figure_26" width="2263" height="739" loading="lazy"></p>
<p>Source: Bluetooth SIG (2024)</p>
<p>Above image illustrates the concept of <strong>Negotiable Inter-Frame Spacing (IFS)</strong> in Bluetooth 6.0, which optimizes the timing between consecutive data packets to improve throughput and reduce interference. The diagram shows two sequences of communication between a <strong>Central (C)</strong> and a <strong>Peripheral (P)</strong> device, represented as alternating blue (C→P) and green (P→C) data blocks. In the first sequence, packets are transmitted with a short, fixed inter-frame spacing labeled <strong>T_IFS</strong>, showing a rapid exchange of packets within a connection event. The second sequence demonstrates the enhanced Bluetooth 6.0 model, where devices can dynamically negotiate a longer spacing interval — indicated by the notation “≥ T_IFS” — to accommodate environmental conditions, controller processing delays, or congestion. The red horizontal arrows mark the overall connection event duration, while the vertical lines represent packet boundaries. By allowing flexible timing adjustments between frames, Bluetooth 6.0 reduces airtime collisions and improves coexistence with other 2.4 GHz systems, particularly in dense or interference-prone environments.</p>
<h4 id="heading-isoal-enhancements">ISOAL Enhancements</h4>
<p>Audio data, especially LE Audio streams, now move more smoothly thanks to improved support for large frames.</p>
<p><img src="https://www.bluetooth.com/wp-content/uploads/2024/08/Bluetooth_Core_6_Figure_22.png" alt="Bluetooth_Core_6_Figure_22" width="2000" height="1606" loading="lazy"></p>
<p>Source: Bluetooth SIG (2024)</p>
<p>Above diagram illustrates the internal data flow and timing structure of the <strong>Isochronous Adaptation Layer (ISOAL)</strong> in Bluetooth 5.2 and later, which supports synchronized audio and data transmission over LE Isochronous Channels. The figure is divided into three main sections: the <strong>Upper Layer</strong>, the <strong>ISOAL</strong>, and the <strong>Link Layer</strong>. At the top, the Upper Layer handles isochronous data in the form of Service Data Units (SDUs). Within the ISOAL layer, SDUs undergo several key processes — <strong>Fragmentation</strong> and <strong>Segmentation</strong> break data into smaller protocol units, while <strong>Recombination</strong> and <strong>Reassembly</strong> merge received fragments back into complete SDUs. Two important timing-related steps occur in parallel: the <strong>Inclusion of Timing Offsets</strong>, which ensures proper packet scheduling, and <strong>Timing Reconstruction</strong>, which synchronizes the playback or reassembly timing for received streams. These operations produce either <strong>Framed</strong> or <strong>Unframed Protocol Data Units (PDUs)</strong>, which are then passed to the <strong>Link Layer</strong> at the bottom for transmission over the <strong>Isochronous Stream</strong>. The diagram highlights how ISOAL bridges the upper and lower layers, managing timing alignment and packet structure to deliver low-latency, synchronized LE Audio or data streams across multiple devices.</p>
<p>When you put all that together, Bluetooth 6.0 starts looking a lot like Ultra-Wideband in terms of precision, but without needing new hardware. It’s faster, smarter, and somehow more polite on the airwaves.</p>
<h2 id="heading-deep-dive-technical-enhancements">Deep Dive — Technical Enhancements</h2>
<p>This is where Bluetooth starts to feel less like “a thing your phone just does” and more like a finely tuned machine. The new specs add layers of intelligence that make devices more aware of distance, timing, and context. It’s the kind of stuff that gets engineers grinning because it solves problems we’ve all quietly complained about for years.</p>
<p>Let’s walk through a few of the most important ones.</p>
<h3 id="heading-channel-sounding-and-distance-awareness">Channel Sounding and Distance Awareness</h3>
<p>If you’ve ever used RSSI values to guess how far a device is, you know how unpredictable it can be. RSSI measures how strong the signal sounds, not where it actually came from. A wall, a metal shelf, even a human body can distort it. Channel Sounding solves this by looking at <em>phase</em> instead of strength.</p>
<p>Here’s the idea: two devices exchange carefully crafted packets at multiple frequencies. Each frequency behaves like a different musical note. When those notes reach the receiver, their phases – how the peaks and troughs line up – shift slightly depending on distance. The receiver compares the original and received phases, then crunches the math:</p>
<p>$$[ \text{Distance} = \frac{c \times \Delta \phi}{2\pi f} ]$$</p><p>where:</p>
<ul>
<li><p>( c ) is the speed of light,</p>
</li>
<li><p>( \Delta \phi ) is the phase shift,</p>
</li>
<li><p>( f ) is the carrier frequency.</p>
</li>
</ul>
<p>This approach allows for precise distance measurement, achieving accuracy down to a few centimeters by analyzing the phase differences of signals received at multiple frequencies.</p>
<p>That level of precision changes the game. Cars can unlock automatically only when you’re physically beside the door. Smart-building systems can tell which room you’re standing in. Mixed-reality headsets can map your movements without extra sensors.</p>
<p>From a development point of view, you’ll need hardware that supports the new Channel Sounding PHY. Nordic’s nRF54 and Silicon Labs’ BG24 families already expose low-level APIs for it. Expect to work closer to the metal than usual: calibration, antenna diversity, and clock stability all affect measurement accuracy. It’s worth the effort, though. Few wireless technologies can deliver this precision without expensive dedicated hardware.</p>
<h3 id="heading-periodic-advertising-with-responses-pawr-1">Periodic Advertising with Responses (PAwR)</h3>
<p>For years, BLE advertising worked like shouting into a room and hoping someone heard you. The moment you wanted a reply, you had to form a full connection. That model doesn’t scale when you have ten-thousand tiny sensors that each wake up once a minute.</p>
<p>PAwR flips the model. Think of it as a scheduled town-hall meeting. A coordinator (the gateway) broadcasts a timeline. Each sensor has a reserved time slot to respond within that cycle. Because everyone speaks only during their assigned moment, collisions disappear and energy use plummets.</p>
<p>In practice, this lets one gateway handle tens of thousands of devices without ever maintaining individual connections. Supermarkets use it for electronic shelf labels that update prices in seconds. Factories deploy it for environmental sensors that report temperature and vibration periodically.</p>
<p>Developers integrating PAwR will notice that it doesn’t replace connections, it complements them. You can still open a full GATT session for configuration, but routine data flows through lightweight PAwR exchanges. Most modern SDKs, including Zephyr and ESP-IDF, now include PAwR APIs under their extended-advertising modules.</p>
<h3 id="heading-isochronous-audio-channels-amp-le-audio">Isochronous Audio Channels &amp; LE Audio</h3>
<p>Bluetooth’s original audio stack wasn’t built for what we expect today. It was designed for single-stream mono headsets, not for multi-earbud synchronized audio or broadcast systems. Isochronous Channels fix that by ensuring that every packet in a group shares the same clock reference.</p>
<p>Two modes exist:</p>
<ul>
<li><p><strong>Connected ISO Streams (CIS)</strong> handle one-to-one cases like stereo earbuds</p>
</li>
<li><p><strong>Broadcast ISO Streams (BIS)</strong> allow a transmitter to serve an unlimited audience, such as a gym or theater.</p>
</li>
</ul>
<p>Both rely on the <strong>LC3 codec</strong>, which delivers near-lossless sound at roughly half the bandwidth of SBC.</p>
<p>In real life, this means earbuds that stay perfectly in sync even if you walk between interference zones, hearing aids that seamlessly share the same stream, and venues that broadcast announcements directly to phones without dedicated receivers. Android 14 and iOS 17 have already exposed system-level LE Audio support, so app developers can finally build end-user experiences without vendor-specific hacks.</p>
<p>For embedded engineers, implementing LE Audio requires controller firmware that supports ISOAL (Isochronous Adaptation Layer) and host-side stack integration. Nordic, Qualcomm, and Dialog all provide reference implementations, but testing is key – timing drift between links can break audio quality faster than you might expect.</p>
<h3 id="heading-power-amp-efficiency-improvements">Power &amp; Efficiency Improvements</h3>
<p>Battery life has always been Bluetooth’s quiet superpower, and version 6.0 tightens the screws even more. Rather than one big change, it’s a collection of small ones that add up.</p>
<p>Negotiable inter-frame spacing lets devices adjust the delay between packets, smoothing out contention when the air is busy. Controllers now enter deeper sleep states automatically, waking only when the radio truly needs them. Smarter advertising filters prevent devices from wasting time processing duplicates, and new firmware offloads push repetitive tasks (like connection parameter updates) away from the CPU.</p>
<p>When engineers combine all these tricks, the numbers look impressive: about a ten to twenty percent battery gain in dense environments. That might not sound huge, but for a coin-cell tag meant to last three years, it’s the difference between hitting the spec or not.</p>
<h3 id="heading-security-amp-privacy-upgrades">Security &amp; Privacy Upgrades</h3>
<p>With great connectivity comes great responsibility. Bluetooth now sits at the heart of cars, locks, and health monitors, which makes security non-negotiable. The new stack finally treats it as a first-class citizen.</p>
<p>LE Secure Connections with numeric comparison are now standard, encrypted advertising data hides sensitive broadcasts, and Channel Sounding even enables distance-based access control. In plain language, a device can now verify that you’re physically nearby before sharing keys or unlocking features.</p>
<p>Still, protocol features alone aren’t enough. Developers should rotate identity-resolving keys regularly, invalidate old bonds on firmware updates, and avoid static passkeys. Security in Bluetooth is like security anywhere else: the spec provides the locks, but you’re responsible for turning the key.</p>
<p>Together, these improvements make Bluetooth feel more alive, more aware, and more efficient. The stack now senses distance, saves power, and defends privacy without breaking backward compatibility. It’s a quiet revolution hidden inside chips that most people never think about, yet it’s shaping how billions of devices will talk to each other over the next decade.</p>
<h2 id="heading-real-world-applications-in-2025">Real-World Applications in 2025</h2>
<p>It’s one thing to read about Channel Sounding or PAwR in a spec sheet. It’s another to see these features come alive in everyday products.</p>
<p>Bluetooth has quietly spread into nearly every corner of our lives, from the shelves of supermarkets to the dashboards of cars. By 2025, it’s no exaggeration to call it the most widely deployed wireless ecosystem on Earth.</p>
<p>Let’s look at where these new capabilities are already making an impact.</p>
<h3 id="heading-retail-electronic-shelf-labels-and-smart-inventory">Retail: Electronic Shelf Labels and Smart Inventory</h3>
<p>Walk into a modern supermarket in 2025 and look closely at the price tags. They aren’t paper anymore. Those little digital labels, changing prices in real time, are powered by Bluetooth 5.4’s <strong>Periodic Advertising with Responses (PAwR)</strong> and <strong>Encrypted Advertising Data</strong>.</p>
<p>Each label is a low-power sensor node, quietly listening for broadcast schedules from a gateway mounted above the aisle. When it’s their turn, the tags wake up, confirm their slot, and update the display – all in milliseconds and without forming a traditional Bluetooth connection. The result is a network of tens of thousands of nodes that consumes almost no energy.</p>
<p>Security matters here too. Encrypted advertising ensures that a competing store or curious shopper can’t sniff price data or inject bogus updates. Everything runs on coin-cell batteries that last several years, which saves retailers both time and maintenance costs.</p>
<h3 id="heading-smart-home-context-aware-unlocking-and-personal-audio">Smart Home: Context-Aware Unlocking and Personal Audio</h3>
<p>If you’ve ever fumbled with your phone to unlock a smart door, Bluetooth 6.0 might finally fix that. <strong>Channel Sounding</strong> makes proximity detection precise enough to trust. The system can tell whether you’re standing by the door or ten meters away in the driveway. Only when you’re truly within range does it trigger the unlock sequence.</p>
<p>The same precision is reshaping personal audio. Imagine walking from your living room to the kitchen and having your smart speaker hand off the song to your earbuds automatically. That’s <strong>LE Audio</strong> working behind the scenes with isochronous channels, keeping streams perfectly aligned across multiple endpoints. It feels invisible, which is exactly how good technology should feel.</p>
<h3 id="heading-healthcare-reliable-secure-patient-monitoring">Healthcare: Reliable, Secure Patient Monitoring</h3>
<p>Hospitals have long relied on wireless monitors, but interference and power limits made them tricky. With PAwR, a single access point can now coordinate thousands of small sensors that track vitals like heart rate, oxygen, or temperature. These devices communicate in brief, deterministic bursts, avoiding packet collisions that used to plague dense wards.</p>
<p>Privacy is critical, and that’s where encrypted advertising comes in. Patient identifiers and medical readings remain hidden even in broadcast form. Channel Sounding adds another layer by confirming proximity: only readers within a safe range can retrieve sensitive data.</p>
<p>Combined, these features help reduce misreads and protect patient confidentiality without adding extra setup steps for clinicians.</p>
<h3 id="heading-industry-40-asset-tracking-and-condition-monitoring">Industry 4.0: Asset Tracking and Condition Monitoring</h3>
<p>Factories and warehouses are some of Bluetooth’s biggest playgrounds. Equipment now comes with embedded Bluetooth 6.0 modules that use Channel Sounding for ultra-precise location tracking. Pallets, forklifts, and tools broadcast their position continuously, helping logistics teams know what’s where, all the time.</p>
<p>Add PAwR, and you get scalable telemetry for thousands of machines. Vibration, temperature, or pressure data can flow reliably to a single gateway. Some systems even combine Bluetooth data with AI analytics to predict failures before they happen. The ability to measure distance accurately also helps robots navigate crowded spaces safely.</p>
<h3 id="heading-wearables-hearables-ar-glasses-and-health-bands">Wearables: Hearables, AR Glasses, and Health Bands</h3>
<p>Wearable devices benefit more than any other category. Modern earbuds use LE Audio to keep both sides synchronized, whether you’re streaming a movie or on a call. Hearing aids receive direct broadcast audio in public venues without special adapters.</p>
<p>AR glasses are an even bigger frontier. They use Channel Sounding to sense spatial relationships between the wearer, nearby devices, and the environment. That allows context-aware overlays – navigation cues, health metrics, or notifications – that appear exactly where they make sense. Bluetooth’s low-power model keeps these systems lightweight enough to run all day.</p>
<h3 id="heading-automotive-digital-keys-and-vehicle-telemetry">Automotive: Digital Keys and Vehicle Telemetry</h3>
<p>Cars are fast becoming Bluetooth hubs on wheels. <strong>Digital Key Systems</strong> already use Bluetooth 6.0’s distance measurement to ensure you’re physically close before unlocking or starting the engine. It’s safer than older RSSI-based solutions that could be fooled by signal relays.</p>
<p>Onboard sensors rely on secure connections and encrypted advertising to stream data about tire pressure, cabin air quality, or driver posture. Maintenance centers can access diagnostic data automatically when a car pulls in, without plugging in a cable. In short, Bluetooth has quietly replaced several proprietary systems once needed for short-range communication inside vehicles.</p>
<h3 id="heading-the-big-picture">The Big Picture</h3>
<p>What’s striking is how flexible Bluetooth has become. The same fundamental protocol now powers medical wearables, industrial sensors, and entertainment systems. Each use case leans on a different mix of features – PAwR for scale, Channel Sounding for precision, LE Audio for experience, and encrypted advertising for privacy – but the foundation is consistent.</p>
<p>It’s this adaptability that explains why Bluetooth continues to thrive despite predictions of its demise. Rather than being replaced by Wi-Fi or UWB, it’s learning from them, borrowing their strengths, and finding new roles.</p>
<h2 id="heading-developer-guide-getting-started">Developer Guide — Getting Started</h2>
<p>Bluetooth 6.0 may sound futuristic, but the good news is that you don’t have to wait years to use it. Most of the new features are already landing in chipsets, SDKs, and development kits. If you’re an engineer or hobbyist itching to get your hands dirty, this section walks you through what to look for, how to get started, and a few pitfalls to watch out for along the way.</p>
<h3 id="heading-picking-the-right-chipset">Picking the Right Chipset</h3>
<p>The chipset you choose sets the tone for your entire project. If you’re building something simple, like a smart tag or sensor, you’ll want a microcontroller with integrated Bluetooth Low Energy and minimal power draw. But if you plan to experiment with Channel Sounding, LE Audio, or PAwR, you’ll need silicon that explicitly supports Bluetooth 5.4 or 6.0 features.</p>
<p>Current front-runners include the Nordic nRF54 series, Dialog DA1470x, and Silicon Labs BG24 family. These are developer-friendly chips with mature SDKs and good documentation. They also have flexible radio subsystems, which matter a lot when you’re testing features like Channel Sounding that depend on timing and signal stability.</p>
<p>A small tip from experience: always check the vendor’s firmware release notes. Some Bluetooth 6.0-capable chips still require you to enable experimental PHY layers or SDK flags to unlock certain features.</p>
<h3 id="heading-sdk-and-stack-support">SDK and Stack Support</h3>
<p>Once you’ve got your hardware, the next step is setting up your software stack. Most Bluetooth development happens through vendor SDKs or open platforms like Zephyr RTOS, ESP-IDF, or BlueZ on Linux.</p>
<p>If you’re targeting embedded systems, Zephyr is a great place to start. It’s modular, stable, and already includes PAwR and LE Audio APIs under its <code>bt_le_ext_adv</code> and <code>iso</code> modules. Silicon Labs’ Simplicity Studio also has strong tooling around Bluetooth mesh and PAwR.</p>
<p>On desktop or gateway platforms, Linux’s BlueZ stack supports extended advertising and secure connections out of the box, and work is underway to integrate Channel Sounding support via new HCI commands.</p>
<p>Always verify that your controller firmware is up to date before testing new features. Many “missing API” errors trace back to outdated controller images that don’t yet recognize the relevant HCI opcodes.</p>
<h3 id="heading-advertising-strategy">Advertising Strategy</h3>
<p>Advertising is still the heartbeat of Bluetooth, and now it’s smarter than ever. Here’s a simple example of setting up extended advertising in C-style pseudocode:</p>
<pre><code class="lang-plaintext">ble_adv_params params = {
    .type = ADV_EXTENDED,
    .interval = 160,   // 100ms interval
    .tx_power = 0      // default transmit power
};

ble_set_adv_data(payload, sizeof(payload));
ble_start_advertising(&amp;params);
</code></pre>
<p>Above pseudocode demonstrates how a Bluetooth Low Energy (BLE) device initializes and starts broadcasting advertisements so that nearby devices can discover it. The first block defines a structure named <code>ble_adv_params</code>, which contains the configuration settings for advertising. The <code>.type = ADV_EXTENDED</code> field specifies that the device will use <strong>Extended Advertising</strong>, a feature introduced in Bluetooth 5.0 that allows for larger payloads, better range, and the use of secondary channels beyond the traditional 31-byte limit of legacy advertising. The <code>.interval = 160</code> value sets the advertising interval, expressed in Bluetooth time units of 0.625 milliseconds, meaning the device transmits an advertising packet every 100 milliseconds—frequent enough for responsive discovery without excessive power consumption. The <code>.tx_power = 0</code> field sets the transmit power level to 0 dBm, which is the default radio output power and provides a balanced tradeoff between energy efficiency and signal range. After configuring the parameters, the function <code>ble_set_adv_data(payload, sizeof(payload))</code> loads the advertising data—typically a collection of identifiers such as the device name, UUIDs for available services, manufacturer-specific data, or other Bluetooth advertising fields. This is the information that other devices see when scanning nearby. Finally, <code>ble_start_advertising(&amp;params)</code> begins the actual transmission, instructing the BLE controller to start broadcasting the configured data on the standard advertising channels (37, 38, and 39). Once active, the device periodically transmits these packets until advertising is stopped manually or a central device establishes a connection. In essence, this short snippet encapsulates the three fundamental steps of BLE advertising: configuring the radio parameters, defining the broadcast data, and enabling the periodic advertisements that make the device visible to others.</p>
<p>This kind of setup works well for extended advertising and PAwR broadcast scheduling. When designing your advertising payloads, remember that the new encrypted format (introduced in 5.4) limits available space slightly, so plan for tighter data packing if you’re including custom fields.</p>
<p>If you’re building something that needs connection-less updates (like a sensor network), use PAwR or periodic advertising. For interactive applications, where you expect users to connect via a phone or hub, extended connectable advertising remains the right choice.</p>
<h3 id="heading-connection-optimization">Connection Optimization</h3>
<p>Tuning connection parameters is half art, half science. You’ll often find yourself trading latency for battery life. For streaming or LE Audio applications, intervals around <strong>24–40 ms</strong> usually strike the right balance. For sensors or telemetry, you can stretch that interval out to save energy.</p>
<p>Sniff subrating is another underrated feature. It lets a peripheral sleep longer while maintaining an active connection, reducing energy use without affecting responsiveness too much.</p>
<p>If you’re testing with multiple devices, simulate busy airspace using tools like Ellisys Bluetooth Analyzer or the nRF Sniffer. This helps uncover timing issues or packet loss that might only show up in dense radio environments.</p>
<h3 id="heading-power-testing">Power Testing</h3>
<p>It’s easy to claim low power on paper – but proving it is another story. Use your dev kit’s current profiling tools to measure sleep and active currents under different intervals and PHY settings.</p>
<p>Run your firmware through long-duration tests in “noisy” airspace – meaning multiple other Bluetooth or Wi-Fi devices nearby. The goal is to see how your firmware reacts when packet retries or interference increase. Sometimes small timing tweaks can make big differences in battery life.</p>
<p>As a general rule, always start testing on the <strong>1M PHY</strong> (the default) and only switch to <strong>2M</strong> for high-throughput use cases like audio. Long-range modes can be valuable for IoT, but remember that higher receive sensitivity often costs extra current.</p>
<h3 id="heading-security-checklist">Security Checklist</h3>
<p>Bluetooth 6.0 brings much stronger built-in security, but you’ll still need to wire it up correctly. Make sure to:</p>
<ul>
<li><p>Use LE Secure Connections instead of legacy pairing.</p>
</li>
<li><p>Rotate Identity Resolving Keys (IRK) periodically.</p>
</li>
<li><p>Encrypt advertising payloads whenever transmitting private or medical data.</p>
</li>
<li><p>Handle key storage securely on your device, preferably with hardware-backed encryption or secure flash.</p>
</li>
</ul>
<p>Also, watch for privacy gaps in the connection flow. Even encrypted devices can leak identity information if they reuse resolvable addresses or fail to clear bonds properly on reset.</p>
<h3 id="heading-backward-compatibility">Backward Compatibility</h3>
<p>Real-world devices won’t all jump to Bluetooth 6.0 overnight. Your code should always detect peer capabilities and fall back gracefully. The HCI layer provides read commands that reveal which features the remote device supports.</p>
<p>For example, if Channel Sounding isn’t available, default to RSSI-based proximity or skip distance-based logic entirely. Similarly, if LE Audio isn’t supported, fall back to classic A2DP. Designing your firmware with this flexibility keeps your products compatible with millions of existing devices.</p>
<h3 id="heading-testing-and-certification">Testing and Certification</h3>
<p>Once your prototype works, you’ll need to qualify it through the <strong>Bluetooth SIG Qualification Program</strong>. This process ensures your product complies with the spec and interoperates correctly with others. It might sound intimidating, but many vendors offer pre-qualified modules or test reports you can reuse to simplify the paperwork.</p>
<p>For debugging and validation, tools like the Ellisys Bluetooth Analyzer, Frontline BPA 600, or Nordic’s nRF Sniffer can capture over-the-air traffic and help verify packet sequences, timing, and encryption states.</p>
<p>Bluetooth development can be frustrating at first, as there’s lots of acronyms, layers, and hidden dependencies. But once you start seeing the system as a living conversation between devices, it clicks. The more you experiment with advertising intervals, connection timing, and PHY modes, the more you’ll appreciate how elegant and flexible the stack really is.</p>
<p>If you’ve ever wanted to build something that talks wirelessly and runs for months on a battery, this is your moment. The ecosystem has matured, the tools are ready, and the possibilities keep expanding.</p>
<h2 id="heading-challenges-amp-trade-offs">Challenges &amp; Trade-Offs</h2>
<p>It’s tempting to think of Bluetooth 6.0 as flawless – after all, it’s faster, more efficient, and infinitely scalable. But like every engineering advancement, it comes with trade-offs. Real deployments reveal quirks that the spec sheets don’t mention, and knowing these early can save hours of debugging (and a few late-night rants).</p>
<h3 id="heading-adoption-lag">Adoption Lag</h3>
<p>Every new Bluetooth spec sounds exciting on paper until you realize the hardware for it isn’t widely available yet. Controller vendors take time to integrate the latest features, and phone or OS support can lag by a year or two. You might find yourself reading about Channel Sounding or PAwR in the core spec, only to discover that your development kit still marks them as “experimental.”</p>
<p>This is normal. The Bluetooth SIG’s release cadence moves faster than the hardware ecosystem can follow. The best strategy is to design firmware that detects capabilities dynamically. Build your code to gracefully fall back to 5.0 or 5.2 modes if 6.0 features are missing. That way your product ships today, but it’s ready for the future.</p>
<h3 id="heading-environmental-interference">Environmental Interference</h3>
<p>Bluetooth still lives in the 2.4 GHz band, the same noisy neighborhood as Wi-Fi, microwaves, and countless IoT gadgets. In factories or dense apartments, you’ll see interference spikes that cause packet loss or delay. Even with adaptive frequency hopping, performance can dip if too many radios are talking at once.</p>
<p>Developers need to test in real environments, not just in quiet labs. Use spectrum analyzers or sniffers to visualize congestion. Adjust transmit power, advertisement intervals, or even antenna orientation to mitigate problems. Remember, radio design is part science, part art. Sometimes moving a board trace by a centimeter makes more difference than rewriting code.</p>
<h3 id="heading-power-versus-performance">Power Versus Performance</h3>
<p>Every Bluetooth generation tries to squeeze more precision and range out of roughly the same battery. Channel Sounding and high-speed PHY modes improve accuracy and throughput, but they also increase radio-on time and CPU load. You gain features but spend more energy to get them.</p>
<p>There’s no universal setting that fits all products. A hearing aid might value low latency over battery life, while a temperature sensor prioritizes sleeping as much as possible. Developers must tune intervals, transmission power, and frame spacing through measurement, not guesswork. The good news is that once you find the sweet spot, Bluetooth tends to be remarkably stable over long periods.</p>
<h3 id="heading-security-configuration">Security Configuration</h3>
<p>Modern Bluetooth has excellent built-in security, but only if you use it correctly. Misconfigured advertising, static passkeys, or unrotated identity keys can still leak information. Even encrypted advertising won’t help if your firmware accidentally reuses session data.</p>
<p>The takeaway: don’t assume “secure by default.” Review every pairing and bonding flow, handle key rotation on firmware updates, and wipe old bonds when a user resets the device. The protocol gives you powerful locks, but it’s up to you to actually turn the key.</p>
<h3 id="heading-software-complexity">Software Complexity</h3>
<p>The Bluetooth stack is getting heavier. Features like PAwR, Channel Sounding, and Isochronous Audio require new roles, new timing models, and new APIs. Developers who are used to simple GATT servers now have to think about scheduling, synchronization, and PHY coordination. Testing these features on multi-role devices can be especially tricky, since a single controller might handle multiple concurrent roles (central, peripheral, broadcaster, and observer).</p>
<p>If you’re working on an embedded platform, modular firmware design becomes essential. Split radio control, connection management, and application logic into distinct layers. It’s easier to debug timing bugs when your architecture mirrors the Bluetooth stack’s separation of concerns.</p>
<h3 id="heading-fragmentation">Fragmentation</h3>
<p>Perhaps the most persistent challenge is fragmentation. Not every OEM implements the same subset of features, and some phones or chipsets may partially support a spec while skipping optional sections. Developers quickly learn that “Bluetooth 6.0” can mean slightly different things depending on the vendor.</p>
<p>The practical fix is to build flexibility into your software. Use feature discovery at runtime, keep your update mechanism ready for OTA patches, and enable configuration flags for new features so you can toggle them per device. Testing across diverse hardware early in the process pays off more than any elegant design decision later.</p>
<h3 id="heading-mitigation-and-mindset">Mitigation and Mindset</h3>
<p>Despite these challenges, none of them are deal-breakers. They’re simply part of building systems that live in the real world. Think modular, plan for gradual rollouts, and make firmware updates painless. Bluetooth’s backward compatibility means your device won’t become obsolete overnight, and your users benefit from improvements as the ecosystem matures.</p>
<p>In short, the trick isn’t avoiding the trade-offs but managing them. When you design with flexibility, Bluetooth 6.0 becomes less of a moving target and more of a living platform that grows alongside your product.</p>
<h2 id="heading-the-road-ahead-bluetooth-61-and-beyond">The Road Ahead — Bluetooth 6.1 and Beyond</h2>
<p>If Bluetooth 6.0 was about awareness – knowing distance, filtering intelligently, and optimizing communication – then Bluetooth 6.1 is about refinement. It takes what already works and polishes it into something smoother, faster, and a little more elegant. It’s not a revolution, but it’s an important step in Bluetooth’s quiet transformation from a “wireless cable” into a context-aware network fabric for everyday devices.</p>
<h3 id="heading-small-tweaks-big-payoffs">Small Tweaks, Big Payoffs</h3>
<p>Bluetooth 6.1 focuses on tightening the nuts and bolts rather than changing the whole machine. The update improves Channel Sounding accuracy, enhances advertising efficiency, and introduces a few quality-of-life adjustments to make device coordination easier.</p>
<p>That might sound minor, but it matters. Channel Sounding, for example, becomes more reliable when multiple reflections or obstacles exist. In indoor positioning systems like airports, hospitals, or museums, even a five percent improvement in accuracy can reduce false detections by a wide margin. Advertising refinements also make large IoT deployments more predictable, allowing gateways to manage high-density environments with less radio congestion.</p>
<p>In simpler terms: Bluetooth 6.1 is like a firmware tune-up for an already fast car. You may not notice it day to day, but under heavy load, it performs better and wastes less energy.</p>
<h3 id="heading-the-emerging-themes">The Emerging Themes</h3>
<p>Beyond the incremental fixes, the Bluetooth community is thinking much bigger. The next few years will likely focus on four major themes: energy harvesting, AI-assisted radio optimization, hybrid positioning, and context-aware security.</p>
<h4 id="heading-1-energy-harvesting-bluetooth-devices">1. Energy-Harvesting Bluetooth Devices</h4>
<p>We’re starting to see early prototypes of Bluetooth tags and sensors that run entirely on harvested energy – light, heat, or vibration – with no traditional battery. This ties into the push for maintenance-free IoT devices, especially in logistics and environmental sensing. Future specifications will refine ultra-low-duty-cycle communication patterns to support these “powerless” nodes.</p>
<h4 id="heading-2-ai-driven-radio-management">2. AI-Driven Radio Management</h4>
<p>Imagine a Bluetooth controller that dynamically learns the noise profile of its environment and adjusts its PHY, transmit power, or advertising timing in real time. Instead of a static table of parameters, AI models embedded in the firmware could predict interference and choose the best channel map automatically. It sounds futuristic, but chipmakers are already experimenting with machine learning cores in connectivity modules.</p>
<h4 id="heading-3-cross-technology-fusion-bluetooth-wi-fi-uwb">3. Cross-Technology Fusion (Bluetooth + Wi-Fi + UWB)</h4>
<p>The border between short-range radios is blurring. Some systems already use Wi-Fi for throughput, Bluetooth for discovery, and UWB for pinpoint accuracy – all orchestrated by a single chipset. The goal isn’t to replace one with another but to fuse them, creating hybrid location frameworks that are more reliable than any single technology. Bluetooth’s Channel Sounding makes it a perfect partner in this mix.</p>
<h4 id="heading-4-context-aware-security">4. Context-Aware Security</h4>
<p>Future Bluetooth devices might decide access rights based not just on identity, but on <em>context</em>. For example, your smartwatch could unlock your laptop only if it detects that you’re sitting still and within one meter. That combination of motion, distance, and authentication could drastically reduce spoofing or relay attacks.</p>
<h3 id="heading-the-quiet-backbone-of-connectivity">The Quiet Backbone of Connectivity</h3>
<p>What’s fascinating about Bluetooth’s evolution is how quietly it happens. While other technologies make noise about high throughput or low latency, Bluetooth’s progress feels invisible but omnipresent. It doesn’t chase raw speed anymore – it chases <em>relevance</em>. The protocol is learning to sense, adapt, and coordinate, all qualities that make it essential for the next generation of ambient computing.</p>
<p>So while you might not notice Bluetooth 6.1 when it arrives, you’ll definitely feel its effects. Devices will sync faster, connections will drop less, audio will sound cleaner, and proximity-based features will just “know” what you want them to do. That’s the beauty of mature engineering: when it works so seamlessly that people stop thinking about it altogether.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Bluetooth has come a long way from its early days as a clunky pairing protocol for headsets. It’s now one of the quietest yet most influential technologies shaping how devices around us communicate. The newer generations – 5.4, 6.0, and soon 6.1 – show that Bluetooth’s evolution isn’t about flashy upgrades. It’s about <em>refinement</em>, about making wireless communication more precise, more private, and more power-aware.</p>
<p>At its core, Bluetooth’s story is about context. It’s learning to understand where you are, how far you are from something, and what kind of connection makes sense in that moment. Channel Sounding adds spatial awareness, PAwR makes massive IoT networks practical, LE Audio brings synchronized sound to earbuds, hearing aids, and broadcast systems, and encrypted advertising protects the information flowing through all of it.</p>
<p>For developers, this era of Bluetooth is exciting because it’s full of creative possibilities. You can build smarter sensors, more responsive wearables, or secure access systems that simply <em>know</em> when you’re nearby. The ecosystem is mature enough that you don’t need to be a radio engineer to experiment, but it’s still evolving fast enough to keep pushing boundaries.</p>
<p>The challenge now is not whether Bluetooth can handle the future. It’s how we, as developers and designers, decide to use it. Whether it’s powering ambient computing, healthcare networks, or next-gen audio, the technology is already ready.</p>
<p>So maybe the next time you put on your earbuds or unlock your car, take a moment to appreciate the quiet genius working behind the scenes. Bluetooth is thriving, adapting, and quietly building the connective tissue of our digital lives.</p>
<p>And for those of us who like tinkering with the unseen layers of technology, that’s a future well worth exploring.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Write a PHP Script to Calculate the Area of a Triangle ]]>
                </title>
                <description>
                    <![CDATA[ In programming, being able to find the area of a triangle is useful for many reasons. It can help you understand logic-building and syntax, and it’s a common programming problem used in school assignments. There are also many real-world applications,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/use-php-to-calculate-the-area-of-a-triangle/</link>
                <guid isPermaLink="false">68542db2cefef90e768ba1fe</guid>
                
                    <category>
                        <![CDATA[ PHP ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mathematics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ triangle ]]>
                    </category>
                
                    <category>
                        <![CDATA[ DSA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Blogs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ AYUSH MISHRA ]]>
                </dc:creator>
                <pubDate>Thu, 19 Jun 2025 15:33:06 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750346934679/2e46bebb-9614-4f1a-afb5-9bbe27906b4e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In programming, being able to find the area of a triangle is useful for many reasons. It can help you understand logic-building and syntax, and it’s a common programming problem used in school assignments. There are also many real-world applications, such as computer graphics, geometry-based simulations, or construction-related calculations.</p>
<p>In this article, we’ll look at a common problem: we are given the dimensions of a triangle, and our task is to calculate its area. You can calculate the area of a triangle using different formulas, depending on the information you have about the triangle. Here, you’re going to learn how to do it using PHP.</p>
<h3 id="heading-after-reading-this-tutorial">After reading this tutorial:</h3>
<ul>
<li><p>You will understand the basic logic behind calculating the area of a triangle.</p>
</li>
<li><p>You will know how to write PHP code that calculates the triangle’s area using pre-defined and user-entered values.</p>
</li>
<li><p>You will know how to apply this logic in small projects and assignments.</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-find-the-area-of-a-triangle-using-direct-formulas">Find the Area of a Triangle Using Direct Formulas</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-find-the-area-of-a-triangle-using-the-base-and-height-approach">Find the Area of a Triangle Using the Base and Height Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-find-the-area-of-a-triangle-using-herons-formula">Find the Area of a Triangle Using Heron's Formula</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-find-the-area-of-a-triangle-using-two-sides-and-included-angle-trigonometric-formula">Find the Area of a Triangle Using Two Sides and Included Angle (Trigonometric Formula)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You’ll understand this guide more easily if you have some knowledge about a few things:</p>
<h3 id="heading-basic-php">Basic PHP</h3>
<p>You’ll need to know basic PHP syntax to fully understand the problem. If you know how to write a simple echo statement or create a variable in PHP, then you should be good to go.</p>
<h3 id="heading-local-php-environment">Local PHP Environment</h3>
<p>To run the PHP code successfully, you should have local PHP development, such as XAMPP or WAMP, on your machine. You can also use online PHP editors like PHP Fiddle or OnlineGDB to run a PHP script without any installation.</p>
<p>In this tutorial we are going to explore three approaches to determine the area of the triangle in PHP based on the amount of information available about the triangle.</p>
<ul>
<li><p><strong>Base and Height Formula Approach:</strong> This approach is applicable when you have the perpendicular height from the base and length of the base in the problem.</p>
</li>
<li><p><strong>Heron’s Formula:</strong> This approach is used to calculate the area of triangle when you have the lengths of all three sides of the triangle.</p>
</li>
<li><p><strong>Trigonometric Formula Approach:</strong> This approach is applied on the problem when you have the length of two sides and the included angle between them.</p>
</li>
</ul>
<p>First, let’s go back to math class and use some direct formulas to find the area.</p>
<h2 id="heading-find-the-area-of-a-triangle-using-direct-formulas">Find the Area of a Triangle Using Direct Formulas</h2>
<h3 id="heading-example-1">Example 1:</h3>
<p>In this first example, you’re given the input base and height of a triangle. You have to return the area of the triangle. For this example, you’ll use a direct formula to calculate the area of the triangle.</p>
<p><strong>Input:</strong></p>
<p>Base = 5,</p>
<p>Height = 10</p>
<p>You can calculate the area of the triangle using the formula:</p>
<p>$$Area = (Base * Height) / 2$$</p><p>So, if you plug in the values you have, you get: (5* 10) / 2 = 25.</p>
<p><strong>Output:</strong></p>
<p>Area = 25</p>
<h3 id="heading-example-2">Example 2:</h3>
<p>In this second example, you’re given the length of two sides of a triangle and one angle between them. You have to return the area of the triangle. In this example, you’ll use another direct formula to calculate the area of the triangle.</p>
<p><strong>Input:</strong></p>
<p>Side A = 7, Side B = 9, Angle between them = 60°</p>
<p>In this case, you’ll use the formula:</p>
<p>$$Area = (1/2) A B * sin(Angle).$$</p><p>Then just substitute in the values you’ve been given to find the area.</p>
<p><strong>Output:</strong></p>
<p>Area = 27.33 (approximately)</p>
<p>Now let’s look at some different approaches to finding the area of a triangle using PHP.</p>
<h2 id="heading-find-the-area-of-a-triangle-using-the-base-and-height-approach">Find the Area of a Triangle Using the Base and Height Approach</h2>
<p>This is the simplest and most direct approach for calculating the area of a triangle when you know the base and height. In this approach, you’ll directly put values in the formula and find the area of the triangle – but you’ll do it with PHP code.</p>
<p>First, define the base and height of the triangle. Then apply the formula for the area of the triangle. As we saw above, the formula for the area of a triangle is:</p>
<p>$$Area = (Base * Height) / 2$$</p><p>After calculating the area of the triangle, output the answer.</p>
<p>Alright, so here’s how we can implement that in PHP:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// Define the base and height</span>
$base = <span class="hljs-number">5</span>;
$height = <span class="hljs-number">10</span>;

<span class="hljs-comment">// Calculate the area</span>
$area = ($base * $height) / <span class="hljs-number">2</span>;

<span class="hljs-comment">// Output the result</span>
<span class="hljs-keyword">echo</span> <span class="hljs-string">"The area of the triangle is: "</span> . $area . <span class="hljs-string">" square units."</span>;
<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>Output:</p>
<p>The area of the triangle is 25 square units.</p>
<p>In the above code, first we initialize the base and height of triangle in two variables. Then we plug those values into the area formula. PHP calculates the area of the triangle and displays the answer.</p>
<p><strong>Time Complexity</strong>: In the above approach, we are using the direct formula to calculate and return the area of the triangle, so the time complexity will be constant at O(1). The constant time complexity is efficient as it will remain constant, regardless of the size or values of the base and height.</p>
<p><strong>Space Complexity</strong>: The Space Complexity will be O(1). The space used by the above program is constant, which ensures minimal use of memory. This space complexity is ideal in environments where memory efficiency is a priority.</p>
<p>We use the above approach when we have the length of the base and height of the triangle (whether directly given or easily measurable in a right angle triangle). This method works best for right-angled triangles.</p>
<h2 id="heading-find-the-area-of-a-triangle-using-herons-formula">Find the Area of a Triangle Using Heron's Formula</h2>
<p>Heron’s formula is named after a Greek mathematician named Heron of Alexandria. Heron’s formula is useful when you know the lengths of all three sides of the triangle and you want to calculate the area without needing the height. This formula works for any type of triangle, including scalene triangles (triangles with sides of all different lengths).</p>
<p>Here’s Heron’s formula to calculate the area of a triangle:</p>
<p>$$√s(s−a)(s−b)(s−c) ​$$</p><p>Where:</p>
<ul>
<li><p>s = semi-perimeter = (a+b+c)/2 is the semi-perimeter of the triangle.</p>
</li>
<li><p>a, b, and c are the lengths of the sides.</p>
</li>
</ul>
<p>First, we define the three sides of the triangle. Then, we check all three conditions of the <a target="_blank" href="https://en.wikipedia.org/wiki/Triangle_inequality">Triangle Inequality Theorem</a> which states that if the sum of two sides is greater than the third side, then it is a valid triangle, and the given sides can form a triangle.</p>
<p>We can calculate the semi-perimeter of the triangle using the formula s = a+b+c/2. Then we can apply Heron's formula to calculate the area. After calculating the area, then output the answer.</p>
<p>Here’s how you can implement this in PHP:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// Define the sides of the triangle</span>
$a = <span class="hljs-number">7</span>;
$b = <span class="hljs-number">9</span>;
$c = <span class="hljs-number">10</span>;

<span class="hljs-comment">// Check if the sides form a valid triangle using the Triangle Inequality Theorem</span>
<span class="hljs-keyword">if</span> (($a + $b &gt; $c) &amp;&amp; ($a + $c &gt; $b) &amp;&amp; ($b + $c &gt; $a)) {

    <span class="hljs-comment">// Calculate the semi-perimeter</span>
    $s = ($a + $b + $c) / <span class="hljs-number">2</span>;

    <span class="hljs-comment">// Calculate the area using Heron's formula</span>
    $area = sqrt($s * ($s - $a) * ($s - $b) * ($s - $c));

    <span class="hljs-comment">// Output the result</span>
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"The area of the triangle is: "</span> . $area . <span class="hljs-string">" square units."</span>;

} <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// If the sides can't form a valid triangle</span>
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"The given sides do not form a valid triangle."</span>;
}
<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>Output:</p>
<p>The area of the triangle is: 27.321 square units.</p>
<p>In the above code, we first create three variables to store the lengths of the triangle’s sides, and check if the given sides form a valid triangle or not using the Triangle Inequality Theorem. Then we calculate the semi-perimeter using the formula: s = a + b + c / 2. We put the value of the semi-perimeter and lengths of all sides in Heron’s formula to calculate the area. The area of triangle is returned after calculating using the formula.</p>
<p><strong>Time Complexity</strong>: There is a total fixed number of operations such as addition, subtraction, multiplication, and square root. These operations don’t depend on input size as they are performed only a fixed number of times. This means that the time complexity is constant O(1).</p>
<p><strong>Space Complexity</strong>: We have used a fixed number of variables to calculate the area of the triangle. We have not used any additional data structures such as arrays or objects. The memory usage in the program is constant, which is better for low-memory environments. The space complexity is constant O(1).</p>
<p>This approach works best when the lengths of all sides are given. This approach is used mainly for scalene or isosceles triangles where height is directly not given. This approach can work for any type of triangle, however – scalene, isosceles, or equilateral.</p>
<h2 id="heading-find-the-area-of-a-triangle-using-two-sides-and-included-angle-trigonometric-formula">Find the Area of a Triangle Using Two Sides and Included Angle (Trigonometric Formula)</h2>
<p>In this approach, we will see a different variation of the problem. When you know two sides of a triangle and the included angle between them, you can calculate the area using this formula:</p>
<p>$$Area = 1/2 × a × b × sin(θ)$$</p><p>Where:</p>
<ul>
<li><p>a and b are the lengths of the two sides.</p>
</li>
<li><p>θ is the included angle between the two sides, measured in degrees or radians.</p>
</li>
</ul>
<p>Using the above formula, you can calculate the area of a triangle without needing its height. First, you define the two sides of the triangle and the angle between them. Then you convert the angle from degrees to radians if needed (in PHP, you can use deg2rad() to convert degrees to radians). Then you apply the formula.</p>
<p>After calculating the area of the triangle, output the result.</p>
<p>Here’s how to implement this in PHP:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// Define the two sides and the included angle</span>
$a = <span class="hljs-number">7</span>;
$b = <span class="hljs-number">9</span>;
$angle = <span class="hljs-number">60</span>; <span class="hljs-comment">// Angle in degrees</span>

<span class="hljs-comment">// Convert the angle from degrees to radians</span>
$angle_in_radians = deg2rad($angle);

<span class="hljs-comment">// Calculate the area using the formula</span>
$area = <span class="hljs-number">0.5</span> * $a * $b * sin($angle_in_radians);

<span class="hljs-comment">// Output the result</span>
<span class="hljs-keyword">echo</span> <span class="hljs-string">"The area of the triangle is: "</span> . $area . <span class="hljs-string">" square units."</span>;
<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>Output:</p>
<p>The area of the triangle is: 27.321 square units.</p>
<p>Explanation:</p>
<p>In the above case, we’re using the formula:</p>
<p>Area of Triangle = 1/2 × a × b × sin(θ)</p>
<p>And we’re substituting the following values into the formula:</p>
<p>Area= 1/2 × 7 × 9 × sin(60 ∘) ≈ 27.321</p>
<p>In the code, we declared two variables to store the length of the two sides of the triangle, and the variable <code>$angle</code> hold the included angle in degrees. We used <code>deg2rad()</code>, a PHP built-in function which converts an angle from degrees to radians. Then, we applied the actual formula: Area = 1/2 × 7 × 9 × sin(60 ∘). PHP stores the final answer in the <code>$area</code> variable.</p>
<p><strong>Time Complexity</strong>: We are using the direct formula to calculate the area of a triangle when the length of two sides and the angle between them are given. The constant time complexity is O(1).</p>
<p><strong>Space Complexity</strong>: Similarly, it does not take any extra space or use any data structures. It uses a single variable to store the result, which is why the space complexity is constant O(1).</p>
<p>This approach is perfect for the problem in which two sides and the included angle (angle between those sides) are known. You can use it when you cannot easily calculate the height of the triangle. This problem has real-life applications in geometry problems, CAD applications, or physics simulations. This method is very accurate and doesn’t require the length of all sides.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you’ve learned how you can calculate the area of a triangle, both manually and using PHP. You have seen different approaches and learned about which one is best given the information you have. First, we discussed the base and height approach, then looked at Heron’s formula, and finally examined how to handle things when two sides and the included angle are given.</p>
<p>Understanding the logic behind each of these approaches helps you choose the right one based on the given data.</p>
<p>And if you'd like to support me and my work directly so I can keep creating these tutorials, <a target="_blank" href="https://paypal.me/ayushM010">you can do so here</a>. Thank you!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Data Communication and Networking Handbook ]]>
                </title>
                <description>
                    <![CDATA[ When I was beginning to learn about networks, I didn't know how many things in my daily life depended on them – from texting on WhatsApp to watching YouTube. I still vividly remember when I learned that computers communicate with one another. It was ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-data-communication-and-networking-handbook/</link>
                <guid isPermaLink="false">6853059aed5a0659bcde37e5</guid>
                
                    <category>
                        <![CDATA[ data ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data communication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ networking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ communication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ valentine Gatwiri ]]>
                </dc:creator>
                <pubDate>Wed, 18 Jun 2025 18:29:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750178451091/adea6449-2daf-405b-80f0-e23a356fa45b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When I was beginning to learn about networks, I didn't know how many things in my daily life depended on them – from texting on WhatsApp to watching YouTube.</p>
<p>I still vividly remember when I learned that computers communicate with one another. It was magic – telepathy, nearly. But there is a systematic, logical process behind the magic: computer networking. And I’m excited to help you discover how computers communicate and why it’s possible.</p>
<p>Essentially, data communication is all about exchanging information between two or more machines. But it's not just a question of sending – it's a matter of sending the right data, to the right machine, in the right format. And that's the brilliance of networking basics.</p>
<p>This handbook will teach you the fundamentals of the language of computers. You'll discover how data is passed from machine to machine, how operations are carried out on information, and how networks – from tiny home arrangements to massive worldwide networks – are constructed and managed.</p>
<p>We’ll start with the absolute basics: what a network is, what the hardware is, and how devices know each other and talk to each other. Next, we’ll examine crucial networking models like OSI and TCP/IP stacks that segment communication into layers in order to make it easier to understand and troubleshoot. You'll learn about IP addresses, DNS, routing, switching, and firewalls and security's involvement in keeping networks safe.</p>
<p>Whether you are a complete beginner starting from the ground up or a seasoned dev looking to solidify your foundation, this handbook will walk you through linking the dots. When you're finished, you won't only understand how your favorite sites and apps really function behind the scenes – you'll be able to speak networks in your sleep.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-chapter-1-data-and-communication-fundamentals">Chapter 1: Data and Communication Fundamentals</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-2-signals-the-language-of-communication">Chapter 2: Signals — The Language of Communication</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-3-bandwidth-understanding-how-much-we-can-transmit">Chapter 3: Bandwidth — Understanding How Much We Can Transmit</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-4-transmission-media-the-highways-of-communication">Chapter 4: Transmission Media — The Highways of Communication</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-5-network-topologies-how-we-structure-our-connections">Chapter 5: Network Topologies — How We Structure Our Connections</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-6-the-osi-model-understanding-layers-of-communication">Chapter 6: The OSI Model — Understanding Layers of Communication</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-7-protocols-and-ports-how-rules-and-doors-guide-communication">Chapter 7: Protocols and Ports — How Rules and Doors Guide Communication</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-8-ip-addressing-and-subnetting-naming-and-organizing-the-network">Chapter 8: IP Addressing and Subnetting — Naming and Organizing the Network</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-9-routing-and-switching-directing-data-on-the-network">Chapter 9: Routing and Switching — Directing Data on the Network</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-10-network-infrastructure-devices-security-and-the-modern-internet">Chapter 10: Network Infrastructure — Devices, Security, and the Modern Internet</a></p>
</li>
</ol>
<h2 id="heading-chapter-1-data-and-communication-fundamentals"><strong>Chapter 1: Data and Communication Fundamentals</strong></h2>
<p>This introductory section lays the groundwork for the rest of the handbook. You’ll learn what data communication is, how it's different from "sending a message," and what's required for two computers (or phones, or servers) to exchange information efficiently.</p>
<p>You'll start to feel at home with fundamental ideas, technical terminology, and the machinery behind the scenes that works quietly in the background to make daily technology appear effortless.</p>
<p>By the end, you will be able to:</p>
<ul>
<li><p>Explain what data communication is and how it works in real life</p>
</li>
<li><p>Identify the components involved in data communication systems</p>
</li>
<li><p>Differentiate between types of data and how they're represented</p>
</li>
<li><p>Understand different types of data flow (simplex, half duplex, full duplex)</p>
</li>
<li><p>Describe what a computer network is and its main categories (LAN, MAN, WAN)</p>
</li>
<li><p>Understand the importance of protocols and how they enable communication</p>
</li>
<li><p>Recognize the role of standards and standard organizations in making networking universal</p>
</li>
</ul>
<h2 id="heading-data-vs-information">Data vs Information</h2>
<p>We throw around the word "data" a lot these days – "big data," "data science," "data plans" – but what does it mean?</p>
<ul>
<li><p><strong>Data</strong> is raw. It's unprocessed, meaningless on its own. Think of numbers on a spreadsheet with no labels.</p>
</li>
<li><p><strong>Information</strong> is processed data – it's meaningful and helps us make decisions.</p>
</li>
</ul>
<p><strong>A personal example:</strong> I once received a CSV file from my school with hundreds of rows of marks. It looked like chaos – just student IDs and scores. But the moment I matched those IDs to names and applied the grading criteria, it became useful <strong>information</strong> about who passed, who failed, and who topped the class.</p>
<p>So, data is the ingredient. Information is the cooked dish.</p>
<h2 id="heading-so-what-exactly-is-data-communication">So, What Exactly is Data Communication?</h2>
<p>Imagine you're texting your friend. Your phone sends data to their phone using signals through cables, Wi-Fi, or even satellites. This entire process is called <strong>data communication</strong>, moving data from one place (you!) to another (your friend).</p>
<p>But it’s not as random as it sounds. It follows a set of agreed rules called <strong>protocols</strong>. Think of them as social etiquette for devices – how to talk, when to talk, and what to say.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748435887792/a607b06f-ffe6-47c1-8e18-a79ab4f0b068.png" alt="Explanation of protocols" class="image--center mx-auto" width="530" height="360" loading="lazy"></p>
<p>This process involves:</p>
<ul>
<li><p>Devices (sender and receiver)</p>
</li>
<li><p>A transmission medium (like cables or wireless)</p>
</li>
<li><p>A set of rules (protocols)</p>
</li>
</ul>
<p>Let’s break it down further.</p>
<h3 id="heading-characteristics-of-data-communication">Characteristics of Data Communication</h3>
<p>To be considered effective, data communication must exhibit the following characteristics:</p>
<ol>
<li><p><strong>Delivery</strong>: Data must reach the correct destination. If I send a message to John, it shouldn't land in Sarah's inbox.</p>
</li>
<li><p><strong>Accuracy</strong>: No one wants a corrupted file. Data must be accurate, free from errors.</p>
</li>
<li><p><strong>Timeliness</strong>: Some data, like live video, must arrive on time. Lag ruins the experience.</p>
</li>
<li><p><strong>Jitter</strong>: Inconsistent arrival times of data packets (especially in audio/video) create disruption. A good system keeps jitter low.</p>
</li>
</ol>
<p>I once experienced a video call where the sound lagged by 5 seconds. It turned into a game of "Guess what I said." That's jitter in action.</p>
<h3 id="heading-meet-the-cast-the-components-of-data-communication">Meet the Cast: The Components of Data Communication</h3>
<p>In every data conversation, five key players show up:</p>
<ol>
<li><p><strong>Sender</strong> – The device that starts the chat (like your phone).</p>
</li>
<li><p><strong>Receiver</strong> – The one getting the message (your friend’s phone).</p>
</li>
<li><p><strong>Message</strong> – The actual info, whether it’s "hi" or a TikTok.</p>
</li>
<li><p><strong>Transmission Medium</strong> – The path your message travels (Wi-Fi, cables, and so on).</p>
</li>
<li><p><strong>Protocol</strong> – The language they agree to speak (like TCP/IP).</p>
</li>
</ol>
<p>Pretty cool, right?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748436008530/14d2e296-b999-4790-a4fd-26a7026e8810.png" alt="Essentials of Networking" class="image--center mx-auto" width="557" height="391" loading="lazy"></p>
<h2 id="heading-data-representation">Data Representation</h2>
<p>Computers are not humans. They don’t understand language, pictures, or music – unless these are converted into a format they can process: <strong>bits</strong> (0s and 1s).</p>
<p>Let’s walk through the different types of data representation:</p>
<h3 id="heading-1-text">1. Text</h3>
<p>Text is stored as a sequence of characters using encoding schemes like ASCII and Unicode. For example, the letter "A" in ASCII is 65, which in binary is <code>01000001</code>.</p>
<h3 id="heading-2-numbers">2. Numbers</h3>
<p>Similarly, numeric data is stored as bit patterns. Computers can perform calculations using binary logic.</p>
<h3 id="heading-3-images">3. Images</h3>
<p>An image is a matrix of pixels. Each pixel is represented by bits. A black-and-white image might only need 1 bit per pixel, while a full-color photo could use 24 bits per pixel or more.</p>
<p><strong>Example:</strong> A 10x10 black and white image = 100 pixels = 100 bits.</p>
<h3 id="heading-4-audio">4. Audio</h3>
<p>Audio is analog, but we digitize it for storage and transmission. For instance, voice notes are sampled at certain intervals and stored as bits.</p>
<h3 id="heading-5-video">5. Video</h3>
<p>Video is a sequence of images (frames) along with synchronized audio. It’s high in data volume and needs compression techniques like MP4 to be practical.</p>
<h3 id="heading-how-does-the-data-flow">How Does the Data Flow?</h3>
<p>You might think data just zips across in one go – but it has <em>modes</em>, just like moods:</p>
<ul>
<li><p><strong>Simplex:</strong> One-way only (like a radio broadcast).</p>
</li>
<li><p><strong>Half Duplex:</strong> You take turns – like walkie-talkies.</p>
</li>
<li><p><strong>Full Duplex:</strong> Both sides talk at once – think phone calls.</p>
</li>
</ul>
<p>Each has its own vibe depending on the situation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748436167157/a8e8d277-16f8-4891-8bfd-8b63ac468bda.png" alt="Data flow – simplex, half duplex, full duplex" class="image--center mx-auto" width="574" height="220" loading="lazy"></p>
<h2 id="heading-what-is-a-computer-network">What is a Computer Network?</h2>
<p>A computer network is a system that allows devices to share data. These connected devices (nodes) use communication links to interact.</p>
<p>The main goals of a network are:</p>
<ul>
<li><p><strong>Reliability</strong>: Data should get there.</p>
</li>
<li><p><strong>Security</strong>: Unwanted access should be blocked.</p>
</li>
<li><p><strong>Performance</strong>: High speed, low delay.</p>
</li>
</ul>
<p>When you connect your laptop at a café, for example, you’re part of a <strong>network</strong>. But networks come in all shapes:</p>
<ul>
<li><strong>PAN (A personal area network)</strong>: connects electronic devices within a user's immediate area.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748933101198/29cc06ed-cf79-44b8-bf6b-4691729c80c7.png" alt="Personal Area Network – downloadzone" class="image--center mx-auto" width="251" height="220" loading="lazy"></p>
<ul>
<li><strong>LAN (Local Area Network):</strong> Small – like your home Wi-Fi.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748933264502/fad55c68-0170-4fee-8a6c-cc7463f697be.png" alt="Local Area Network – IT Release" class="image--center mx-auto" width="396" height="233" loading="lazy"></p>
<ul>
<li><strong>MAN (Metropolitan Area Network):</strong> Covers a city – like college campuses.</li>
</ul>
<p><img src="https://cyberhoot.com/wp-content/uploads/2022/01/3d7659f7-2f69-4b14-b851-a84ab85152e0.png" alt="Metropolitan Area Network (MAN) – CyberHoot" width="1993" height="1388" loading="lazy"></p>
<ul>
<li><strong>WAN (Wide Area Network):</strong> Huge – think the <em>entire</em> internet!</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748933893001/aa0343da-2733-447f-98f2-c347a7e964c9.png" alt="Wide Area Network – Vecteezy" class="image--center mx-auto" width="315" height="248" loading="lazy"></p>
<p>The internet isn’t one big net – it’s a net of many, many nets.</p>
<h2 id="heading-what-is-a-protocol">What is a Protocol?</h2>
<p>A protocol is a set of rules that devices follow to communicate. Without a protocol, it’s chaos.</p>
<p><strong>Analogy:</strong> Think of a group project. If everyone agrees to use Google Docs and write in English (or any one language), it works. But if one person uses Word in French, and another emails a PDF in Mandarin, you have a mess.</p>
<p>Protocols define:</p>
<ul>
<li><p><strong>What</strong> data to send</p>
</li>
<li><p><strong>How</strong> to send it</p>
</li>
<li><p><strong>When</strong> to send it</p>
</li>
</ul>
<h3 id="heading-elements-of-a-protocol">Elements of a Protocol</h3>
<ol>
<li><p><strong>Syntax</strong>: Format and structure (like grammar).</p>
</li>
<li><p><strong>Semantics</strong>: Meaning of each section.</p>
</li>
<li><p><strong>Timing</strong>: When to send and at what speed.</p>
</li>
</ol>
<h2 id="heading-standards-in-networking">Standards in Networking</h2>
<p>Standards are agreements to ensure that different systems can work together. Without standards, each manufacturer would create isolated networks that couldn’t talk to others.</p>
<p>There are two types of standards:</p>
<ul>
<li><p><strong>De facto</strong>: By convention (used commonly but not formally approved)</p>
</li>
<li><p><strong>De jure</strong>: By law (formally approved)</p>
</li>
</ul>
<h3 id="heading-standards-organizations">Standards Organizations</h3>
<p>There are a few key organizations that help define these standards:</p>
<ul>
<li><p><strong>ISO</strong> – International Organization for Standardization</p>
</li>
<li><p><strong>ITU-T</strong> – International Telecommunication Union</p>
</li>
<li><p><strong>IEEE</strong> – Institute of Electrical and Electronics Engineers</p>
</li>
<li><p><strong>ANSI</strong> – American National Standards Institute</p>
</li>
<li><p><strong>EIA</strong> – Electronic Industries Association</p>
</li>
</ul>
<h2 id="heading-chapter-2-signals-the-language-of-communication"><strong>Chapter 2: Signals — The Language of Communication</strong></h2>
<p>In this chapter, I’ll teach you about the invisible messengers – signals – that make it all possible. You will:</p>
<ul>
<li><p>Understand what signals are and how they carry data</p>
</li>
<li><p>Distinguish between analog and digital signals, and when each is used</p>
</li>
<li><p>Learn about key signal characteristics like amplitude, frequency, phase, and wavelength</p>
</li>
<li><p>Visualize and compare time domain vs frequency domain representations</p>
</li>
<li><p>Appreciate how real-world signals are composed of multiple waves (composite signals)</p>
</li>
<li><p>Understand digital signal features like bit rate, baud rate, and bit interval</p>
</li>
<li><p>Learn about baseband vs broadband transmission methods</p>
</li>
<li><p>Identify challenges like attenuation, distortion, and noise</p>
</li>
<li><p>Grasp how bandwidth affects data quality and speed</p>
</li>
</ul>
<p>When I was a teenager, I often wondered how my voice traveled through a phone and reached someone else in another town. I imagined tiny versions of myself running through wires with a message in hand. Turns out, while not exactly accurate, the idea of something carrying your message is spot on. That something is called a <strong>signal</strong>.</p>
<p>A signal is the form data takes to move through physical space. Whether it’s your mom calling you, your professor sending an email, or your friend uploading a reel – all of that happens through signals.</p>
<h2 id="heading-data-and-signals">Data and Signals</h2>
<h3 id="heading-what-is-a-signal">What is a Signal?</h3>
<p>I learned that data is like the message I wanted to send, and a signal is the delivery truck. Without the truck, the message goes nowhere.</p>
<p>Here’s where things get a bit science-y, but stay with me. When data travels, it becomes signals, kind of like waves. These waves can be classified in to two common ways, by the nature of the signal, and by their patterns over time. We’ll talk about the nature of the signal first.</p>
<h3 id="heading-the-nature-of-the-signal-analog-vs-digital">The Nature of the Signal: Analog vs Digital</h3>
<ul>
<li><p><strong>Analog</strong> – A signal that varies smoothly over time and can take any value in a range. Like ocean waves, always changing smoothly. Continuous (like voices).</p>
</li>
<li><p><strong>Digital</strong> – A signal that has discrete values, usually 0s and 1s. Like a staircase – clear, sharp steps, either up or down, in bits (1s and 0s, like computers).</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748436311536/db273577-c474-4eca-8396-b9ea7bd0031f.png" alt="Analog and digital signals" class="image--center mx-auto" width="554" height="234" loading="lazy"></p>
<h3 id="heading-analog-signals">Analog Signals</h3>
<p>The first time I visualized an analog signal, it looked like the ripples I saw after tossing a stone in water. Gentle curves moving outwards.</p>
<h4 id="heading-key-features-of-analog-signals">Key features of analog signals:</h4>
<ul>
<li><p><strong>Amplitude</strong>: This reminded me of volume. Louder signals have taller waves.</p>
</li>
<li><p><strong>Frequency</strong>: It’s the beat or rhythm. High frequency = rapid waves = higher pitch.</p>
</li>
<li><p><strong>Period</strong>: Time for one full wave cycle. Shorter periods mean higher frequency.</p>
</li>
<li><p><strong>Phase</strong>: Two waves can start at different points – just like dancers starting a move a second apart.</p>
</li>
<li><p><strong>Wavelength</strong>: How far one wave travels in space. It depends on how fast it moves and its frequency.</p>
</li>
</ul>
<h4 id="heading-time-vs-frequency-domain">Time vs. Frequency Domain</h4>
<ul>
<li><p><strong>Time Domain</strong>: Shows how signals change over time. Like watching a song’s audio waveform.</p>
</li>
<li><p><strong>Frequency Domain</strong>: Shows the ingredients – how much bass, how much treble. It’s like the EQ settings on a music player.</p>
</li>
</ul>
<h4 id="heading-composite-signals-and-fourier">Composite Signals and Fourier</h4>
<p>Real-world signals are messy, made of multiple waves mixed. <a target="_blank" href="https://en.wikipedia.org/wiki/Joseph_Fourier">Fourier’s</a> big idea was: <em>Any messy signal can be broken down into simple sine waves.</em> That insight changed how engineers understand and clean up signals.</p>
<h3 id="heading-digital-signals">Digital Signals</h3>
<p>Digital signals felt familiar to me. My laptop, my phone, even my microwave speaks digital.</p>
<h4 id="heading-key-features-of-digital-signals">Key features of digital signals:</h4>
<ul>
<li><p><strong>Bit Interval</strong>: One bit’s duration. Like how long I hold down a piano key.</p>
</li>
<li><p><strong>Bit Rate</strong>: How many notes (bits) I can play per second.</p>
</li>
<li><p><strong>Baud Rate</strong>: How often the signal actually changes. Not always the same as bit rate.</p>
</li>
<li><p><strong>Levels</strong>: 2-level = 1s and 0s. More levels = more complex encoding.</p>
</li>
</ul>
<h4 id="heading-square-waves">Square Waves</h4>
<p>If analog signals are elegant curves, digital signals are sharp edges. A square wave is a bold, binary shout: ON-OFF-ON-OFF.</p>
<h4 id="heading-digital-advantages-and-struggles">Digital Advantages and Struggles</h4>
<p><strong>Why I love them:</strong></p>
<ul>
<li><p>They’re clean and easy to work with.</p>
</li>
<li><p>Errors are easier to spot and fix.</p>
</li>
</ul>
<p><strong>But they’re not perfect:</strong></p>
<ul>
<li><p>They need more bandwidth.</p>
</li>
<li><p>They don’t travel well over long distances without help.</p>
</li>
</ul>
<h3 id="heading-pattern-over-time-periodic-vs-non-periodic-signals">Pattern Over Time: Periodic vs Non-periodic Signals</h3>
<ul>
<li><p><strong>Periodic Signals</strong>: Repeat at regular intervals over time (for example, sine waves, clock pulses).</p>
</li>
<li><p><strong>Non-periodic Signals</strong>: Do <strong>not</strong> repeat – more random or unique (for example, a burst of data or speech waveform).</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749818448163/c505ace2-587d-4c50-9111-bda8a902f439.png" alt="Periodic vs non-periodic signals" class="image--center mx-auto" width="609" height="134" loading="lazy"></p>
</li>
</ul>
<h3 id="heading-periodic-signals">Periodic Signals</h3>
<p>These feel like the rhythm of my favorite song. They’re predictable. Repeating. Reliable.</p>
<h4 id="heading-key-features">Key Features</h4>
<ul>
<li><p><strong>Repetition</strong>: The same pattern, again and again. Like waves hitting the shore at steady intervals.</p>
</li>
<li><p><strong>Cycle</strong>: One complete shape of the signal. Think of it as one heartbeat in a steady pulse.</p>
</li>
<li><p><strong>Frequency</strong>: How many cycles per second? Measured in Hertz (Hz).</p>
</li>
</ul>
<h4 id="heading-why-i-like-them">Why I like them</h4>
<ul>
<li><p>Easy to analyze – like having a beat to follow.</p>
</li>
<li><p>Great for systems that need synchronization, like clock signals in my devices.</p>
</li>
</ul>
<h4 id="heading-but-still">But still...</h4>
<ul>
<li>They can’t carry surprise or variety. No space for one-time messages.</li>
</ul>
<h3 id="heading-non-periodic-signals">Non-periodic Signals</h3>
<p>These are the jazz solos of the signal world. Wild. Unique. Unpredictable.</p>
<h4 id="heading-key-features-1">Key Features</h4>
<ul>
<li><p><strong>No repetition</strong>: Each part is different – like my playlist on shuffle.</p>
</li>
<li><p><strong>Spikes and silence</strong>: Sudden changes, long pauses. Perfect for one-off data transmissions.</p>
</li>
<li><p><strong>Used in real-life data</strong>: Emails, videos, and downloads all love this format.</p>
</li>
</ul>
<h4 id="heading-why-theyre-cool">Why they’re cool</h4>
<ul>
<li><p>Great for representing actual information – each burst means something new.</p>
</li>
<li><p>More flexible for transmitting complex messages.</p>
</li>
</ul>
<h4 id="heading-whats-tricky">What’s tricky</h4>
<ul>
<li><p>Harder to analyze and predict.</p>
</li>
<li><p>Tougher to filter or compress efficiently.</p>
</li>
</ul>
<p>Understanding signals helps us know how fast and cleanly information travels.</p>
<h2 id="heading-channels-the-roads-signals-travel-on">Channels: The Roads Signals Travel On</h2>
<p>In the context of signals and communication, <strong>channels</strong> refer to the medium or path through which a signal travels from a sender (transmitter) to a receiver. Channels are like roads. You can’t just send a truck (signal) without knowing if the road (channel) allows it.</p>
<p>We can describe channels in different ways:</p>
<ul>
<li><p><strong>Physically</strong>: What the signal travels through (like a wire or air).</p>
</li>
<li><p><strong>Functionally</strong>: How the signal is allowed to move through (based on frequency).</p>
</li>
<li><p><strong>Logically</strong>: How we organize multiple data streams within the same physical path.</p>
</li>
</ul>
<h3 id="heading-physical-channels-the-road-itself">Physical Channels = The Road Itself</h3>
<p>These are the real, tangible paths for signals:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Example</strong></td><td><strong>Medium</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Ethernet cable</td><td>Copper wire</td></tr>
<tr>
<td>Fiber-optic link</td><td>Glass strand</td></tr>
<tr>
<td>Wi-Fi or Radio</td><td>Air (wireless)</td></tr>
<tr>
<td>Satellite transmission</td><td>Space (electromagnetic waves)</td></tr>
</tbody>
</table>
</div><h3 id="heading-frequency-behavior-of-physical-channels">Frequency Behavior of Physical Channels</h3>
<p>Just like roads are built for certain speeds, physical channels are better at carrying certain frequencies.</p>
<p>Here’s where <strong>low-pass</strong>, <strong>high-pass</strong>, <strong>band-pass</strong>, and <strong>band-stop</strong> come in – they describe how a physical channel behaves.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Channel Type</strong></td><td><strong>Behavior</strong></td><td><strong>Analogy</strong></td><td><strong>Common Use</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Low-pass</td><td>Lets low frequencies pass</td><td>Quiet country road (slow cars only)</td><td>Telephone lines (voice)</td></tr>
<tr>
<td>Band-pass</td><td>Allows a specific frequency band</td><td>Toll road with speed range</td><td>FM radio, Wi-Fi</td></tr>
<tr>
<td>High-pass</td><td>Blocks low, passes high frequencies</td><td>Speedway (fast cars only)</td><td>Audio filtering</td></tr>
<tr>
<td>Band-stop</td><td>Blocks a range but passes others</td><td>Road under construction</td><td>Noise removal (for example, hum filter)</td></tr>
</tbody>
</table>
</div><p>So when we say "low-pass channel," we're talking about <strong>how a physical channel filters signals.</strong></p>
<h3 id="heading-logical-channels-lanes-on-the-road">Logical Channels = Lanes on the Road</h3>
<p>A <strong>logical channel</strong> is a virtual path created within a physical one. It organizes or splits the signal flow so multiple people or devices can use the same channel without crashing into each other.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Feature</strong></td><td><strong>Description</strong></td><td><strong>Analogy</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Frequency Division</td><td>Each user gets their own frequency</td><td>FM radio stations</td></tr>
<tr>
<td>Time Division</td><td>Each user gets a time slot</td><td>Taking turns at a speaking table</td></tr>
<tr>
<td>Virtual Circuits</td><td>Custom paths inside networks</td><td>Reserved bus seats</td></tr>
</tbody>
</table>
</div><p>So yes – you can have many logical channels on one physical cable.</p>
<h4 id="heading-how-they-work-together">How They Work Together</h4>
<p>Let’s combine it all:</p>
<p>Imagine a fiber optic cable (physical channel) that’s designed to carry a specific frequency range (band-pass).<br>Within that frequency range, you can create many logical channels using time or frequency division.</p>
<p>Example: FM Radio</p>
<ul>
<li><p><strong>Physical Channel</strong>: Air (radio waves)</p>
</li>
<li><p><strong>Type</strong>: Band-pass (88–108 MHz)</p>
</li>
<li><p><strong>Logical Channels</strong>: Each station (for example, 98.4 FM) is a logical channel inside that band</p>
</li>
</ul>
<p>Example: Internet over DSL</p>
<ul>
<li><p><strong>Physical Channel</strong>: Telephone line (copper wire)</p>
</li>
<li><p><strong>Type</strong>: Low-pass for voice, high-pass for internet</p>
</li>
<li><p><strong>Logical Channels</strong>: Browsing, streaming, and downloads running together via time/frequency division</p>
</li>
</ul>
<h2 id="heading-baseband-vs-broadband-transmission-how-we-use-the-channel">Baseband vs Broadband Transmission: How We Use the Channel</h2>
<p>There are two main types of ways we use the channel: baseband and broadband transmission.</p>
<p>Baseband Transmission is like talking directly to someone across a quiet room. Simple and unaltered. Common in local systems like Ethernet.</p>
<p>Broadband Transmission is a bit different. Here, we dress up the digital message in analog clothing using <strong>modulation</strong>. That’s how we send data over radio or fiber. It’s more complex, but necessary when you’re dealing with wider, noisier roads.</p>
<h3 id="heading-signal-villains-what-goes-wrong-on-the-way">Signal Villains: What Goes Wrong on the Way</h3>
<p>As your signal travels down the channel, it may face <strong>three big problems</strong>.</p>
<ol>
<li><p><strong>Attenuation:</strong> It’s like my voice getting quieter the farther I am from someone. Amplifiers help boost it.</p>
</li>
<li><p><strong>Distortion:</strong> Imagine you and I agree to send square waves, but by the time it reaches you, it looks like mush. That’s distortion, especially bad over long cables.</p>
</li>
<li><p><strong>Noise:</strong> Noise is anything extra that wasn’t supposed to be in the signal. From lightning strikes to microwaves, interference is real.</p>
</li>
</ol>
<p><strong>Types I learned about:</strong></p>
<ul>
<li><p>Thermal (heat-related)</p>
</li>
<li><p>Induced (nearby equipment)</p>
</li>
<li><p>Crosstalk (adjacent wires “talking”)</p>
</li>
<li><p>Impulse (sudden bursts)</p>
</li>
</ul>
<p>We can reduce noise using better cables, filters, and digital corrections.</p>
<h2 id="heading-bandwidth">Bandwidth</h2>
<p>The word ‘bandwidth’ gets thrown around so much. For me, it used to just mean internet speed. But it’s deeper:</p>
<ul>
<li><p><strong>Analog Bandwidth</strong>: Range of frequencies a signal uses.</p>
</li>
<li><p><strong>Digital Bandwidth</strong>: How much data we can push through per second.</p>
</li>
</ul>
<p>More bandwidth = more room = faster, clearer communication.</p>
<p>We’ll talk more about bandwidth in the next chapter.</p>
<p>Learning about signals was like being handed the key to a secret code. Every beep, flash, and wave in our world is part of a language. Once you see it, you can’t unsee it. Signals are not just theory – they are the reason I can write this on a laptop, send it to the cloud, and have you read it anywhere in the world.</p>
<h2 id="heading-chapter-3-bandwidth-understanding-how-much-we-can-transmit">Chapter 3: Bandwidth — Understanding How Much We Can Transmit</h2>
<p>When I first heard the term "bandwidth," I assumed it just meant how fast my internet was. And while that’s not entirely wrong, I came to learn there’s much more to it.</p>
<p>In this chapter, we’ll delve into the concept of bandwidth as the capacity of a communication path, examine its impact on signal quality and speed, and investigate how it's measured in both analog and digital systems.</p>
<p>By the end of this chapter, you will be able to explain:</p>
<ul>
<li><p>What bandwidth means in different contexts</p>
</li>
<li><p>How analog and digital bandwidths are measured</p>
</li>
<li><p>The concept of throughput and how it differs from bandwidth</p>
</li>
<li><p>Factors that affect data transmission performance</p>
</li>
</ul>
<h2 id="heading-what-bandwidth-is-all-about">What Bandwidth is All About</h2>
<p><strong>Bandwidth</strong> is the maximum amount of data that can be transmitted over a communication channel in a given amount of time.</p>
<p>Have you ever streamed a movie and it kept buffering? That frustrating lag led me to one of the most important concepts in networking: bandwidth. Bandwidth is like a highway. The wider the road, the more cars (or data) can pass at once.</p>
<p>I also like to think of it this way: If I’m trying to pour water (data) through a pipe (the communication channel), a narrow pipe limits how much water can flow through at a time. That’s low bandwidth. A wide pipe? Now we’re talking high bandwidth – fast and smooth.</p>
<h3 id="heading-bandwidth-utilization">Bandwidth Utilization</h3>
<h4 id="heading-efficiency">Efficiency</h4>
<p>This is how well we use the available bandwidth. High efficiency means most of the bandwidth is being used for actual data (not overhead).</p>
<h4 id="heading-overhead">Overhead</h4>
<p>Overhead includes headers, acknowledgments, and error-checking codes. It’s necessary, but it eats into our available bandwidth.</p>
<h4 id="heading-idle-time">Idle Time</h4>
<p>Sometimes the channel sits unused, due to waiting for acknowledgment, processing time, and so on. Minimizing idle time improves efficiency.</p>
<h2 id="heading-bandwidth-in-analog-and-digital-terms">Bandwidth in Analog and Digital Terms</h2>
<h3 id="heading-analog-bandwidth">Analog Bandwidth</h3>
<p>Analog bandwidth refers to the <strong>range of frequencies</strong> over which an analog signal can be accurately acquired, processed, or transmitted by a system. Beyond this range, the signal begins to degrade – either being attenuated or distorted, making it unreliable for precise use.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750094089263/3f02c7a4-9652-4162-b258-422e431d94a8.png" alt="Analog Bandwidth - amplitude &amp; frequency graph" class="image--center mx-auto" width="338" height="293" loading="lazy"></p>
<h4 id="heading-key-concepts">Key Concepts</h4>
<ul>
<li><p><strong>Frequency Range:</strong> Analog bandwidth defines the spectrum of frequencies that a system can handle <strong>without significant degradation</strong>. It’s the system’s “comfort zone” for signal fidelity.</p>
</li>
<li><p><strong>3 dB Bandwidth:</strong> One common method of defining analog bandwidth is the <strong>-3 dB point</strong>. At this point, the signal’s amplitude drops to about 70.7% of its original value, meaning almost half its power is lost. Frequencies beyond this threshold experience much more signal loss or distortion.</p>
</li>
<li><p><strong>Importance in Signal Fidelity:</strong> Analog bandwidth directly affects how well a system can reproduce or process real-world signals – especially in audio, video, instrumentation, and telecommunications. A narrow bandwidth results in muffled or distorted outputs, while a wider bandwidth ensures better detail and accuracy.</p>
</li>
</ul>
<h3 id="heading-bandwidth-and-rise-time">Bandwidth and Rise Time</h3>
<p>In instruments like oscilloscopes, analog bandwidth is closely related to <strong>rise time</strong> – the time it takes for a signal to transition from low to high. A wider bandwidth enables faster transitions to be captured accurately, which is essential for analyzing high-speed or fast-changing signals.</p>
<h3 id="heading-real-life-example">Real-Life Example</h3>
<p>Consider old telephone systems: they typically had an analog bandwidth ranging from 300 Hz to 3300 Hz, resulting in a 3000 Hz bandwidth. This range was enough for clear voice transmission, but not wide enough for high-fidelity music or modern audio standards.</p>
<h3 id="heading-applications-of-analog-bandwidth">Applications of Analog Bandwidth</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Application Area</strong></td><td><strong>Role of Analog Bandwidth</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Oscilloscopes</td><td>Determines how accurately signals (especially fast ones) are captured.</td></tr>
<tr>
<td>Amplifiers</td><td>Specifies which frequency ranges can be amplified without distortion.</td></tr>
<tr>
<td>Communication Systems</td><td>Defines signal capacity and transmission quality.</td></tr>
<tr>
<td>Data Acquisition</td><td>Affects how well fast-changing signals are measured and analyzed.</td></tr>
</tbody>
</table>
</div><h3 id="heading-digital-bandwidth">Digital Bandwidth</h3>
<p>Digital bandwidth refers to the <strong>maximum capacity</strong> of a digital channel to transmit data over a specific period, usually measured in <strong>bits per second (bps)</strong>. It’s a measure of how much data can “flow” through a communication path, much like how the width of a pipe controls how much water can pass through.</p>
<p>The wider the digital bandwidth, the more data can be transmitted simultaneously, resulting in faster downloads, smoother video streams, and better overall network performance.</p>
<h4 id="heading-bandwidth-vs-data-rate">Bandwidth vs. Data Rate</h4>
<p>Although they’re often used interchangeably, they aren’t quite the same:</p>
<ul>
<li><p><strong>Bandwidth</strong> is the capacity of the channel – the <em>maximum potential</em>.</p>
</li>
<li><p><strong>Data rate</strong> is the actual speed at which data is transmitted, which can vary based on factors like:</p>
<ul>
<li><p>Network congestion</p>
</li>
<li><p>Hardware limitations</p>
</li>
<li><p>Signal interference</p>
</li>
</ul>
</li>
</ul>
<p>Think of bandwidth as the size of a highway, and data rate as how fast cars are moving on it.</p>
<h4 id="heading-how-digital-bandwidth-is-measured">How Digital Bandwidth is Measured</h4>
<p>Digital bandwidth is expressed in units such as:</p>
<ul>
<li><p><strong>bps</strong> – bits per second</p>
</li>
<li><p><strong>Kbps</strong> – thousands of bits per second</p>
</li>
<li><p><strong>Mbps</strong> – millions of bits per second</p>
</li>
<li><p><strong>Gbps</strong> – billions of bits per second</p>
</li>
</ul>
<p><strong>Example</strong>: A 100 Mbps internet connection can, in theory, transfer 100 million bits of data every second.</p>
<h4 id="heading-why-it-matters">Why It Matters</h4>
<p>Bandwidth plays a central role in modern digital life. Without enough bandwidth:</p>
<ul>
<li><p>Streaming videos buffer</p>
</li>
<li><p>Video calls drop in quality or disconnect</p>
</li>
<li><p>Online games lag or stutter</p>
</li>
<li><p>Large files download painfully slowly</p>
</li>
</ul>
<p>This becomes even more critical when multiple devices share the same network. Each device draws from the available bandwidth, which can quickly get overwhelmed if the demand is too high.</p>
<h3 id="heading-digital-vs-analog-bandwidth">Digital vs. Analog Bandwidth</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Aspect</strong></td><td><strong>Digital Bandwidth</strong></td><td><strong>Analog Bandwidth</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Measured in</td><td>Bits per second (bps, Mbps, Gbps)</td><td>Hertz (Hz)</td></tr>
<tr>
<td>Focus</td><td>Data transmission rate</td><td>Frequency range</td></tr>
<tr>
<td>Example</td><td>Internet connection</td><td>FM radio signal (for example, 88–108 MHz)</td></tr>
</tbody>
</table>
</div><h3 id="heading-bandwidth-in-shared-networks">Bandwidth in Shared Networks</h3>
<p>In shared environments – like home Wi-Fi or public hotspots – everyone taps into the same bandwidth. If bandwidth is limited and several devices are streaming, gaming, or downloading, the network slows down for everyone.</p>
<h2 id="heading-throughput-what-gets-delivered">Throughput – What Gets Delivered</h2>
<p>While <strong>bandwidth</strong> is the <em>potential</em> capacity of a channel (the width of the road), <strong>throughput</strong> is the <em>actual</em> rate at which data travels end‑to‑end under real‑world conditions. It’s the number of cars that make it through the city per minute, after red lights, speed limits, and detours.</p>
<p><strong>Key factors that influence throughput:</strong></p>
<ul>
<li><p><strong>Interference &amp; Noise</strong> (analog) or <strong>packet collisions</strong> (digital)</p>
</li>
<li><p><strong>Hardware Constraints</strong> (CPU, NICs, switches)</p>
</li>
<li><p><strong>Network Congestion</strong> (too many users/devices)</p>
</li>
<li><p><strong>Error Retransmissions</strong> (when packets get lost or corrupted)</p>
</li>
</ul>
<p><strong>Example:</strong> A “100 Mbps” link (bandwidth) might only sustain 80 Mbps of throughput because of TCP overhead, competing traffic, and occasional packet losses.</p>
<h3 id="heading-latency-and-delay-the-time-dimension">Latency and Delay – The Time Dimension</h3>
<p>Latency is the <em>time</em> it takes for a single bit (or packet) to travel from sender to receiver. Think of it as a travel time, whereas bandwidth and throughput are about volume.</p>
<ol>
<li><p><strong>Propagation Delay:</strong> Time for the signal to move through the medium (for example, light in fiber: ~200,000 km/s).</p>
</li>
<li><p><strong>Transmission Delay:</strong> Time to push all the bits of a packet onto the wire:<br> Packet Size (bits)÷Link Bandwidth (bps)\text{Packet Size (bits)} ÷ \text{Link Bandwidth (bps)}Packet Size (bits)÷Link Bandwidth (bps)</p>
</li>
<li><p><strong>Processing Delay:</strong> Time routers or switches spend examining headers, making forwarding decisions.</p>
</li>
<li><p><strong>Queuing Delay:</strong> Time packets wait in buffers when traffic spikes.</p>
</li>
</ol>
<p><strong>Real‑world story:</strong> During a long‑distance video call, even 100 ms of round‑trip latency can feel like talking through molasses – voices overlap, and the conversation feels stilted.</p>
<h3 id="heading-jitter-variability-in-arrival">Jitter – Variability in Arrival</h3>
<p><strong>Jitter</strong> is the inconsistency in packet arrival times. Even if the average latency is low, high jitter disrupts:</p>
<ul>
<li><p><strong>Audio/Video Streams:</strong> Choppy playback when packets clump or arrive too late.</p>
</li>
<li><p><strong>VoIP Calls:</strong> Glitches, echoes, or dropped words.</p>
</li>
</ul>
<p>You can mitigate this through Buffers and Quality of Service (QoS) agreements, which real‑time traffic to smooth out the delivery.</p>
<h3 id="heading-how-to-improve-performance">How to Improve Performance</h3>
<p>If I could go back in time and give myself one tip: Performance isn’t just about speed – it’s about reliability and consistency, too.</p>
<p><strong>Here’s what affects performance:</strong></p>
<ol>
<li><p><strong>Bandwidth:</strong> Think of this as the largest diameter of your internet pipe – how much data can actually move through it per second, usually in Mbps or Gbps.</p>
<p> <strong>Why it matters:</strong> More bandwidth means your connection can handle more data – like downloading big files fast or streaming in 4K. <strong>BUT:</strong> Just because your connection can go fast doesn't necessarily mean that it always does. That's where throughput comes in.</p>
</li>
<li><p><strong>Throughput:</strong> Your actual speed – how much data is really passing through the pipe right now.</p>
<p> <strong>Why it matters:</strong> Your actual internet experience (web page loading, Netflix streaming, gaming) is throughput-dependent, not bandwidth-dependent. If your throughput is bad, your videos buffer, downloads crawl, and games lag – even when you're signed up for a "fast" plan.</p>
</li>
<li><p><strong>Latency &amp; Jitter: Latency</strong> is the lag – how long it takes information to travel from your machine back to the server and vice versa (in milliseconds). <strong>Jitter</strong> is the variation in that lag – how inconsistent the timing gets.</p>
<p> <strong>Why they're significant:</strong> High latency = frustrating delay in video calls, sluggish online gaming, or keyboard lag in remote desktops. High jitter = choppy audio, frozen faces, or desync'd video in live meetings or streams.</p>
</li>
<li><p><strong>Packet Loss:</strong> Sometimes, data just doesn't get to where it’s supposed to go. Packets are tiny chunks of data, and if a few get lost along the way, your device has to ask for them again.</p>
<p> <strong>Why it matters:</strong> Small levels of packet loss can cause buffering, call drops, or rubberbanding during gaming. Greater loss = subpar performance, stuttery audio, or crashed streams.</p>
</li>
<li><p><strong>Utilization &amp; Overhead: Utilization</strong> refers to what ratio of your total bandwidth is being used at any one time. <strong>Overhead</strong> is the extra information that needs to be dealt with to manage your connection – like labels on a package.</p>
<p> <strong>Why they're important:</strong> High utilization is when your connection gets crowded – for example, rush hour. Everything slows down. High overhead absorbs your free bandwidth – less room for what you actually love (video, games, files).</p>
</li>
</ol>
<p>Engineers use <a target="_blank" href="https://www.parkplacetechnologies.com/blog/network-optimization-performance-techniques/">techniques</a> like compression, efficient routing, better cabling, and load balancing to improve performance.</p>
<p>I now see bandwidth everywhere – not just in networks, but in life. Our mental bandwidth, emotional bandwidth – it's all about capacity. Knowing how bandwidth works helped me troubleshoot slow Wi-Fi, plan file transfers, and appreciate what’s going on behind a simple Google search.</p>
<p>Just as in life with mental or emotional bandwidth, we need both ca<em>pacity</em> and <em>consistency</em> to function at our best. Understanding these metrics empowers you to diagnose slow Wi‑Fi, optimize file transfers, and build networks that meet real user demands.</p>
<h2 id="heading-chapter-4-transmission-media-the-highways-of-communication"><strong>Chapter 4: Transmission Media — The Highways of Communication</strong></h2>
<p>How does data move across distances? What path does it take?</p>
<p>This chapter dives into the physical and wireless pathways data takes from one device to another – the <strong>transmission media</strong>. By the end of this chapter, you will understand:</p>
<ul>
<li><p>What transmission media is and why it matters</p>
</li>
<li><p>The difference between guided (wired) and unguided (wireless) media</p>
</li>
<li><p>Various types of cables (twisted pair, coaxial, fiber optics)</p>
</li>
<li><p>Wireless media like radio waves, microwaves, and infrared</p>
</li>
<li><p>The strengths and limitations of each medium</p>
</li>
</ul>
<h2 id="heading-what-are-transmission-media">What are Transmission Media?</h2>
<p>Imagine needing to deliver a letter. Do you send it through a postal truck? Drop it by drone? Deliver it by hand? The method you choose is your <strong>transmission medium</strong>.</p>
<p>In the digital world, transmission media refers to the path data takes from the sender to the receiver. These paths can be <strong>physical (guided)</strong>, like cables, or <strong>wireless (unguided)</strong>, like airwaves.</p>
<p>When I finally understood that even invisible data needs a “road,” I realized how crucial this topic was to building fast, reliable networks.</p>
<h2 id="heading-different-types-of-transmission-media">Different Types of Transmission Media</h2>
<p>Transmission media are classified into two broad categories:</p>
<ol>
<li><p><strong>Guided Media</strong> (Wired): The data follows a specific path (like a road or railway). Common types include a Twisted Pair cable, a Coaxial cable, and a Fiber Optic cable.</p>
</li>
<li><p><strong>Unguided Media</strong> (Wireless): Data floats freely through the atmosphere, like radio signals or Wi-Fi. Types include Radio Waves, Microwaves, and Infrared Waves.</p>
</li>
</ol>
<p>Let’s dive into each of these types of transmission media in a bit more detail.</p>
<h3 id="heading-guided-transmission-media">Guided Transmission Media</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748674489096/fe9c0cfd-6aaf-4746-a129-8c994287a976.png" alt="Guided Transmission media" class="image--center mx-auto" width="634" height="425" loading="lazy"></p>
<h4 id="heading-1-twisted-pair-cable">1. Twisted Pair Cable</h4>
<p>This was the first cable I ever handled – it looked like two wires twisted together. Signals are transmitted as tiny voltage differences between the two copper conductors. By twisting the pair, electromagnetic interference picked up on one wire tends to be canceled out on the other, since each twist reverses their positions relative to the noise source.</p>
<p><strong>Features &amp; Use‑Cases:</strong></p>
<ul>
<li><p><strong>Structure</strong>: Two insulated copper wires twisted to reduce interference.</p>
</li>
<li><p><strong>Types</strong>:</p>
<ul>
<li><p><strong>Unshielded Twisted Pair (UTP)</strong>: Common in LANs, cheaper but more prone to noise.</p>
</li>
<li><p><strong>Shielded Twisted Pair (STP)</strong>: Has shielding for better noise protection.</p>
</li>
</ul>
</li>
<li><p><strong>Usage</strong>: Telephones, Ethernet.</p>
</li>
<li><p><strong>Bandwidth</strong>: Low to medium.</p>
</li>
<li><p><strong>Distance</strong>: Up to 100 meters (for UTP).</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748674630033/34e507b8-4c67-4e47-9275-a37dd48191e4.png" alt="Twisted pair cable" class="image--center mx-auto" width="326" height="191" loading="lazy"></p>
<h4 id="heading-2-coaxial-cable">2. Coaxial Cable</h4>
<p>I remember unscrewing one from the back of our old TV. A single copper core carries the signal; an insulating layer and an outer metal shield form a concentric geometry. The signal propagates as an electromagnetic wave confined between the inner conductor and shield, which also blocks external noise.</p>
<p><strong>Features &amp; Use‑Cases:</strong></p>
<ul>
<li><p><strong>Structure</strong>: A central copper core, surrounded by insulation, a metal shield, and an outer plastic cover.</p>
</li>
<li><p><strong>Advantages</strong>: Better shielding, higher bandwidth than UTP.</p>
</li>
<li><p><strong>Usage</strong>: Cable TV, broadband internet.</p>
</li>
<li><p><strong>Distance</strong>: Up to several kilometers with amplifiers.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748675087884/6a7d9a7c-a0a9-4780-b43d-69dd1d581a26.png" alt="Coaxial Cable" class="image--center mx-auto" width="326" height="191" loading="lazy"></p>
<h4 id="heading-3-fiber-optic-cable">3. Fiber Optic Cable</h4>
<p>This one blew my mind – light carrying data! Data is encoded into light pulses (laser or LED) sent down a glass or plastic core. Total internal reflection at the core–cladding interface traps light, allowing it to travel long distances with almost no loss.</p>
<p><strong>Features &amp; Use‑Cases:</strong></p>
<ul>
<li><p><strong>Structure</strong>: Glass or plastic core surrounded by cladding and a protective sheath.</p>
</li>
<li><p><strong>Types</strong>:</p>
<ul>
<li><p><strong>Single-Mode Fiber</strong>: For long distances, uses a laser.</p>
</li>
<li><p><strong>Multi-Mode Fiber</strong>: For shorter distances, uses LED.</p>
</li>
</ul>
</li>
<li><p><strong>Advantages</strong>:</p>
<ul>
<li><p>Immune to electromagnetic interference</p>
</li>
<li><p>Higher bandwidth and longer distances</p>
</li>
<li><p>More secure and reliable</p>
</li>
</ul>
</li>
<li><p><strong>Usage</strong>: Backbone of the internet, submarine cables, hospitals.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748675141484/627c2f1c-c6bb-4959-ae7e-5d59e427d3ae.png" alt="Fiber-optic Cable" class="image--center mx-auto" width="326" height="191" loading="lazy"></p>
<h3 id="heading-unguided-transmission-media">Unguided Transmission Media</h3>
<p>When you connect to Wi-Fi or use Bluetooth, you are relying on unguided media. These don’t need a cable – just air.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748675235793/0c0f16b4-e96c-4056-9240-c908fba813f8.png" alt="Wireless Communication" class="image--center mx-auto" width="326" height="191" loading="lazy"></p>
<p>There are several different kinds of unguided transmission media. Let’s talk about some of the most common.</p>
<h4 id="heading-1-radio-waves">1. Radio Waves</h4>
<p><strong>How It Works:</strong><br>Antennas convert electrical signals into electromagnetic waves (and vice versa). Radio frequencies (3 kHz–1 GHz) propagate omnidirectionally (or in broad beams) through the air and can diffract around obstacles.</p>
<ul>
<li><p><strong>Pros:</strong> Penetrates walls; easy broadcast to many receivers.</p>
</li>
<li><p><strong>Cons:</strong> Susceptible to interference and eavesdropping.</p>
</li>
<li><p><strong>Applications:</strong> FM/AM radio, Wi‑Fi (2.4 GHz band), Bluetooth, cordless phones.</p>
</li>
</ul>
<h4 id="heading-2-microwaves">2. Microwaves</h4>
<p><strong>How It Works:</strong><br>Highly directional beams (1 GHz–300 GHz) generated by parabolic dishes or waveguide antennas. Because they travel in straight lines (line‑of‑sight), they must be carefully aligned between towers or rooftop dishes.</p>
<ul>
<li><p><strong>Pros:</strong> High data rates, cellular backhaul, satellite links.</p>
</li>
<li><p><strong>Cons:</strong> Rain fade, clear path required, more expensive antennas.</p>
</li>
<li><p><strong>Applications:</strong> Mobile networks, satellite TV, point‑to‑point enterprise links.</p>
</li>
</ul>
<h4 id="heading-3-infrared">3. Infrared</h4>
<p><strong>How It Works:</strong><br>LED or laser diodes emit infrared light pulses, which are detected by photodiodes on the receiver. Because IR light cannot pass through walls, it works only in a confined, line‑of‑sight – or within a reflective “cone.”</p>
<ul>
<li><p><strong>Pros:</strong> Highly secure (confined to room), no RF interference.</p>
</li>
<li><p><strong>Cons:</strong> Very short range; blocked by obstacles; strict alignment.</p>
</li>
<li><p><strong>Applications:</strong> TV remotes, short‑range device pairing, some industrial sensors.</p>
</li>
</ul>
<h3 id="heading-comparison-table">Comparison Table</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Medium</strong></td><td><strong>Speed</strong></td><td><strong>Distance</strong></td><td><strong>Interference</strong></td><td><strong>Cost</strong></td><td><strong>Usage</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Twisted Pair</td><td>Low-Medium</td><td>~100m</td><td>High</td><td>Low</td><td>LAN, telephony</td></tr>
<tr>
<td>Coaxial</td><td>Medium</td><td>~2km (amplified)</td><td>Medium</td><td>Medium</td><td>Cable TV, broadband</td></tr>
<tr>
<td>Fiber Optic</td><td>Very High</td><td>&gt;60km (with repeaters)</td><td>Very Low</td><td>High</td><td>Backbone, high-speed</td></tr>
<tr>
<td>Radio</td><td>Low-Medium</td><td>Long (via towers)</td><td>High</td><td>Low</td><td>Wi-Fi, radio, Bluetooth</td></tr>
<tr>
<td>Microwave</td><td>High</td><td>Long (LOS)</td><td>Medium</td><td>High</td><td>Mobile, satellites</td></tr>
<tr>
<td>Infrared</td><td>Low</td><td>Short</td><td>Very Low</td><td>Low</td><td>Remotes, IR sensors</td></tr>
</tbody>
</table>
</div><hr>
<h3 id="heading-how-to-choose-the-right-transmission-medium">How to Choose the Right Transmission Medium</h3>
<p>When I set up my first home network, I had to think about speed, distance, and cost. That’s what engineers do when designing large networks, too.</p>
<p><strong>Questions to ask yourself or your team:</strong></p>
<ul>
<li><p>How far does the data need to travel?</p>
</li>
<li><p>How fast do I need the connection?</p>
</li>
<li><p>Can I afford high-end cables or equipment?</p>
</li>
<li><p>Is the environment prone to interference?</p>
</li>
</ul>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Scenario</td><td>Best Medium</td><td>Why &amp; How to Decide</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Home LAN &amp; Office Ethernet</strong></td><td>Cat6 UTP</td><td>Affordable, easy to install, handles Gigabit speeds up to 100 m.</td></tr>
<tr>
<td><strong>No‑Cable Wireless Access</strong></td><td>Wi‑Fi (2.4/5 GHz)</td><td>Easy coverage of rooms; choose 5 GHz for less interference, higher speed.</td></tr>
<tr>
<td><strong>Long‑Distance Fiber Backbone</strong></td><td>Single‑Mode Fiber</td><td>Minimal signal loss over tens of kilometers; vital for ISP backbones.</td></tr>
<tr>
<td><strong>Campus/Building Interconnect</strong></td><td>Multi‑Mode Fiber</td><td>Supports 10–100 Gbps across campus; lower cost than single‑mode for short runs.</td></tr>
<tr>
<td><strong>Point‑to‑Point Enterprise Link</strong></td><td>Microwave Link</td><td>Rapid deployment between buildings; ensure clear LOS and proper dish alignment.</td></tr>
<tr>
<td><strong>Industrial/Noisy Environments</strong></td><td>Shielded Twisted‑Pair or Fiber</td><td>STP resists EMI ; fiber is immune but costlier.</td></tr>
<tr>
<td><strong>Room‑Confined, Secure Control Signals</strong></td><td>Infrared</td><td>Perfect for IR‑controlled lighting or remote‑only devices in one room.</td></tr>
<tr>
<td><strong>Broad Wireless Broadcast</strong></td><td>Radio Waves</td><td>For wide‑area IoT sensors or broadcast audio; simple omnidirectional antennas.</td></tr>
</tbody>
</table>
</div><ol>
<li><p><strong>Define Distance &amp; Speed:</strong></p>
<ul>
<li><p>Short run (&lt;100 m) + moderate speed → UTP.</p>
</li>
<li><p>Long haul → fiber or microwave.</p>
</li>
</ul>
</li>
<li><p><strong>Assess Environment:</strong></p>
<ul>
<li><p>High EMI (factories) → fiber or STP.</p>
</li>
<li><p>Indoor home/office → UTP or Wi‑Fi.</p>
</li>
</ul>
</li>
<li><p><strong>Consider Mobility:</strong></p>
<ul>
<li>Devices moving around → wireless (Wi‑Fi, cellular).</li>
</ul>
</li>
<li><p><strong>Weigh Cost vs. Performance:</strong></p>
<ul>
<li><p>Budget LAN → UTP</p>
</li>
<li><p>Critical backbone → fiber</p>
</li>
</ul>
</li>
<li><p><strong>Security Needs:</strong></p>
<ul>
<li><p>Room‑confined control → infrared</p>
</li>
<li><p>Open campus → directional microwave or encrypted Wi‑Fi</p>
</li>
</ul>
</li>
</ol>
<p>By matching distance, throughput requirements, environmental constraints, and budget, you can select the transmission medium that delivers optimal real‑world performance, just as engineers do when designing networks that power everything from our smartphones to submarine data cables.</p>
<p>Learning about transmission media made me realize how much effort goes into a simple text message. Whether it’s a copper wire under the road or a beam of light under the ocean, there’s always a path connecting us.</p>
<p>I now see cables and antennas not just as hardware, but as lifelines of human connection. They are the highways of our digital lives.</p>
<h2 id="heading-chapter-5-network-topologies-how-we-structure-our-connections"><strong>Chapter 5: Network Topologies — How We Structure Our Connections</strong></h2>
<p>The word “topology”, in the context of networking, refers to how devices are arranged and connected. This chapter helps you see that the structure of a network is just as important as the technology it uses.</p>
<p>By the end of this chapter, you will:</p>
<ul>
<li><p>Understand what a network topology is and why it matters</p>
</li>
<li><p>Explore different types of physical and logical topologies</p>
</li>
<li><p>Learn the pros and cons of each layout (bus, ring, star, mesh, hybrid)</p>
</li>
<li><p>Recognize how topology affects performance, scalability, and fault tolerance</p>
</li>
</ul>
<h2 id="heading-what-is-topology">What is Topology?</h2>
<p>If you’ve ever arranged chairs in a room for a meeting, you’ve thought about topology. Should everyone face forward? Sit in a circle? Group up in clusters?</p>
<p>Networking topology is the same idea – it’s about the <strong>layout of devices and how they connect</strong>. Whether you're designing a small home LAN or a vast corporate network, choosing the right topology affects everything: speed, cost, troubleshooting, and scalability.</p>
<h2 id="heading-physical-vs-logical-topology">Physical vs Logical Topology</h2>
<h3 id="heading-physical-topology">Physical Topology</h3>
<p>This is what you can see – the actual layout of wires and devices.</p>
<p><strong>Example:</strong> You see computers in a classroom connected by cables to a central switch. That’s the physical topology.</p>
<h3 id="heading-logical-topology">Logical Topology</h3>
<p>This is how data flows, regardless of how devices are physically connected.</p>
<p><strong>Example:</strong> Even if computers are wired to a switch (star), the data may travel like a bus – this makes it a logical bus topology (more on this below).</p>
<p>It’s like a subway map vs. the actual underground tunnels – one shows the concept, the other shows the reality.</p>
<h2 id="heading-types-of-network-topologies">Types of Network Topologies</h2>
<p>Let’s go through the main types of network topologies. Each has strengths, weaknesses, and ideal use cases.</p>
<h3 id="heading-bus-topology">Bus Topology</h3>
<p>Imagine one long cable – all devices “tap into” it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748937876952/03749b9f-55a9-4864-8727-c82d5f8f7df6.png" alt="Bus Topology – Shiksha" class="image--center mx-auto" width="408" height="227" loading="lazy"></p>
<p>In a bus topology, a single backbone cable connects all devices.</p>
<ul>
<li><p><strong>Pros</strong>:</p>
<ul>
<li><p>Simple and cheap</p>
</li>
<li><p>Uses less cable</p>
</li>
</ul>
</li>
<li><p><strong>Cons</strong>:</p>
<ul>
<li><p>If the backbone fails, the whole network goes down</p>
</li>
<li><p>Difficult to troubleshoot</p>
</li>
<li><p>Performance degrades with more devices</p>
</li>
</ul>
</li>
<li><p><strong>Use case</strong>: Small temporary networks</p>
</li>
</ul>
<h3 id="heading-ring-topology">Ring Topology</h3>
<p>Here, each device connects to exactly two others, forming a circle.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748938093608/fbdd3460-1631-4959-abac-145c7ead69a1.png" alt="Ring Topology – Shiksha" class="image--center mx-auto" width="433" height="285" loading="lazy"></p>
<p>In this case, data travels in one direction, passing through each node.</p>
<ul>
<li><p><strong>Pros</strong>:</p>
<ul>
<li><p>Easy to install</p>
</li>
<li><p>Better than bus for managing traffic</p>
</li>
</ul>
</li>
<li><p><strong>Cons</strong>:</p>
<ul>
<li><p>Failure in one node can break the ring</p>
</li>
<li><p>Adding/removing nodes is disruptive</p>
</li>
</ul>
</li>
<li><p><strong>Use case</strong>: Token Ring networks (rare today)</p>
</li>
</ul>
<h3 id="heading-star-topology">Star Topology</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748938238120/78f568ef-4d7c-493a-a574-be59551f2bbf.png" alt="Star Topology – Shiksha" class="image--center mx-auto" width="288" height="230" loading="lazy"></p>
<p>This is what I used when setting up a LAN in my home. All devices connect to a central hub or switch.</p>
<ul>
<li><p><strong>Pros</strong>:</p>
<ul>
<li><p>Easy to install and manage</p>
</li>
<li><p>Failure of one device doesn’t affect the rest</p>
</li>
</ul>
</li>
<li><p><strong>Cons</strong>:</p>
<ul>
<li><p>If the central device fails, everything goes down</p>
</li>
<li><p>Requires more cable</p>
</li>
</ul>
</li>
<li><p><strong>Use case</strong>: Modern Ethernet networks</p>
</li>
</ul>
<h3 id="heading-mesh-topology">Mesh Topology</h3>
<p>This one fascinated me because of its complexity.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748938980213/81eb109a-1acb-4932-a8c0-17445591d660.png" alt="Mesh Topology – Shiksha" class="image--center mx-auto" width="468" height="263" loading="lazy"></p>
<p>In a mesh topology, every device is connected to every other device.</p>
<ul>
<li><p><strong>Pros</strong>:</p>
<ul>
<li><p>Redundant paths ensure reliability</p>
</li>
<li><p>Excellent fault tolerance</p>
</li>
</ul>
</li>
<li><p><strong>Cons</strong>:</p>
<ul>
<li><p>Expensive and complex to install</p>
</li>
<li><p>Requires lots of cabling</p>
</li>
</ul>
</li>
<li><p><strong>Use case</strong>: Military, critical systems, backbone networks</p>
</li>
</ul>
<h3 id="heading-hybrid-topology">Hybrid Topology</h3>
<p>Like a recipe with ingredients from different cuisines.</p>
<p><img src="https://images.shiksha.com/mediadata/images/articles/1709021924phpTqwiOP.jpeg" alt="What is Hybrid Topology – Shiksha" width="600" height="400" loading="lazy"></p>
<p>A hybrid topology works by combining two or more topologies.</p>
<ul>
<li><p><strong>Pros</strong>:</p>
<ul>
<li><p>Flexible and scalable</p>
</li>
<li><p>Can be tailored to specific needs</p>
</li>
</ul>
</li>
<li><p><strong>Cons</strong>:</p>
<ul>
<li>Complex design and management</li>
</ul>
</li>
<li><p><strong>Use case</strong>: Large organizations with diverse requirements</p>
</li>
</ul>
<h3 id="heading-comparison-table-1">Comparison Table</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Topology</strong></td><td><strong>Cost</strong></td><td><strong>Reliability</strong></td><td><strong>Scalability</strong></td><td><strong>Complexity</strong></td><td><strong>Use Case</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Bus</td><td>Low</td><td>Low</td><td>Low</td><td>Low</td><td>Small LANs</td></tr>
<tr>
<td>Ring</td><td>Medium</td><td>Medium</td><td>Low</td><td>Medium</td><td>Outdated systems</td></tr>
<tr>
<td>Star</td><td>Medium</td><td>Medium-High</td><td>High</td><td>Low</td><td>Homes, offices</td></tr>
<tr>
<td>Mesh</td><td>High</td><td>Very High</td><td>Medium</td><td>Very High</td><td>Data centers, military</td></tr>
<tr>
<td>Hybrid</td><td>High</td><td>High</td><td>Very High</td><td>High</td><td>Enterprises</td></tr>
</tbody>
</table>
</div><hr>
<h3 id="heading-how-to-choose-the-right-topology">How to Choose the Right Topology</h3>
<p>When I built my first network for a class project, I went with a <strong>star topology</strong>. Why? Because it was easy to set up and troubleshoot, and it matched our desk layout, with all PCs around a central switch. That hands-on experience taught me that the right topology isn’t just about wiring – it’s about reliability, cost, and how people use the network.</p>
<p>Think of it like planning a city:</p>
<ul>
<li><p>Where are the busiest hubs?</p>
</li>
<li><p>Do you need alternate routes in case one fails?</p>
</li>
<li><p>Can you maintain all the connections?</p>
</li>
</ul>
<h3 id="heading-common-network-topologies-and-when-to-use-them">Common Network Topologies and When to Use Them</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Topology</td><td>How It Works</td><td>When to Use It</td><td>Pros</td><td>Cons</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Bus</strong></td><td>All devices share a single backbone cable</td><td>Very small networks, temporary setups, or budget constraints</td><td>Cheap, minimal cabling</td><td>Hard to troubleshoot, poor scalability, one break = network down</td></tr>
<tr>
<td><strong>Star</strong></td><td>Devices connect to a central hub or switch</td><td>Home networks, classrooms, offices</td><td>Easy to manage, isolate issues, scalable</td><td>Hub is single point of failure</td></tr>
<tr>
<td><strong>Ring</strong></td><td>Each device connects to two others forming a closed loop</td><td>Legacy systems or specialized industrial networks</td><td>Predictable data flow, fair traffic management</td><td>Break in loop can halt the network unless dual ring used</td></tr>
<tr>
<td><strong>Mesh</strong></td><td>Every device connects to multiple others</td><td>Critical systems (e.g. military, finance), where uptime is vital</td><td>Highly fault-tolerant, redundant paths</td><td>Expensive, complex, heavy cabling</td></tr>
<tr>
<td><strong>Hybrid</strong></td><td>Mix of two or more topologies</td><td>Large enterprises or campuses</td><td>Flexible, optimized for different departments</td><td>Can be complex and costly to manage</td></tr>
</tbody>
</table>
</div><hr>
<h3 id="heading-how-to-actually-choose-a-topology-real-life-scenarios">How to Actually Choose a Topology (Real-Life Scenarios)</h3>
<p>Let’s move beyond theory. Here’s how you'd pick a topology depending on your network goals and constraints:</p>
<h4 id="heading-1-need-a-simple-setup-with-a-tight-budget">1. Need a simple setup with a tight budget?</h4>
<ul>
<li><p><strong>Choose:</strong> Bus or Star</p>
</li>
<li><p><strong>Why:</strong> Bus requires minimal cabling (but be warned—it’s fragile); Star uses affordable switches and is easy to expand.</p>
</li>
<li><p><strong>Example:</strong> Setting up a temporary lab or a network for a rural clinic.</p>
</li>
</ul>
<h4 id="heading-2-setting-up-a-home-or-small-office">2. Setting up a home or small office?</h4>
<ul>
<li><p><strong>Choose:</strong> Star</p>
</li>
<li><p><strong>Why:</strong> It mirrors how devices are physically placed. One faulty PC won’t crash the whole network.</p>
</li>
<li><p><strong>Example:</strong> Wi-Fi router (the central node) with laptops, smart TVs, and printers.</p>
</li>
</ul>
<h4 id="heading-3-running-a-business-with-multiple-departments">3. Running a business with multiple departments?</h4>
<ul>
<li><p><strong>Choose:</strong> Hybrid (Star + Mesh or Star + Ring)</p>
</li>
<li><p><strong>Why:</strong> Combine flexibility with reliability. Use star for offices, mesh for server interconnects.</p>
</li>
<li><p><strong>Example:</strong> A university with classrooms (star) and data centers (mesh).</p>
</li>
</ul>
<h4 id="heading-4-downtime-is-a-dealbreaker">4. Downtime is a dealbreaker?</h4>
<ul>
<li><p><strong>Choose:</strong> Mesh</p>
</li>
<li><p><strong>Why:</strong> Redundant paths keep communication alive even if several links fail.</p>
</li>
<li><p><strong>Example:</strong> Military control center or emergency dispatch system.</p>
</li>
</ul>
<h4 id="heading-5-working-with-legacy-systems">5. Working with legacy systems?</h4>
<ul>
<li><p><strong>Choose:</strong> Ring</p>
</li>
<li><p><strong>Why:</strong> Some older systems (like token ring networks or SONET) require ring layouts.</p>
</li>
<li><p><strong>Example:</strong> Legacy manufacturing networks that still run on ring-based designs.</p>
</li>
</ul>
<h4 id="heading-6-expecting-rapid-growth">6. Expecting rapid growth?</h4>
<ul>
<li><p><strong>Choose:</strong> Star or Hybrid</p>
</li>
<li><p><strong>Why:</strong> You can easily add more nodes to the central hub or integrate new segments.</p>
</li>
<li><p><strong>Example:</strong> A startup anticipating more staff and devices within 6–12 months.</p>
</li>
</ul>
<h3 id="heading-tips-from-experience">Tips from Experience</h3>
<ul>
<li><p><strong>Think long-term</strong>: Design for tomorrow’s load, not just today’s.</p>
</li>
<li><p><strong>Plan for failures</strong>: Even if you don’t need full mesh, maybe add backup links for your star’s hub.</p>
</li>
<li><p><strong>Sketch the layout</strong>: Visualizing devices and data flow helps you pick the best design.</p>
</li>
<li><p><strong>Consider wireless topologies too</strong>: For mobile or flexible environments, wireless mesh or infrastructure-based topologies might be better than wired ones.</p>
</li>
</ul>
<p>Just like roads and power lines shape how a city grows, your network topology shapes how your digital systems evolve. The best layout isn’t the one with the fanciest name – it’s the one that fits your users, your budget, and your goals.</p>
<p>Choose thoughtfully, and your network becomes more than wires – it becomes infrastructure for productivity, connection, and growth.</p>
<p>Network topology is the blueprint for that digital city. When done right, everything flows. When it’s messy, things get congested, slow, or fail. And that’s why I now look at every network not just as wires and switches, but as architecture, with a purpose and design.</p>
<h2 id="heading-chapter-6-the-osi-model-understanding-layers-of-communication"><strong>Chapter 6: The OSI Model — Understanding Layers of Communication</strong></h2>
<p>The OSI model is like a translator – it helps all types of systems speak the same language. And it’s everywhere.</p>
<p>In this chapter, you will:</p>
<ul>
<li><p>Understand what the OSI model is and why it was created</p>
</li>
<li><p>Learn what each of the 7 layers does</p>
</li>
<li><p>Discover how the layers work together during communication</p>
</li>
<li><p>Apply real-life analogies to remember each layer’s role</p>
</li>
</ul>
<h2 id="heading-what-is-the-osi-model">What is the OSI Model?</h2>
<p>Picture this: you want to send a letter. You write it 📝 → put it in an envelope ✉️ → mail it 📮 → it goes to your friend’s house 🏠 → they open it 👐 → and read it 👀.</p>
<p>That’s basically how the <strong>OSI Model</strong> works. The OSI (Open Systems Interconnection) model is a conceptual framework that describes <strong>how data moves from one device to another</strong> in a network. Instead of all systems operating differently, the OSI model helps break down communication into 7 distinct layers.</p>
<p>Each layer has a specific task, and together they make communication structured, understandable, and interoperable.</p>
<p>Developed by the <strong>International Organization for Standardization (ISO)</strong>, the OSI model was created to provide a universal standard for different systems to communicate.</p>
<p>Think of it like this: You’re building a house. You wouldn’t put the roof before the walls. Similarly, data follows an order, moving through each of these layers – from sender to receiver.</p>
<p>The 7 layers of the OSI model are:</p>
<ol>
<li><p><strong>Application</strong> (your browser or app)</p>
</li>
<li><p><strong>Presentation</strong> (formatting, encrypting)</p>
</li>
<li><p><strong>Session</strong> (starting/ending chats)</p>
</li>
<li><p><strong>Transport</strong> (reliable delivery)</p>
</li>
<li><p><strong>Network</strong> (finding the route)</p>
</li>
<li><p><strong>Data Link</strong> (organizing the data)</p>
</li>
<li><p><strong>Physical</strong> (the actual wires or Wi-Fi)</p>
</li>
</ol>
<p>It’s teamwork that makes the stream work!</p>
<p>An easy mnemonic I used to memorize them (from top to bottom): <strong>“All People Seem To Need Data Processing.”</strong></p>
<p>Let’s explore each layer from the bottom (Layer 1) to the top (Layer 7):</p>
<h3 id="heading-layer-1-physical-layer">Layer 1 – Physical Layer</h3>
<p>This is the <strong>hardware level</strong>.</p>
<ul>
<li><p>Handles: cables, switches, voltages, pins</p>
</li>
<li><p>Responsible for: physically transmitting raw bits (0s and 1s)</p>
</li>
<li><p>Example: Ethernet cables, fiber optics</p>
</li>
</ul>
<p><strong>Analogy</strong>: The roads on which data travels.</p>
<h3 id="heading-layer-2-data-link-layer">Layer 2 – Data Link Layer</h3>
<p>Ensures reliable transfer across the physical link.</p>
<ul>
<li><p>Handles: MAC addresses, framing, error detection</p>
</li>
<li><p>Divided into:</p>
<ul>
<li><p><strong>Logical Link Control (LLC)</strong></p>
</li>
<li><p><strong>Media Access Control (MAC)</strong></p>
</li>
</ul>
</li>
<li><p>Example: Switches, MAC addressing</p>
</li>
</ul>
<p><strong>Analogy</strong>: Street signs and traffic signals managing who goes when.</p>
<h3 id="heading-layer-3-network-layer">Layer 3 – Network Layer</h3>
<p>This is about <strong>routing</strong> – finding the best path to the destination.</p>
<ul>
<li><p>Handles: IP addresses, packet forwarding</p>
</li>
<li><p>Devices: Routers</p>
</li>
<li><p>Protocols: IP, ICMP</p>
</li>
</ul>
<p><strong>Analogy</strong>: Google Maps calculating the best route.</p>
<h3 id="heading-layer-4-transport-layer">Layer 4 – Transport Layer</h3>
<p>Responsible for <strong>end-to-end communication</strong> and reliability.</p>
<ul>
<li><p>Handles: segmentation, flow control, error correction</p>
</li>
<li><p>Protocols: TCP (reliable), UDP (fast but no guarantee)</p>
</li>
</ul>
<p><strong>Analogy</strong>: Your personal driver, making sure you arrive safely.</p>
<h3 id="heading-layer-5-session-layer">Layer 5 – Session Layer</h3>
<p>This layer manages <strong>dialogues</strong> (sessions) between systems.</p>
<ul>
<li>Handles: session setup, management, and termination</li>
</ul>
<p><strong>Analogy</strong>: A host managing who gets to speak in a Zoom meeting.</p>
<h3 id="heading-layer-6-presentation-layer">Layer 6 – Presentation Layer</h3>
<p>Responsible for <strong>data formatting and translation</strong>.</p>
<ul>
<li><p>Handles: encryption, compression, data conversion</p>
</li>
<li><p>Example: JPEG, MP3, SSL, ASCII, EBCDIC</p>
</li>
</ul>
<p><strong>Analogy</strong>: A translator ensuring the data is understood.</p>
<h3 id="heading-layer-7-application-layer">Layer 7 – Application Layer</h3>
<p>The layer closest to the <strong>user</strong>.</p>
<ul>
<li><p>Handles: user interfaces, network services</p>
</li>
<li><p>Protocols: HTTP, FTP, SMTP, DNS</p>
</li>
</ul>
<p><strong>Analogy</strong>: The app you open – browser, email client, and so on.</p>
<h3 id="heading-communication-flow">Communication Flow</h3>
<p>When I send a message:</p>
<ul>
<li><p>It <strong>starts at Layer 7</strong> and goes down to Layer 1 at my device</p>
</li>
<li><p>Then <strong>travels</strong> across the medium</p>
</li>
<li><p>And <strong>climbs back up</strong> from Layer 1 to Layer 7 on the receiving device</p>
</li>
</ul>
<p>Each layer talks to its “peer” on the other device using a protocol.</p>
<h3 id="heading-why-the-osi-model-matters">Why the OSI Model Matters</h3>
<p>The OSI model is more than theory. It’s a <strong>map of the journey your data takes</strong> that helped give structure to the chaos. It’s also helped me think systematically about problems, identify where things break down, and appreciate the complexity behind “just sending a message.” When debugging a network problem, I ask:</p>
<ul>
<li><p>Is the cable plugged in? (Layer 1)</p>
</li>
<li><p>Is the MAC address correct? (Layer 2)</p>
</li>
<li><p>Can I ping the destination? (Layer 3)</p>
</li>
<li><p>Is the application service running? (Layer 7)</p>
</li>
</ul>
<p>It gave me a checklist to go through, along with some clarity.</p>
<p>Whether you’re a student or a network pro, these 7 layers are your best friends.</p>
<h2 id="heading-tcpip-the-real-mvp-of-the-internet"><strong>TCP/IP: The Real MVP of the Internet</strong></h2>
<p>While the OSI model is an ideal learning tool, the <strong>TCP/IP model</strong> is what the internet actually uses. It has only four layers, combining some of the OSI layers for simplicity and practicality:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>TCP/IP Layer</strong></td><td><strong>Corresponds to OSI Layers</strong></td><td><strong>Examples</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Application</td><td>Layers 5–7 (Application to Session)</td><td>HTTP, FTP, DNS, SMTP</td></tr>
<tr>
<td>Transport</td><td>Layer 4 (Transport)</td><td>TCP, UDP</td></tr>
<tr>
<td>Internet</td><td>Layer 3 (Network)</td><td>IP, ICMP</td></tr>
<tr>
<td>Network Access / Link</td><td>Layers 1–2 (Physical + Data Link)</td><td>Ethernet, Wi-Fi, MAC addresses</td></tr>
</tbody>
</table>
</div><p><strong>Why TCP/IP Matters:</strong></p>
<ul>
<li><p><strong>Scalable</strong>: It powers everything from home routers to global telecom infrastructure.</p>
</li>
<li><p><strong>Interoperable</strong>: Works across all hardware, operating systems, and devices.</p>
</li>
<li><p><strong>Fault-tolerant</strong>: TCP handles dropped packets, reordering, and error checking.</p>
</li>
<li><p><strong>Backbone of the Internet</strong>: Every website, email, or Zoom call runs over TCP/IP.</p>
</li>
</ul>
<h3 id="heading-how-tcpip-works-simplified-walkthrough">How TCP/IP Works (Simplified Walkthrough)</h3>
<p>Let’s say you open your browser and type in <code>www.example.com</code>.</p>
<ol>
<li><p><strong>Application Layer</strong> (HTTP): Your browser sends a request for a web page.</p>
</li>
<li><p><strong>Transport Layer</strong> (TCP): The request is broken into segments, with each piece numbered and prepared for reliable delivery.</p>
</li>
<li><p><strong>Internet Layer</strong> (IP): Each segment gets an IP address and is routed across networks.</p>
</li>
<li><p><strong>Network Access Layer</strong>: The data is turned into frames and signals, then physically transmitted over the internet (via cables or wireless).</p>
</li>
</ol>
<p>At the other end, the process reverses, and you see the web page appear on your screen.</p>
<h3 id="heading-osi-vs-tcpip-why-learn-both">OSI vs. TCP/IP: Why Learn Both?</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>OSI</strong></td><td><strong>TCP/IP</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Conceptual, educational model</td><td>Practical, real-world protocol suite</td></tr>
<tr>
<td>7 distinct layers</td><td>4 simplified layers</td></tr>
<tr>
<td>Rarely used directly in implementation</td><td>Foundation of the internet</td></tr>
</tbody>
</table>
</div><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750099098223/f767b099-c0db-4810-ab48-eacd95d8cf08.png" alt="OSI Model vs TCP/IP Model" class="image--center mx-auto" width="598" height="405" loading="lazy"></p>
<p>Think of the OSI model as a textbook diagram – helpful for troubleshooting and interviews. TCP/IP is the actual engine – streamlined and optimized for real-world communication.</p>
<h2 id="heading-chapter-7-protocols-and-ports-how-rules-and-doors-guide-communication"><strong>Chapter 7: Protocols and Ports — How Rules and Doors Guide Communication</strong></h2>
<p>Protocols and ports are the rules and gates that make it all happen smoothly. This chapter helps you appreciate how structured communication actually is.</p>
<p>By the end of this chapter, you will:</p>
<ul>
<li><p>Understand what protocols are and why they’re essential</p>
</li>
<li><p>Learn about standard protocols used in networking</p>
</li>
<li><p>Explore the concept of ports and their numbers</p>
</li>
<li><p>Discover how protocols and ports work together to manage communication</p>
</li>
</ul>
<h2 id="heading-the-importance-of-protocols-and-ports">The Importance of Protocols and Ports</h2>
<p>When I tried setting up a local web server for the first time, nothing loaded. It took me a while to realize I hadn’t opened the right port or used the correct protocol.</p>
<p><strong>Protocols</strong> are the rules that devices follow when talking to each other. <strong>Ports</strong> are like doors that allow specific types of data to come in and go out.</p>
<p>Without protocols and ports, communication would be total chaos.</p>
<h2 id="heading-what-is-a-protocol-1">What is a Protocol?</h2>
<p>A <strong>protocol</strong> is an agreed-upon set of rules for sending and receiving data.</p>
<p>Think of it like:</p>
<ul>
<li><p>A language: both sides must understand it</p>
</li>
<li><p>A traffic system: everyone follows the same rules to avoid crashes</p>
</li>
</ul>
<h3 id="heading-characteristics-of-good-protocols">Characteristics of Good Protocols</h3>
<p>For a protocol to be effective in communication, it must clearly define how data is structured, understood, and managed in time. Let’s break that down:</p>
<h4 id="heading-1-syntax-the-format-and-structure-of-the-data">1. Syntax – The Format and Structure of the Data</h4>
<p>Think of syntax like grammar in language. It defines:</p>
<ul>
<li><p><strong>Data format</strong> (for example, header, payload, footer)</p>
</li>
<li><p><strong>Order of fields</strong> in a message</p>
</li>
<li><p><strong>Encoding rules</strong> (for example, binary, ASCII, JSON, XML)</p>
</li>
</ul>
<p><strong>Example:</strong> In an email protocol like SMTP, the syntax might require that the sender and recipient addresses come in a specific format like <code>MAIL FROM:</code> and <code>RCPT TO:</code>.</p>
<p>A good protocol syntax is:</p>
<ul>
<li><p><strong>Consistent</strong> and <strong>unambiguous</strong></p>
</li>
<li><p>Easy to <strong>parse</strong> by machines</p>
</li>
<li><p>Designed to <strong>minimize errors</strong> in interpretation</p>
</li>
</ul>
<h4 id="heading-2-semantics-the-meaning-of-each-field">2. Semantics – The Meaning of Each Field</h4>
<p>Semantics defines what each piece of data means – what should be done with it.</p>
<ul>
<li><p><strong>What does a "200 OK" response mean in HTTP?</strong> (It means the request was successful.)</p>
</li>
<li><p><strong>What does a SYN flag mean in TCP?</strong> (It initiates a new connection.)</p>
</li>
</ul>
<p>Good protocol semantics:</p>
<ul>
<li><p>Ensure that both sender and receiver interpret the data in the same way</p>
</li>
<li><p>Clearly define error codes, commands, and responses</p>
</li>
<li><p>Support meaningful actions tied to each instruction</p>
</li>
</ul>
<h4 id="heading-3-timing-when-and-how-fast-to-communicate">3. Timing – When and How Fast to Communicate</h4>
<p>Timing refers to:</p>
<ul>
<li><p><strong>When messages are sent</strong> (synchronization)</p>
</li>
<li><p><strong>How fast</strong> messages should arrive (data rate)</p>
</li>
<li><p><strong>How long</strong> to wait before assuming failure (timeouts)</p>
</li>
</ul>
<p>A good protocol timing design:</p>
<ul>
<li><p>Prevents collisions (two devices sending at the same time)</p>
</li>
<li><p>Supports flow control to avoid overwhelming slower devices</p>
</li>
<li><p>Includes retransmission logic in case of delay or loss</p>
</li>
</ul>
<h3 id="heading-common-networking-protocols">Common Networking Protocols</h3>
<p>Before diving into details, here’s some context: A networking protocol is like a shared language for computers. It ensures that devices can communicate, share data, and coordinate actions reliably and securely.</p>
<h4 id="heading-tcp-transmission-control-protocol">TCP – Transmission Control Protocol</h4>
<p>TCP is the backbone of reliable internet communication.</p>
<p>It is:</p>
<ul>
<li><p><strong>Connection-oriented</strong>: A session is established before data is sent.</p>
</li>
<li><p><strong>Reliable</strong>: It ensures all data arrives correctly and in order using acknowledgments and retransmission.</p>
</li>
<li><p><strong>Error-checked</strong>: Includes checksums to detect and correct corruption.</p>
</li>
</ul>
<p>You use TCP in Web browsing (HTTP/HTTPS), email (SMTP), and file transfers (FTP). It’s like mailing a package with tracking and a required signature on delivery.</p>
<h4 id="heading-udp-user-datagram-protocol">UDP – User Datagram Protocol</h4>
<p>UDP is lightweight, fast, and doesn’t worry about delivery guarantees.</p>
<p>It is:</p>
<ul>
<li><p><strong>Connectionless</strong>: No handshake or setup, just send and forget.</p>
</li>
<li><p><strong>Low overhead</strong>: No acknowledgments or retransmission.</p>
</li>
<li><p><strong>Faster</strong> than TCP, but riskier for data loss.</p>
</li>
</ul>
<p>You use it in online gaming, voice calls (VoIP), and live video streaming. It’s like shouting a message across a noisy room – quick, but no guarantee it’ll be heard.</p>
<h4 id="heading-http-https-hypertext-transfer-protocol">HTTP / HTTPS – HyperText Transfer Protocol</h4>
<p>HTTP is the protocol of the web – it enables your browser to request and display web pages.</p>
<p>It is:</p>
<ul>
<li><p><strong>Stateless</strong>: Each request is independent.</p>
</li>
<li><p><strong>Based on the request-response model</strong>: Client sends a request; server responds.</p>
</li>
</ul>
<p>HTTPS adds encryption via SSL/TLS, making it secure for sensitive data (for example, online banking, logins).</p>
<p>It’s used for activities like browsing websites and in REST APIs.</p>
<h4 id="heading-ftp-file-transfer-protocol">FTP – File Transfer Protocol</h4>
<p>FTP is a classic protocol for transferring files between devices on a network.</p>
<p>It:</p>
<ul>
<li><p>Works in client-server mode</p>
</li>
<li><p>Requires authentication (username/password)</p>
</li>
<li><p>Is not secure on its own – can be enhanced with FTPS or replaced by SFTP (uses SSH)</p>
</li>
</ul>
<p>You can use it for website hosting and file backup systems.</p>
<h4 id="heading-smtp-pop3-imap-email-protocols">SMTP, POP3, IMAP – Email Protocols</h4>
<p>These are the three common email protocols, and each has its own features:</p>
<ul>
<li><p><strong>SMTP</strong> (Simple Mail Transfer Protocol): Used to send email from clients to servers or between servers.</p>
</li>
<li><p><strong>POP3</strong> (Post Office Protocol v3): Downloads emails to the device and usually deletes them from the server.</p>
</li>
<li><p><strong>IMAP</strong> (Internet Message Access Protocol): Keeps email on the server and synchronizes across devices.</p>
</li>
</ul>
<p>These are used in email clients like Outlook, Thunderbird, and Apple Mail.</p>
<h4 id="heading-dns-domain-name-system"><strong>DNS – Domain Name System</strong></h4>
<p>DNS is the internet’s phonebook – it converts human-readable names (like <code>google.com</code>) into IP addresses.</p>
<ul>
<li><p>Hierarchical and distributed system</p>
</li>
<li><p>Uses caching to speed up lookups</p>
</li>
<li><p>Works behind the scenes of every website visit</p>
</li>
</ul>
<p>It’s used in every internet-connected application that uses domain names.</p>
<h3 id="heading-what-is-a-port">What is a Port?</h3>
<p>A <strong>port</strong> is a virtual door on a device that allows certain kinds of data through.</p>
<p>Each application or service uses a specific <strong>port number</strong>, which ranges from 0 to 65535.</p>
<h4 id="heading-port-ranges">Port Ranges</h4>
<ul>
<li><p><strong>Well-known ports</strong>: 0–1023 (assigned to common services)</p>
</li>
<li><p><strong>Registered ports</strong>: 1024–49151 (used by user processes)</p>
</li>
<li><p><strong>Dynamic/Private ports</strong>: 49152–65535 (temporary or private use)</p>
</li>
</ul>
<h4 id="heading-common-port-numbers">Common Port Numbers</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Service</td><td>Protocol</td><td>Port</td></tr>
</thead>
<tbody>
<tr>
<td>HTTP</td><td>TCP</td><td>80</td></tr>
<tr>
<td>HTTPS</td><td>TCP</td><td>443</td></tr>
<tr>
<td>FTP</td><td>TCP</td><td>21</td></tr>
<tr>
<td>SSH</td><td>TCP</td><td>22</td></tr>
<tr>
<td>DNS</td><td>UDP/TCP</td><td>53</td></tr>
<tr>
<td>SMTP</td><td>TCP</td><td>25</td></tr>
<tr>
<td>POP3</td><td>TCP</td><td>110</td></tr>
<tr>
<td>IMAP</td><td>TCP</td><td>143</td></tr>
</tbody>
</table>
</div><h3 id="heading-how-protocols-and-ports-work-together">How Protocols and Ports Work Together</h3>
<p>Imagine you’re throwing a party:</p>
<ul>
<li><p><strong>Protocol</strong>: The invitation format – RSVP, dress code, rules.</p>
</li>
<li><p><strong>Port</strong>: The door your friends enter through.</p>
</li>
</ul>
<p>A web browser knows to use <strong>HTTP (protocol)</strong> on <strong>port 80</strong>. A secure connection will use <strong>HTTPS</strong> on <strong>port 443</strong>.</p>
<p>Your computer and servers use these pairings to know what type of data to expect.</p>
<p>Once I understood protocols and ports, troubleshooting network issues got easier. Suddenly, firewall rules, web server configs, and error messages started to make sense.</p>
<p>Protocols ensure everyone speaks the same language. Ports ensure everyone enters through the correct door.</p>
<p>They are the silent heroes of every network conversation.</p>
<h2 id="heading-chapter-8-ip-addressing-and-subnetting-naming-and-organizing-the-network"><strong>Chapter 8: IP Addressing and Subnetting — Naming and Organizing the Network</strong></h2>
<p>When I first saw an IP address like 192.168.0.1, I didn’t think much of it. But now I see it for what it is, the digital address that tells data where to go. In this chapter, you will learn:</p>
<ul>
<li><p>What an IP address is and why it's necessary</p>
</li>
<li><p>The difference between IPv4 and IPv6</p>
</li>
<li><p>How subnetting works and why it's useful</p>
</li>
<li><p>How to calculate and interpret IP ranges, subnet masks, and CIDR notation</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748436668531/8e7330cf-35f0-4c3d-a628-46261698b331.png" alt="IP Adress" class="image--center mx-auto" width="549" height="358" loading="lazy"></p>
<p>Imagine trying to mail a letter without an address – it would be lost forever. The same applies to data on a network. Every device needs a unique identifier called an <strong>IP address</strong> to send and receive information correctly.</p>
<p>IP addressing ensures that when I request a webpage, my data comes back to <strong>me</strong>, not someone else on the network.</p>
<h2 id="heading-what-is-an-ip-address">What is an IP Address?</h2>
<p>An IP address (Internet Protocol address) is a unique number assigned to every device on a network.</p>
<p>Every device on a network needs an IP address to identify it – like a phone number for computers. There are two main versions of IP addresses: <strong>IPv4</strong> and <strong>IPv6</strong>.</p>
<h3 id="heading-ipv4-vs-ipv6">IPv4 vs. IPv6</h3>
<p><strong>IPv4 (Internet Protocol version 4)</strong> is the older, more widely used system. It uses a <strong>32-bit address format</strong>, written as four numbers (each 0–255) separated by dots—for example: <code>192.168.1.1</code>. This format allows for about <strong>4.3 billion</strong> unique addresses.</p>
<p>But with the explosion of internet-connected devices, we quickly ran out of IPv4 addresses. That’s why <strong>IPv6 (Internet Protocol version 6)</strong> was introduced.IPv6 uses a <strong>128-bit address format</strong>, written in hexadecimal and separated by colons: <code>2001:0db8:85a3:0000:0000:8a2e:0370:7334</code>. This allows for a virtually unlimited number of addresses – <strong>over 340 undecillion</strong> (that’s 340 followed by 36 zeros)!</p>
<p>Let’s see a quick breakdown of the key details of each protocol:</p>
<h4 id="heading-ipv4-address-format">IPv4 Address Format</h4>
<ul>
<li><p>Composed of four numbers separated by dots</p>
</li>
<li><p>Each number ranges from 0 to 255 (i.e., 8 bits per number)</p>
</li>
<li><p>Total: 32 bits (4 x 8)</p>
</li>
<li><p>Example: <code>192.168.1.1</code></p>
</li>
</ul>
<h4 id="heading-ipv6-address-format">IPv6 Address Format</h4>
<ul>
<li><p>Created to solve the address shortage in IPv4</p>
</li>
<li><p>Composed of eight blocks of hexadecimal values</p>
</li>
<li><p>Total: 128 bits</p>
</li>
<li><p>Example: <code>2001:0db8:85a3:0000:0000:8a2e:0370:7334</code></p>
</li>
</ul>
<h3 id="heading-the-old-ipv4-class-system">The Old IPv4 Class System</h3>
<p>Originally, IPv4 addresses were grouped into <strong>classes</strong> to simplify allocation:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Class</td><td>Range</td><td>Default Subnet Mask</td><td>Use</td></tr>
</thead>
<tbody>
<tr>
<td>A</td><td>1.0.0.0 – 126.0.0.0</td><td>255.0.0.0</td><td>Large networks</td></tr>
<tr>
<td>B</td><td>128.0.0.0 – 191.255.0.0</td><td>255.255.0.0</td><td>Medium networks</td></tr>
<tr>
<td>C</td><td>192.0.0.0 – 223.255.255.0</td><td>255.255.255.0</td><td>Small networks</td></tr>
<tr>
<td>D</td><td>224.0.0.0 – 239.255.255.255</td><td>N/A</td><td>Multicasting</td></tr>
<tr>
<td>E</td><td>240.0.0.0 – 255.255.255.255</td><td>N/A</td><td>Reserved for future use</td></tr>
</tbody>
</table>
</div><p>But this system was too rigid. It wasted address space by assigning fixed block sizes, even when a network didn’t need that much.</p>
<h3 id="heading-enter-cidr-classless-inter-domain-routing">Enter CIDR: Classless Inter-Domain Routing</h3>
<p><strong>CIDR (pronounced "cider")</strong> replaced the old class system in the 1990s. CIDR allows for more flexible and efficient allocation of IP addresses. Instead of using predefined classes, CIDR uses a <strong>prefix length</strong> to specify how many bits represent the network portion.</p>
<ul>
<li>Example: <code>192.168.1.0/24</code>: This means the first 24 bits are the network, and the last 8 bits are available for hosts.</li>
</ul>
<p>CIDR made it easier to split (subnet) networks and slow the exhaustion of IPv4 addresses. We’ll discuss this more below.</p>
<h3 id="heading-does-ipv6-use-classes">Does IPv6 Use Classes?</h3>
<p>No, IPv6 does not use classes. It was designed from the start to avoid the inefficiencies of the class system. Instead, it uses a hierarchical structure and <strong>prefix notation</strong> similar to CIDR. IPv6 addresses are divided into:</p>
<ul>
<li><p><strong>Global unicast</strong> (like public IPv4 addresses)</p>
</li>
<li><p><strong>Link-local</strong> (used within a local network)</p>
</li>
<li><p><strong>Multicast</strong> (send to many devices at once)</p>
</li>
</ul>
<p>IPv6’s design naturally supports efficient routing and address assignment without needing "classes" as a workaround.</p>
<h2 id="heading-understanding-subnetting-and-related-concepts">Understanding Subnetting and Related Concepts</h2>
<p>After learning about IP addresses – especially the difference between IPv4 and IPv6 – it’s important to understand how networks manage and organize these addresses. That’s where <strong>subnetting</strong> comes in.</p>
<h3 id="heading-what-is-subnetting">What Is Subnetting?</h3>
<p>Think of a large network like a school compound. Subnetting is like dividing the school into classrooms or departments. It’s the process of dividing a larger network into smaller, more manageable subnetworks (subnets).</p>
<p>Subnetting helps with:</p>
<ul>
<li><p><strong>Efficient use of IP addresses</strong>: You don’t need to assign a huge range of addresses when only a few devices are needed.</p>
</li>
<li><p><strong>Network organization</strong>: Departments or teams can be separated into their own subnets.</p>
</li>
<li><p><strong>Better performance and security</strong>: Traffic stays local within each subnet, and issues in one subnet don’t affect the whole network.</p>
</li>
</ul>
<h3 id="heading-how-subnet-masks-work">How Subnet Masks Work</h3>
<p>To understand subnetting, we need to talk about <strong>subnet masks</strong>.</p>
<p>Every IPv4 address is divided into two parts:</p>
<ul>
<li><p>The <strong>network portion</strong> tells you <em>which</em> network it belongs to.</p>
</li>
<li><p>The <strong>host portion</strong> tells you <em>which specific device</em> (computer, phone, printer, and so on) on that network.</p>
</li>
</ul>
<p>A <strong>subnet mask</strong> tells us how to separate those two parts.</p>
<h4 id="heading-example">Example:</h4>
<ul>
<li><p><strong>IP Address</strong>: <code>192.168.1.10</code></p>
</li>
<li><p><strong>Subnet Mask</strong>: <code>255.255.255.0</code></p>
</li>
</ul>
<p>This means:</p>
<ul>
<li><p>The first three numbers of the IP address (<code>192.168.1</code>) represent the network.</p>
</li>
<li><p>The last number (<code>10</code>) identifies the specific host on that network.</p>
</li>
</ul>
<p>The subnet mask acts like a filter that shows which part of the IP is fixed (network) and which part can vary (host).</p>
<h3 id="heading-cidr-notation-a-modern-alternative">CIDR Notation: A Modern Alternative</h3>
<p>You might also see IP addresses written like this: <code>192.168.1.0/24</code>. This is called <strong>CIDR notation</strong> (Classless Inter-Domain Routing), which we discussed briefly above.</p>
<p>CIDR is a more flexible and compact way to express IP addresses and subnet masks. The <code>/24</code> tells us that the <strong>first 24 bits</strong> of the address are used for the network. The rest are for hosts.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>CIDR Notation</td><td>Subnet Mask</td><td>Number of Hosts</td></tr>
</thead>
<tbody>
<tr>
<td>/24</td><td>255.255.255.0</td><td>256 IPs (254 usable)</td></tr>
<tr>
<td>/26</td><td>255.255.255.192</td><td>64 IPs (62 usable)</td></tr>
<tr>
<td>/30</td><td>255.255.255.252</td><td>4 IPs (2 usable)</td></tr>
</tbody>
</table>
</div><p>CIDR allows networks to be split or combined more precisely than the old Class A/B/C system, which had fixed sizes.</p>
<h3 id="heading-how-to-calculate-a-subnet">How to Calculate a Subnet</h3>
<p>Let’s walk through a basic example.</p>
<p>You’re given the network: <code>192.168.1.0/26</code></p>
<ol>
<li><p>The <code>/26</code> means 26 bits are used for the network and 6 bits remain for hosts (since IPv4 has 32 bits total).</p>
</li>
<li><p>Using the formula <code>2^number_of_host_bits</code>, you get <code>2^6 = 64</code> total addresses.</p>
</li>
<li><p>But 2 addresses are reserved: one for the network itself, and one for the broadcast address.</p>
</li>
<li><p>So, you’re left with 62 usable addresses in that subnet.</p>
</li>
</ol>
<p>This is helpful when dividing a network among departments, buildings, or device types.</p>
<h3 id="heading-public-vs-private-ip-addresses">Public vs Private IP Addresses</h3>
<p>Not all IP addresses are meant for use on the open internet. Some are private, used within internal networks.</p>
<h4 id="heading-private-ip-addresses">Private IP Addresses:</h4>
<ul>
<li><p>Not routed over the internet.</p>
</li>
<li><p>Used in homes, schools, and offices.</p>
</li>
<li><p>Can be reused in different networks without conflict.</p>
</li>
</ul>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Range</td><td>Purpose</td></tr>
</thead>
<tbody>
<tr>
<td>10.0.0.0 – 10.255.255.255</td><td>Private use</td></tr>
<tr>
<td>172.16.0.0 – 172.31.255.255</td><td>Private use</td></tr>
<tr>
<td>192.168.0.0 – 192.168.255.255</td><td>Private use</td></tr>
</tbody>
</table>
</div><p>Devices with private IPs connect to the internet through a router that uses NAT (Network Address Translation).</p>
<h4 id="heading-public-ip-addresses">Public IP Addresses:</h4>
<ul>
<li><p>Assigned by your ISP (Internet Service Provider).</p>
</li>
<li><p>Must be <strong>globally unique</strong>.</p>
</li>
<li><p>Used by websites, servers, and other devices reachable over the internet.</p>
</li>
</ul>
<h3 id="heading-static-vs-dynamic-ip-addresses">Static vs Dynamic IP Addresses</h3>
<p>IP addresses can also be either <strong>static</strong> or <strong>dynamic</strong>.</p>
<ul>
<li><p><strong>Static IP Address</strong>:</p>
<ul>
<li><p>Manually assigned to a device.</p>
</li>
<li><p>Doesn’t change over time.</p>
</li>
<li><p>Commonly used for servers, printers, or devices that need consistent access.</p>
</li>
</ul>
</li>
<li><p><strong>Dynamic IP Address</strong>:</p>
<ul>
<li><p>Assigned automatically using <strong>DHCP (Dynamic Host Configuration Protocol)</strong>.</p>
</li>
<li><p>Changes occasionally.</p>
</li>
<li><p>Most home networks use dynamic IPs for convenience and flexibility.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-why-this-all-matters">Why This All Matters</h3>
<p>Understanding subnetting, masks, and IP types helps you:</p>
<ul>
<li><p>Design networks that scale and perform well.</p>
</li>
<li><p>Assign addresses efficiently.</p>
</li>
<li><p>Improve security through network isolation.</p>
</li>
<li><p>Troubleshoot and configure routers and firewalls effectively.</p>
</li>
</ul>
<p>Subnetting felt confusing at first, but once I saw how it's like breaking down a neighborhood into streets and houses, it clicked. It's a powerful skill for anyone working in networking or IT. And with the rise of IPv6 and cloud-based systems, it's more relevant than ever.</p>
<h2 id="heading-chapter-9-routing-and-switching-directing-data-on-the-network"><strong>Chapter 9: Routing and Switching — Directing Data on the Network</strong></h2>
<p>In this chapter, you will:</p>
<ul>
<li><p>Understand the roles of routers and switches</p>
</li>
<li><p>Learn how data is directed within and across networks</p>
</li>
<li><p>Explore routing tables, packet forwarding, and switching techniques</p>
</li>
<li><p>Compare static vs. dynamic routing</p>
</li>
<li><p>Understand how LAN and WAN switching works</p>
</li>
</ul>
<p>Every time we send an email or watch a video, data is being <strong>routed</strong> and <strong>switched</strong> through a maze of devices. It’s like navigating a city using both small alleyways (switching) and highways (routing).</p>
<p>These processes ensure that data goes from point A to point B efficiently, securely, and correctly, even if they’re continents apart.</p>
<h2 id="heading-what-is-switching">What is Switching?</h2>
<p>Switching happens within local networks (LANs). It’s all about moving data between devices on the same network.</p>
<h3 id="heading-what-is-a-switch">What is a Switch?</h3>
<p>A <strong>switch</strong> is a device used in LANs to connect computers, printers, and other networked devices. It operates at <strong>Layer 2 (Data Link Layer)</strong> of the OSI model and plays a crucial role in directing traffic inside a local network.</p>
<p>But how does a switch know where to send the data?</p>
<p>It uses something called a <strong>MAC address</strong>.</p>
<h4 id="heading-what-are-mac-addresses">What Are MAC Addresses?</h4>
<p>A <strong>MAC (Media Access Control) address</strong> is a unique identifier assigned to a device’s network interface card (NIC). It’s like a digital fingerprint for your laptop, printer, or phone.</p>
<p>Each MAC address is a 48-bit address usually displayed in hexadecimal format like this:<br><code>00:1A:2B:3C:4D:5E</code></p>
<p>When data is sent over a LAN, it’s broken into frames, which include both a <strong>source MAC address</strong> and a <strong>destination MAC address</strong>.</p>
<p>The switch reads the destination MAC address and forwards the frame only to the port where that specific device is connected. This makes switching faster and more secure than old-style hubs that sent data to all devices.</p>
<h4 id="heading-lan-switching-techniques">LAN Switching Techniques</h4>
<p>Switches use different techniques to decide <strong>when and how to forward frames</strong>. These include:</p>
<ul>
<li><p><strong>Store-and-Forward Switching:</strong> The switch receives the entire frame, checks it for errors using a CRC (Cyclic Redundancy Check), and then forwards it. It’s reliable but slightly slower.</p>
</li>
<li><p><strong>Cut-Through Switching:</strong> The switch reads just the destination MAC address – often within the first 6 bytes – and immediately begins forwarding the frame. It’s faster but doesn’t check for errors.</p>
</li>
<li><p><strong>Fragment-Free Switching:</strong> A hybrid approach. It reads the first 64 bytes before forwarding, enough to avoid most collision-related errors.</p>
</li>
</ul>
<h2 id="heading-what-is-routing">What is Routing?</h2>
<p>While switching moves data within a single network, <strong>routing</strong> is what moves data <strong>between networks</strong>. This is how information travels from your home network to the wider internet.</p>
<h3 id="heading-what-is-a-router">What is a Router?</h3>
<p>A <strong>router</strong> is a device that connects different networks and determines the best path for data to travel. It operates at <strong>Layer 3 (Network Layer)</strong> of the OSI model and forwards data based on <strong>IP addresses</strong> rather than MAC addresses.</p>
<p>You can think of a router like a GPS navigator for internet traffic. It chooses the best available route based on traffic, cost, and destination.</p>
<h4 id="heading-what-is-a-routing-table">What is a Routing Table?</h4>
<p>Each router has a <strong>routing table</strong>, which is like a map that tells the router:</p>
<ul>
<li><p>Which destination networks does it know about</p>
</li>
<li><p>The next hop (which router to send the packet to next)</p>
</li>
<li><p>Which interface (port) to send it out on</p>
</li>
<li><p>The metric, which is a number representing the cost or preference of that path</p>
</li>
</ul>
<p>When a router receives a data packet, it checks the routing table to decide where to send it next.</p>
<h3 id="heading-static-vs-dynamic-routing">Static vs. Dynamic Routing</h3>
<p>Routers can learn routes in two main ways: <strong>static</strong> or <strong>dynamic</strong>.</p>
<h4 id="heading-static-routing">Static Routing</h4>
<p>With <strong>static routing</strong>, a network administrator manually enters routes into the router's configuration. This method is:</p>
<ul>
<li><p>Simple and efficient for small, stable networks</p>
</li>
<li><p>Very secure since routes never change unless manually updated</p>
</li>
<li><p>Limited because it doesn’t adapt if a network link goes down</p>
</li>
</ul>
<p>Example: If you tell a router, “To reach network X, always go through Router A,” that route will stay in place until someone changes it.</p>
<h4 id="heading-dynamic-routing">Dynamic Routing</h4>
<p><strong>Dynamic routing</strong> uses protocols that allow routers to automatically share and update routing information with each other. This approach is:</p>
<ul>
<li><p>Ideal for large or complex networks</p>
</li>
<li><p>Adaptive routes are recalculated if something changes or fails</p>
</li>
<li><p>Slightly more resource-intensive due to constant updates</p>
</li>
</ul>
<p>Common dynamic routing protocols include:</p>
<ul>
<li><p><strong>RIP (Routing Information Protocol)</strong> – Simple, but outdated</p>
</li>
<li><p><strong>OSPF (Open Shortest Path First)</strong> – Fast and widely used in large networks</p>
</li>
<li><p><strong>EIGRP (Enhanced Interior Gateway Routing Protocol)</strong> – Cisco’s proprietary protocol, combining the best of both distance vector and link-state methods</p>
</li>
<li><p><strong>BGP (Border Gateway Protocol)</strong> – The protocol that powers routing across the entire internet</p>
</li>
</ul>
<h3 id="heading-routing-in-action">Routing in Action</h3>
<p>Let’s say I’m watching a YouTube video:</p>
<ol>
<li><p>My device sends a request</p>
</li>
<li><p>The switch sends it to the router</p>
</li>
<li><p>The router consults its table and forwards it to another router</p>
</li>
<li><p>This process continues until the request reaches YouTube’s server</p>
</li>
<li><p>The server sends data back, following the same or a different route</p>
</li>
</ol>
<p>Routers and switches never sleep. They’re working behind the scenes, 24/7, making sure our digital lives function smoothly.</p>
<p>Routing and switching may sound technical, but they are the backbone of modern networking. Knowing how they work has helped me troubleshoot issues and understand why certain delays or outages happen.</p>
<p>Switching keeps local communication efficient. Routing connects us to the world.Together, they are the traffic controllers of the internet.</p>
<h2 id="heading-chapter-10-network-infrastructure-devices-security-and-the-modern-internet"><strong>Chapter 10: Network Infrastructure — Devices, Security, and the Modern Internet</strong></h2>
<p>As I continued my journey through networking and data communication, I could see that it's not theory alone – it's hardware, security, and innovation that are essential to the backbone of our everyday life on the internet.</p>
<p>This final chapter brings together the essential knowledge of networks: devices, security protocols, and the technologies behind new connectivity.</p>
<p>In this chapter, you will:</p>
<ul>
<li><p>Understand common networking devices and their functions</p>
</li>
<li><p>Explore firewalls, intrusion detection, and best practices for security</p>
</li>
<li><p>Learn how the internet works (DNS, cloud computing, IoT)</p>
</li>
<li><p>Appreciate the role of protocols, encryption, and data integrity in today's connected world</p>
</li>
</ul>
<h2 id="heading-network-devices-the-building-blocks-of-connectivity"><strong>Network Devices — The Building Blocks of Connectivity</strong></h2>
<p>Every time we send an email, stream a video, or browse the web, a collection of physical devices quietly work behind the scenes to make it all possible. These network devices form the infrastructure of both small local networks and the vast global internet. Let’s take a closer look at some of the key players.</p>
<h3 id="heading-hub">Hub</h3>
<p>The <strong>hub</strong> is one of the earliest and simplest network devices. It operates at the <strong>Physical Layer (Layer 1)</strong> of the OSI model and has a very basic job: when it receives data from one of its ports, it broadcasts that data to all other connected devices.</p>
<p>This method is inefficient, as it creates unnecessary traffic and poses security risks. Because of this, hubs are rarely used in modern networks, having been largely replaced by more intelligent devices like switches.</p>
<h3 id="heading-switch">Switch</h3>
<p>A <strong>switch</strong> is a more advanced and efficient version of a hub. It operates at <strong>Layer 2 (Data Link Layer)</strong> and uses MAC addresses to forward data only to the intended recipient. Instead of flooding the entire network with every transmission, a switch makes sure the data goes only where it's needed. This makes it the go-to device in most <strong>Local Area Networks (LANs)</strong> today.</p>
<h3 id="heading-router">Router</h3>
<p>While switches handle local traffic, <strong>routers</strong> are responsible for sending data between different networks. Operating at <strong>Layer 3 (Network Layer)</strong>, a router uses <strong>IP addresses</strong> to determine the best path for forwarding packets across the internet. In home and business environments, routers are essential for enabling access to the wider world beyond the local network.</p>
<h3 id="heading-access-point-ap">Access Point (AP)</h3>
<p>An <strong>Access Point</strong> bridges the gap between wired and wireless networking. It connects to a wired network and provides <strong>Wi-Fi</strong> so that wireless devices like laptops and smartphones can connect. Access points are especially important in large areas such as offices, schools, or public places where seamless wireless connectivity is needed.</p>
<h3 id="heading-modem">Modem</h3>
<p>A <strong>modem</strong> (short for <em>modulator-demodulator</em>) is the device that connects your local network to your <strong>Internet Service Provider (ISP)</strong>. It converts digital data from your computer into signals that can travel over telephone lines or cable systems, and vice versa. In many homes, the modem is combined with a router in a single device.</p>
<h3 id="heading-network-interface-card-nic">Network Interface Card (NIC)</h3>
<p>A <strong>NIC</strong> is the hardware component inside a device—like a laptop or desktop—that allows it to connect to a network. It can be built-in or external and can support either wired Ethernet or wireless Wi-Fi connections. Without a NIC, a device simply can’t participate in network communication.</p>
<h2 id="heading-network-security-protecting-our-digital-lives">Network Security — Protecting Our Digital Lives</h2>
<p>I never thought much about network security – until I once received a very convincing spam email that nearly tricked me into sharing personal info. It was a wake-up call that our digital spaces aren’t always as safe as they seem.</p>
<p>In today’s connected world, network security is not just an IT concern – it’s a crucial part of everyday life. As we connect more devices and store more personal data online, the risks of cyberattacks and data breaches grow. Here’s a look at the major threats and how we protect against them.</p>
<h3 id="heading-common-threats">Common Threats</h3>
<p>There are many ways attackers can exploit vulnerabilities in a network. Some of the most common threats include:</p>
<ul>
<li><p><strong>Malware</strong>: This includes viruses, worms, and ransomware – malicious software that can damage files, steal information, or lock systems until a ransom is paid.</p>
</li>
<li><p><strong>Phishing</strong>: Attackers send fake emails or create deceptive websites to trick users into revealing sensitive information like passwords or credit card numbers.</p>
</li>
<li><p><strong>DDoS Attacks</strong>: A Distributed Denial of Service attack overwhelms a system with traffic from multiple sources, causing it to slow down or crash entirely.</p>
</li>
</ul>
<h3 id="heading-security-devices-and-techniques">Security Devices and Techniques</h3>
<p>To defend against these threats, networks are equipped with various tools and strategies:</p>
<ul>
<li><p><strong>Firewalls</strong>: These act as gatekeepers between networks, blocking unauthorized access while allowing legitimate communication.</p>
</li>
<li><p><strong>Intrusion Detection Systems (IDS)</strong>: These monitor network traffic for suspicious behavior or known attack patterns.</p>
</li>
<li><p><strong>Antivirus and Endpoint Security</strong>: These tools protect individual devices by scanning for and removing malicious software.</p>
</li>
<li><p><strong>VPNs (Virtual Private Networks)</strong>: VPNs encrypt data transmitted over the internet, shielding users from eavesdropping—especially on public Wi-Fi networks.</p>
</li>
</ul>
<h3 id="heading-best-practices"><strong>Best Practices</strong></h3>
<p>Technology alone isn’t enough – human behavior plays a big role in security. Some key habits include:</p>
<ul>
<li><p>Using strong, unique passwords and changing them regularly</p>
</li>
<li><p>Keeping software and operating systems up to date, since patches often fix security holes</p>
</li>
<li><p>Enabling multi-factor authentication (MFA) to add an extra layer of protection</p>
</li>
<li><p>Educating users to recognize suspicious emails and links</p>
</li>
</ul>
<p>Together, these tools and habits form a multi-layered defense that helps safeguard personal and organizational data.</p>
<h2 id="heading-the-modern-internet-dns-cloud-and-iot"><strong>The Modern Internet — DNS, Cloud, and IoT</strong></h2>
<p>Today’s internet is about far more than just connecting computers. It’s a complex, evolving ecosystem of services and smart devices, all working together to deliver seamless digital experiences. Let’s explore three key pillars of the modern internet: <strong>DNS</strong>, <strong>Cloud Computing</strong>, and the <strong>Internet of Things (IoT)</strong>.</p>
<h3 id="heading-domain-name-system-dns">Domain Name System (DNS)</h3>
<p>Imagine trying to access websites using IP addresses like <code>142.250.190.206</code> instead of just typing <a target="_blank" href="http://google.com"><code>google.com</code></a>. It would be nearly impossible to remember. That’s where the <strong>Domain Name System (DNS)</strong> comes in.</p>
<p>DNS works like the internet’s phonebook: it translates easy-to-remember domain names (like google.com) into the numerical IP addresses that computers use to communicate. Without DNS, web browsing as we know it wouldn’t exist.</p>
<h3 id="heading-cloud-computing">Cloud Computing</h3>
<p>The <strong>cloud</strong> has transformed how we store, process, and access information. Rather than relying on local hardware, cloud computing delivers services—like file storage, applications, or processing power—via the internet. Platforms like Google Drive, Amazon Web Services (AWS), and Microsoft Azure make it easy to scale up resources as needed, work from anywhere, and reduce infrastructure costs.</p>
<p>The benefits are clear: scalability, flexibility, and cost efficiency. But it also brings new challenges in terms of data privacy, security, and compliance.</p>
<h3 id="heading-internet-of-things-iot">Internet of Things (IoT)</h3>
<p>The <strong>Internet of Things</strong> refers to everyday objects – like light bulbs, refrigerators, security cameras – that are connected to the internet and can communicate with each other. These devices offer convenience and automation, like turning off lights remotely or monitoring your home while away.</p>
<p>But the explosion of connected devices introduces challenges:</p>
<ul>
<li><p><strong>Security</strong>: Many IoT devices are poorly secured, making them easy targets for hackers.</p>
</li>
<li><p><strong>Interoperability</strong>: With so many manufacturers and standards, getting devices to work together can be difficult.</p>
</li>
<li><p><strong>Privacy</strong>: IoT devices often collect sensitive personal data, raising concerns about how that information is used.</p>
</li>
</ul>
<h2 id="heading-encryption-and-secure-protocols"><strong>Encryption and Secure Protocols</strong></h2>
<p>As data travels through this vast digital landscape, it must be protected from prying eyes. That’s where <strong>encryption</strong> and <strong>secure protocols</strong> come into play. These tools ensure that even if data is intercepted, it remains unreadable without the correct key.</p>
<p>Some of the most widely used secure protocols include:</p>
<ul>
<li><p><strong>HTTPS (Hypertext Transfer Protocol Secure)</strong>: Ensures encrypted communication between your browser and websites.</p>
</li>
<li><p><strong>SSL/TLS (Secure Sockets Layer / Transport Layer Security)</strong>: Used behind HTTPS to secure web data.</p>
</li>
<li><p><strong>IPSec</strong>: Encrypts IP packets and is commonly used in VPNs to secure network-level communication.</p>
</li>
<li><p><strong>SSH (Secure Shell)</strong>: Provides secure remote access to systems and devices.</p>
</li>
</ul>
<p>These technologies form the backbone of secure internet communication, protecting users from data leaks, identity theft, and other forms of digital attack.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Looking back, it's amazing how far we've come – from learning what a bit is, to understanding how huge global networks function securely and efficiently.</p>
<p>Networking is more than routers and wires – it's a finely crafted system of trust, logic, and global cooperation. It's the very reason that we're able to learn, work, connect, and create anywhere.</p>
<p>And having established this foundation, I feel ready to go further.</p>
<p>Thank you for joining me on this journey.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn to Build a Multilayer Perceptron with Real-Life Examples and Python Code ]]>
                </title>
                <description>
                    <![CDATA[ The perceptron is a fundamental concept in deep learning, with many algorithms stemming from its original design. In this tutorial, I’ll show you how to build both single layer and multi-layer perceptrons (MLPs) across three frameworks: Custom class... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-multilayer-perceptron-with-examples-and-python-code/</link>
                <guid isPermaLink="false">6839f729798ea464918cffe8</guid>
                
                    <category>
                        <![CDATA[ Deep Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ neural networks ]]>
                    </category>
                
                    <category>
                        <![CDATA[ binary classification ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MLP (Multi-Layer Perceptrons) ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Data Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kuriko ]]>
                </dc:creator>
                <pubDate>Fri, 30 May 2025 18:21:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748616370600/01903917-4be7-476b-90d1-18295d19edef.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The <strong>perceptron</strong> is a fundamental concept in deep learning, with many algorithms stemming from its original design.</p>
<p>In this tutorial, I’ll show you how to build both single layer and multi-layer perceptrons (MLPs) across three frameworks:</p>
<ul>
<li><p>Custom classifier</p>
</li>
<li><p>Scikit-learn’s MLPClassifier</p>
</li>
<li><p>Keras Sequential classifier using SGD and Adam optimizers.</p>
</li>
</ul>
<p>This will help you learn about their various use cases and how they work.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-perceptron">What is a Perceptron?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-a-single-layered-classifier">How to Build a Single-Layered Classifier</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-multi-layer-perceptron">What is a Multi-Layer Perceptron?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-multi-layered-perceptrons">How to Build Multi-Layered Perceptrons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-optimizers">Understanding Optimizers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-an-mlp-classifier-with-sgd-optimizer">How to Build an MLP Classifier with SGD Optimizer</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-an-mlp-classifier-with-adam-optimizer">How to Build an MLP Classifier with Adam Optimizer</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-results-generalization">Final Results: Generalization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li><p>Mathematics (Calculus, Linear Algebra, Statistics)</p>
</li>
<li><p>Coding in Python</p>
</li>
<li><p>Basic understanding of Machine Learning concepts</p>
</li>
</ul>
<h2 id="heading-what-is-a-perceptron">What is a Perceptron?</h2>
<p>A perceptron is one of the simplest types of artificial neurons used in Machine Learning. It’s a building block of artificial neural networks that learns from labeled data to perform classification and pattern recognition tasks, typically on linearly separable data.</p>
<p>A single-layer perceptron consists of a single layer of artificial neurons, called perceptrons.</p>
<p>But when you connect many perceptrons together in layers, you have a multi-layer perceptron (MLP). This lets the network learn more complex patterns by combining simple decisions from each perceptron. And this makes MLPs powerful tools for tasks like image recognition and natural language processing.</p>
<p>The perceptron consists of four main parts:</p>
<ul>
<li><p><strong>Input layer</strong>: Takes the initial numerical values into the system for further processing.</p>
</li>
<li><p><strong>Weights</strong>: Combines input values with weights (and bias terms).</p>
</li>
<li><p><strong>Activation function</strong>: Determines whether the neuron should fire based on the threshold value.</p>
</li>
<li><p><strong>Output layer</strong>: Produces classification result.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748438698612/5b2920db-4ec1-455b-840e-7b5e9d6c2e75.png" alt="Image: Organization of a perceptron. Source: Rosenblatt 1958" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>It performs a weighted sum of inputs, adds a bias, and passes the result through an activation function – just like logistic regression. It’s sort of like a little decision-maker that says “yes” or “no” based on the information it gets.</p>
<p>So for instance, when we use a sigmoid activation, its output is a probability between 0 and 1, mimicking the behavior of logistic regression.</p>
<h3 id="heading-applications-of-perceptrons">Applications of Perceptrons</h3>
<p>Perceptrons are applied to tasks such as:</p>
<ul>
<li><p><strong>Image classification:</strong> Perceptrons classify images containing specific objects. They achieve this by performing binary classification tasks.</p>
</li>
<li><p><strong>Linear regression:</strong> Perceptrons can predict continuous outputs based on input features. This makes them useful for solving linear regression problems.</p>
</li>
</ul>
<h3 id="heading-how-the-activation-function-works">How the Activation Function Works</h3>
<p>For a single perceptron used for binary classification, the most common activation function is the <strong>step function</strong> (also known as the threshold function):</p>
<p>$$\phi(z) = \begin{cases} 1 &amp;\text{if } z \geq \theta \\ \\ 0 &amp;\text{if } z &lt; \theta \end{cases}$$</p><p>where:</p>
<ul>
<li><p><code>ϕ(z)</code>: the output of the activation function.</p>
</li>
<li><p><code>z</code>: the weighted sum of the inputs plus the bias:</p>
</li>
</ul>
<p>$$z = \sum_{i=1}^m w_i x_i + b$$</p><p>(xi: input values, w: weight associated with each input, b: bias terms)</p>
<p><code>θ</code> is the threshold. Often, the threshold θ is set to zero, and the bias (b) effectively controls the activation threshold.</p>
<p>In that case, the formula becomes:</p>
<p>$$\phi(z) = \begin{cases} 1 &amp;\text{if } z \geq 0 \\ \\ 0 &amp;\text{if } z &lt; 0 \end{cases}$$</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748439460839/e74f1c1c-4e89-419b-aa9e-24a297d81ff5.png" alt="Image: Step Function (Author)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>When the step function ϕ(z) outputs one, it signifies that the input belongs to the class labeled one.</p>
<p>This occurs <strong>when the weighted sum is greater than zero,</strong> leading the perceptron to predict the input is in this binary class.</p>
<p>While the step function is conceptually the original activation for a perceptron, its discontinuity at zero causes computational challenges.</p>
<p>In modern implementations, we can use other activation functions like the <strong>sigmoid</strong> function:</p>
<p>$$\sigma (z) = \frac {1} {1 + e^{-z}}$$</p><p>The sigmoid function also outputs zero or one depending on the weighted sum (z).</p>
<h3 id="heading-how-the-loss-function-works">How the Loss Function Works</h3>
<p>The <strong>loss function</strong> is a crucial concept in machine learning that quantifies the error or discrepancy between the model's predictions and the actual target values.</p>
<p>Its purpose is to penalize the model for making incorrect or inaccurate predictions, which guides the learning algorithm (for example, gradient descent) to adjust the model's parameters in a way that minimizes this error and improves performance.</p>
<p>In a binary classification task, the model may adopt the <strong>hinge loss function</strong> to penalize misclassifications by incurring an additional cost for incorrect predictions:</p>
<p>$$L(y, h(x)) = max(0, 1- y*h(x))$$</p><p>(h(x): prediction label, y: true label)</p>
<h2 id="heading-how-to-build-a-single-layered-classifier">How to Build a Single-Layered Classifier</h2>
<p>Now, let’s build a simple single-layer perceptron for binary classification.</p>
<h3 id="heading-1-custom-classifier">1. Custom Classifier</h3>
<h4 id="heading-initialize-the-classifier">Initialize the classifier</h4>
<p>We’ll first initialize the classifier with <code>weights</code>, <code>bias</code>, number of epochs (<code>n_iterations)</code>, and <code>learning_rates</code>.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, learning_rate=<span class="hljs-number">0.01</span>, n_iterations=<span class="hljs-number">1000</span></span>):</span>
    self.learning_rate = learning_rate
    self.n_iterations = n_iterations
    self.weights = <span class="hljs-literal">None</span>
    self.bias = <span class="hljs-literal">None</span>
</code></pre>
<h4 id="heading-define-the-activation-function">Define the activation function</h4>
<p>Use a step function that returns zero if input (x) ≤ 0, else 1. By default, the <code>threshold</code> is set to zero.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_step_function</span>(<span class="hljs-params">self, x, threshold: int = <span class="hljs-number">0</span></span>):</span>
     <span class="hljs-keyword">return</span> np.where(x &gt; threshold, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>)
</code></pre>
<h4 id="heading-train-the-model">Train the model</h4>
<p>Now it’s time to start training. The learning process involves iteratively updating the perceptron’s internal parameters: <code>weights</code> and <code>bias</code>.</p>
<p>This process is controlled by a specified number of training epochs defined by <code>n_iterations</code>.</p>
<p>In each epoch, the model processes the entire input dataset (X) and adjusts its weights and bias based on the difference between its predictions and the true labels (y), guided by a predefined <code>learning_rate</code>.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fit</span>(<span class="hljs-params">self, X, y</span>):</span>
    n_samples, n_features = X.shape

    self.weights = np.zeros(n_features)
    self.bias = <span class="hljs-number">0</span>

    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(self.n_iterations):
        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(n_samples):
            <span class="hljs-comment"># compute weighted sum (z)</span>
            z = np.dot(X[i], self.weights) + self.bias

            <span class="hljs-comment"># apply the activation function</span>
            y_pred = self._step_function(z)

            <span class="hljs-comment"># update weights and bias</span>
            self.weights += self.learning_rate * (y[i] - y_pred) * X[i]
            self.bias += self.learning_rate * (y[i] - y_pred)
</code></pre>
<h4 id="heading-how-the-weights-work-in-the-iteration-loop">How the weights work in the iteration loop</h4>
<p>The weights in a perceptron define the orientation (slope) of the decision boundary that separates the classes.</p>
<p>Its iterative update in the <code>for</code> loop aims to reduce classification errors such that:</p>
<p>$$\begin {align*} w_j &amp;:= w_j + \Delta w_j \\ &amp; := w_j + \eta (y_i - \hat y_i)x_{ij} \\ &amp;= \begin{cases} w_j &amp;\text{(a) } y_i - \hat y_i = 0\\ w_j + \eta x_ij &amp;\text{(b) } y_i - \hat y_i = 1 \\ w_j - \eta x_ij &amp;\text{(c) } y_i - \hat y_i = -1 \\ \end{cases} \end{align*}$$</p><p>(<code>w_j</code>: j-th weight, <code>η</code>: learning rate, (<code>yi​−y^​i​</code>): error)</p>
<p>This means that:</p>
<ol>
<li><p>When the prediction is <strong>correct</strong>, the error is zero, so the weight is unchanged.</p>
</li>
<li><p>When the prediction is <strong>too low</strong> (yi​=1 and y^​i​=0), the weight is adjusted to the same direction to increase the weighted sum.</p>
</li>
<li><p>When the prediction is <strong>too high</strong> (yi​=0 and y^​i​=1), the weight is adjusted to the opposite direction to pull the weighted sum lower.</p>
</li>
</ol>
<h4 id="heading-how-the-bias-terms-work-in-the-iteration-loop">How the bias terms work in the iteration loop</h4>
<p>The bias determines the decision boundary’s intercept (position from the origin).</p>
<p>Similar to weights, we adjust the bias terms in each epoch to position the decision boundary:</p>
<p>$$\begin {align*} b &amp;:= b + \Delta b \\ &amp; := b + \eta (y_i - \hat y_i) \\ &amp;= \begin{cases} b &amp;\text{(a) } y_i - \hat y_i = 0\\ b + \eta &amp;\text{(b) } y_i - \hat y_i = 1 \\ b - \eta &amp;\text{(c) } y_i - \hat y_i = -1 \\ \end{cases} \end{align*}$$</p><p>This repeated adjustment aims to optimize the model’s ability to correctly classify the training data.</p>
<h4 id="heading-make-a-prediction">Make a prediction</h4>
<p>Lastly, we add a function to generate an outcome value (zero or one) for a new, unseen data (X):</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict</span>(<span class="hljs-params">self, X</span>):</span>
      linear_output = np.dot(X, self.weights) + self.bias
      predictions = self._step_function(linear_output)
      <span class="hljs-keyword">return</span> predictions
</code></pre>
<p><strong>The entire classifier looks like this:</strong></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Perceptron</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, learning_rate=<span class="hljs-number">0.01</span>, n_iterations=<span class="hljs-number">1000</span></span>):</span>
        self.learning_rate = learning_rate
        self.n_iterations = n_iterations
        self.weights = <span class="hljs-literal">None</span>
        self.bias = <span class="hljs-literal">None</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_step_function</span>(<span class="hljs-params">self, x, threshold: int = <span class="hljs-number">0</span></span>):</span>
        <span class="hljs-keyword">return</span> np.where(x &gt; threshold, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fit</span>(<span class="hljs-params">self, X, y</span>):</span>
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = <span class="hljs-number">0</span>

        <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(self.n_iterations):
            <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(n_samples):
                linear_output = np.dot(X[i], self.weights) + self.bias
                y_pred = self._step_function(linear_output)
                self.weights += self.learning_rate * (y[i] - y_pred) * X[i]
                self.bias += self.learning_rate * (y[i] - y_pred)
        <span class="hljs-keyword">return</span> self

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict</span>(<span class="hljs-params">self, X</span>):</span>
        linear_output = np.dot(X, self.weights) + self.bias
        y_pred = self._step_function(linear_output)
        <span class="hljs-keyword">return</span> y_pred
</code></pre>
<h4 id="heading-simulate-with-synthetic-datasets">Simulate with synthetic datasets</h4>
<p>First, we generated a synthetic linearly separable dataset using <code>make_blob</code> and computed a decision boundary, then train the classifier we created.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> make_blobs
<span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np

<span class="hljs-comment"># create a mock dataset</span>
X, y = make_blobs(n_features=<span class="hljs-number">2</span>, centers=<span class="hljs-number">2</span>, n_samples=<span class="hljs-number">1000</span>, random_state=<span class="hljs-number">12</span>)

<span class="hljs-comment"># split</span>
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=<span class="hljs-number">0.2</span>, random_state=<span class="hljs-number">42</span>)

<span class="hljs-comment"># train the model</span>
perceptron = Perceptron(learning_rate=<span class="hljs-number">0.1</span>, n_iterations=<span class="hljs-number">1000</span>).fit(X_train, y_train)

<span class="hljs-comment"># make a prediction</span>
y_pred_train = perceptron.predict(X_train)
y_pred_test = perceptron.predict(X_test)

<span class="hljs-comment"># evaluate the results</span>
acc_train = np.mean(y_pred_train == y_train)
acc_test = np.mean(y_pred_test == y_test)
print(<span class="hljs-string">f"Accuracy (Train): <span class="hljs-subst">{acc_train:<span class="hljs-number">.3</span>}</span> \nAccuracy (Test): <span class="hljs-subst">{acc_test:<span class="hljs-number">.3</span>}</span>"</span>)
</code></pre>
<h4 id="heading-results">Results</h4>
<p>The classifier generated a clear, highly accurate linear decision boundary.</p>
<ul>
<li><p><em>Accuracy (Train): 0.981</em></p>
</li>
<li><p><em>Accuracy (Test): 0.975</em></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748440470195/0a01c5ad-124e-4f59-b4d5-9ee5dd5b23ce.png" alt="Decision boundary of single-layer perceptron (Custom classifier)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-2-leverage-sckitlearns-mcp-classifier">2. Leverage SckitLearn’s MCP Classifier</h3>
<p>For our convenience, we’ll use sckit-learn’s build-in classifier ( <code>MCPClassifier</code>) to build a similar, yet more robust classifier:</p>
<pre><code class="lang-python">model = MLPClassifier(
    hidden_layer_sizes=(), <span class="hljs-comment"># intentionally set empty to create a single layer perceptron</span>
    activation=<span class="hljs-string">'logistic'</span>, <span class="hljs-comment"># choosing a sigmoid function as an activation function</span>
    solver=<span class="hljs-string">'sgd'</span>, <span class="hljs-comment"># choosing SGD optimizer</span>
    max_iter=<span class="hljs-number">1000</span>,
    random_state=<span class="hljs-number">42</span>, 
    learning_rate=<span class="hljs-string">'constant'</span>, 
    learning_rate_init=<span class="hljs-number">0.1</span>
).fit(X_train, y_train)

y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)

acc_train = np.mean(y_pred_train == y_train)
acc_test = np.mean(y_pred_test == y_test)
print(<span class="hljs-string">f"MCPClassifier\nAccuracy (Train): <span class="hljs-subst">{acc_train:<span class="hljs-number">.3</span>}</span> \nAccuracy (Test): <span class="hljs-subst">{acc_test:<span class="hljs-number">.3</span>}</span>"</span>)
</code></pre>
<h4 id="heading-results-1">Results</h4>
<p>The MCP Classifier generated a clear linear decision boundary with slightly better accuracy scores.</p>
<ul>
<li><p><em>Accuracy (Train): 0.985</em></p>
</li>
<li><p><em>Accuracy (Test): 0.995</em></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748440118956/f5391f47-711a-4948-b956-1a76dbd7ca92.png" alt="Decision boundary of single-layer perceptron (MCP Classifier)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-limitations-of-single-layer-perceptrons">Limitations of Single-Layer Perceptrons</h3>
<p>Now, let’s talk about the key differences between the MCP Classifier and our custom single-layer perceptron.</p>
<p>Unlike more general neural networks, single-layer perceptrons use a <strong>step function</strong> as their activation.</p>
<p>Due to its discontinuity at x=0, the step function is not differentiable over its entire domain (−∞ to ∞).</p>
<p>This fundamental property precludes the use of <strong>gradient-based optimization algorithms</strong> such as SGD or Adam, as these methods depend on the computation of gradients, partial derivatives for the cost function.</p>
<p>In contrast, most neural networks employ differentiable activation functions (for example, <strong>sigmoid</strong>, <strong>ReLU</strong>) and loss functions (for example, <strong>MSE</strong>, <strong>Cross-Entropy</strong>) for effective optimization.</p>
<p>Other challenges of a single-layer perceptron include:</p>
<ul>
<li><p><strong>Limited to linear separability:</strong> Because they can only learn linear decision boundaries, they are unable to handle complex, non-linearly separable data.</p>
</li>
<li><p><strong>Lack of depth:</strong> Being single-layered, they cannot learn complex hierarchical representations.</p>
</li>
<li><p><strong>Limited optimizer options:</strong> As mentioned, their non-differentiable activation function precludes the use of major gradient-based optimizers.</p>
</li>
</ul>
<p>So, in the next section, you’ll learn about multi-layered perceptrons to overcome the disadvantages.</p>
<h2 id="heading-what-is-a-multi-layer-perceptron">What is a Multi-Layer Perceptron?</h2>
<p>An MLP is a class of feedforward artificial neural network that consists of at least <strong>three layers</strong> of nodes:</p>
<ul>
<li><p>an input layer,</p>
</li>
<li><p>one or more hidden layers, and</p>
</li>
<li><p>an output layer.</p>
</li>
</ul>
<p>Except for the input nodes, each node is a neuron that uses a <strong>nonlinear</strong> activation function.​</p>
<p>MLPs are widely used for classification problems as well as regression:</p>
<ul>
<li><p><strong>Classification tasks:</strong> MLPs are widely used for classification problems, such as handwriting recognition and speech recognition.​</p>
</li>
<li><p><strong>Regression analysis:</strong> They are also applied in regression problems where the relationship between input and output is complex.​</p>
</li>
</ul>
<h2 id="heading-how-to-build-multi-layered-perceptrons">How to Build Multi-Layered Perceptrons</h2>
<p>Let’s handle a binary classification task using a standard MLP architecture.</p>
<h3 id="heading-outline-of-the-project">Outline of the Project</h3>
<h4 id="heading-objective">Objective</h4>
<ul>
<li>Detect fraudulent transactions</li>
</ul>
<h4 id="heading-evaluation-metrics">Evaluation Metrics</h4>
<ul>
<li><p>Considering the cost of misclassification, we’ll prioritize improving <strong>Recall</strong> and <strong>Precision scores</strong></p>
</li>
<li><p>Then check the accuracy of classification with <strong>Accuracy</strong> Score (TP + TN / (TP + TN + FP + FN ))</p>
</li>
</ul>
<p><strong>Cost of Misclassification (from high to low):</strong></p>
<ul>
<li><p><strong>False Negative (FN):</strong> The model incorrectly identifies a fraudulent transaction as legitimate (Missing actual fraud)</p>
</li>
<li><p><strong>False Positive (FP):</strong> The model incorrectly identifies a legitimate transaction as fraudulent (Blocking legitimate customers.)</p>
</li>
<li><p><strong>True Positive (TP):</strong> The model correctly identifies a fraudulent transaction as fraud.</p>
</li>
<li><p><strong>True Negative (TN):</strong>  The model correctly identifies a non-fraudulent transaction as non-fraud.</p>
</li>
</ul>
<h3 id="heading-planning-an-mlp-architecture">Planning an MLP Architecture</h3>
<p>In the network, 19 input features feed into the first hidden layer’s 30 neurons, which use a ReLU activation function.</p>
<p>Then, their outputs are passed to the second layer, culminating in sigmoid values as the final output.</p>
<p>During the optimization process, we’ll let the optimizer (SGD and Adam) perform forward and backward passes to adjust parameters.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748440761512/37753a4c-f7f8-44bc-bea9-c50360830456.png" alt="Standard MLP Architecture for Binary Classification Tasks)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Image: Standard MLP Architecture for Binary Classification Tasks (Created by Kuriko Iwai using <a target="_blank" href="https://www.researchgate.net/publication/355148120_SS-MLP_A_Novel_Spectral-Spatial_MLP_Architecture_for_Hyperspectral_Image_Classification">image source</a>)</p>
<p>Especially in deeper network, <strong>ReLU</strong> is advantageous in preventing <a target="_blank" href="https://en.wikipedia.org/wiki/Vanishing_gradient_problem#:~:text=In%20machine%20learning%2C%20the%20vanishing,derivative%20of%20the%20loss%20function">vanishing gradient problems</a> where gradients become extremely small as they are backpropagated from the output layers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748440797954/ba19bf66-cdb9-4bfb-9b92-e1e3f72e9fc7.png" alt="Comparison of major activation functions: From left to right: Sigmoid, Tanh, ReLU" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><a target="_blank" href="https://medium.com/data-science-collective/a-comprehensive-guide-on-neural-network-in-deep-learning-442ba9f1f0e5">Learn More: A Comprehensive Guide on Neural Network in Deep Learning</a></p>
<h3 id="heading-preprocessing-the-datasets">Preprocessing the Datasets</h3>
<p>First, we consolidate <a target="_blank" href="https://www.kaggle.com/datasets/computingvictor/transactions-fraud-datasets">three datasets  –  transaction, customer, and credit card</a>  –  into a single DataFrame, independently sanitizing numerical and categorical data:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
<span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split
<span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> StandardScaler, OneHotEncoder
<span class="hljs-keyword">from</span> sklearn.impute <span class="hljs-keyword">import</span> SimpleImputer
<span class="hljs-keyword">from</span> sklearn.compose <span class="hljs-keyword">import</span> ColumnTransformer
<span class="hljs-keyword">from</span> sklearn.pipeline <span class="hljs-keyword">import</span> Pipeline

<span class="hljs-comment"># download the raw data to local</span>
<span class="hljs-keyword">import</span> kagglehub
path = kagglehub.dataset_download(<span class="hljs-string">"computingvictor/transactions-fraud-datasets"</span>)
dir = <span class="hljs-string">f'<span class="hljs-subst">{path}</span>/gd_card_flaud_demo'</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sanitize_df</span>(<span class="hljs-params">amount_str</span>):</span>
    <span class="hljs-string">"""Removes '$' and converts the string to a float."""</span>
    <span class="hljs-keyword">if</span> isinstance(amount_str, str):
        <span class="hljs-keyword">return</span> float(amount_str.replace(<span class="hljs-string">'$'</span>, <span class="hljs-string">''</span>))
    <span class="hljs-keyword">return</span> amount_str

<span class="hljs-comment"># load transaction data</span>
trx_df = pd.read_csv(<span class="hljs-string">f'<span class="hljs-subst">{dir}</span>/transactions_data.csv'</span>)

<span class="hljs-comment"># sanitize the dataset (drop unnecessary columns and error transactions, convert string to int/float dtype)</span>
trx_df = trx_df[trx_df[<span class="hljs-string">'errors'</span>].isna()]
trx_df = trx_df.drop(columns=[<span class="hljs-string">'merchant_city'</span>,<span class="hljs-string">'merchant_state'</span>, <span class="hljs-string">'date'</span>, <span class="hljs-string">'mcc'</span>, <span class="hljs-string">'errors'</span>], axis=<span class="hljs-string">'columns'</span>)
trx_df[<span class="hljs-string">'amount'</span>] = trx_df[<span class="hljs-string">'amount'</span>].apply(sanitize_df)

<span class="hljs-comment"># merge the dataframe with fraud transaction flag.</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">f'<span class="hljs-subst">{dir}</span>/train_fraud_labels.json'</span>, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> fp:
    fraud_labels_json = json.load(fp=fp)

fraud_labels_dict = fraud_labels_json.get(<span class="hljs-string">'target'</span>, {})
fraud_labels_series = pd.Series(fraud_labels_dict, name=<span class="hljs-string">'is_fraud'</span>)
fraud_labels_series.index = fraud_labels_series.index.astype(int) <span class="hljs-comment"># convert the datatype from string to integer</span>
merged_df = pd.merge(trx_df, fraud_labels_series, left_on=<span class="hljs-string">'id'</span>, right_index=<span class="hljs-literal">True</span>, how=<span class="hljs-string">'left'</span>)
merged_df.fillna({<span class="hljs-string">'is_fraud'</span>: <span class="hljs-string">'No'</span>}, inplace=<span class="hljs-literal">True</span>)
merged_df[<span class="hljs-string">'is_fraud'</span>] = merged_df[<span class="hljs-string">'is_fraud'</span>].map({<span class="hljs-string">'Yes'</span>: <span class="hljs-number">1</span>, <span class="hljs-string">'No'</span>: <span class="hljs-number">0</span>})

<span class="hljs-comment"># load card data</span>
card_df = pd.read_csv(<span class="hljs-string">f'<span class="hljs-subst">{dir}</span>/cards_data.csv'</span>)
card_df = card_df.drop(columns=[<span class="hljs-string">'client_id'</span>, <span class="hljs-string">'acct_open_date'</span>, <span class="hljs-string">'card_number'</span>, <span class="hljs-string">'expires'</span>, <span class="hljs-string">'cvv'</span>], axis=<span class="hljs-string">'columns'</span>)
card_df[<span class="hljs-string">'credit_limit'</span>] = card_df[<span class="hljs-string">'credit_limit'</span>].apply(sanitize_df)

<span class="hljs-comment"># merge transaction and card data</span>
merged_df = pd.merge(left=merged_df, right=card_df, left_on=<span class="hljs-string">'card_id'</span>, right_on=<span class="hljs-string">'id'</span>, how=<span class="hljs-string">'inner'</span>)
merged_df = merged_df.drop(columns=[<span class="hljs-string">'id_y'</span>, <span class="hljs-string">'card_id'</span>], axis=<span class="hljs-string">'columns'</span>)

<span class="hljs-comment"># converts categorical variables into a new binary column (0 or 1)</span>
categorical_cols = merged_df.select_dtypes(include=[<span class="hljs-string">'object'</span>]).columns
df = merged_df.copy()
df = pd.get_dummies(df, columns=categorical_cols, dummy_na=<span class="hljs-literal">False</span>, dtype=float) 
df = df.dropna().drop([<span class="hljs-string">'client_id'</span>, <span class="hljs-string">'id_x'</span>], axis=<span class="hljs-number">1</span>)
print(<span class="hljs-string">'\nDataFrame: \n'</span>, df.head(n=<span class="hljs-number">3</span>))
</code></pre>
<p>DataFrame:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748440856826/ba79bdaf-e0a1-457f-ab19-fda3e0f08141.png" alt="Base DataFrame" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Our DataFrame shows an extremely <strong>skewed data distribution</strong> with:</p>
<ul>
<li><p>Fraud samples: 1,191</p>
</li>
<li><p>Non-fraud samples: 11,477,397</p>
</li>
</ul>
<p>For classification tasks, <strong>it's crucial to be aware of sample size imbalances and employ appropriate strategies to mitigate their negative impact</strong> on classification model performance, especially regarding the minority class.</p>
<p>For our data, we’ll:</p>
<ol>
<li><p>split the 1,191 fraud samples into training, validation, and test sets,</p>
</li>
<li><p>add an equal number of randomly chosen non-fraud samples from the DataFrame, and</p>
</li>
<li><p>adjust split balances later if generalization challenges arise.</p>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-comment"># define the desired size of the fraud samples for the validation and test sets</span>
val_size_per_class = <span class="hljs-number">200</span>
test_size_per_class = <span class="hljs-number">200</span>

<span class="hljs-comment"># create test sets</span>
X_test_fraud = df_fraud.sample(n=test_size_per_class, random_state=<span class="hljs-number">42</span>)
X_test_non_fraud = df_non_fraud.sample(n=test_size_per_class, random_state=<span class="hljs-number">42</span>)

<span class="hljs-comment"># combine to form the balanced test set</span>
X_test = pd.concat([X_test_fraud, X_test_non_fraud]).sample(frac=<span class="hljs-number">1</span>, random_state=<span class="hljs-number">42</span>).reset_index(drop=<span class="hljs-literal">True</span>)
y_test = X_test[<span class="hljs-string">'is_fraud'</span>]
X_test = X_test.drop(<span class="hljs-string">'is_fraud'</span>, axis=<span class="hljs-number">1</span>)

<span class="hljs-comment"># remove sampled rows from the original dataframes to avoid data leakage</span>
df_fraud_remaining = df_fraud.drop(X_test_fraud.index)
df_non_fraud_remaining = df_non_fraud.drop(X_test_non_fraud.index)


<span class="hljs-comment"># create validation sets</span>
X_val_fraud = df_fraud_remaining.sample(n=val_size_per_class, random_state=<span class="hljs-number">42</span>)
X_val_non_fraud = df_non_fraud_remaining.sample(n=val_size_per_class, random_state=<span class="hljs-number">42</span>)

<span class="hljs-comment"># combine to form the balanced validation set</span>
X_val = pd.concat([X_val_fraud, X_val_non_fraud]).sample(frac=<span class="hljs-number">1</span>, random_state=<span class="hljs-number">42</span>).reset_index(drop=<span class="hljs-literal">True</span>)
y_val = X_val[<span class="hljs-string">'is_fraud'</span>]
X_val = X_val.drop(<span class="hljs-string">'is_fraud'</span>, axis=<span class="hljs-number">1</span>)

<span class="hljs-comment"># remove sampled rows from the remaining dataframes</span>
df_fraud_train = df_fraud_remaining.drop(X_val_fraud.index)
df_non_fraud_train = df_non_fraud_remaining.drop(X_val_non_fraud.index)


<span class="hljs-comment"># create training sets</span>
min_train_samples_per_class = min(len(df_fraud_train), len(df_non_fraud_train))

X_train_fraud = df_fraud_train.sample(n=min_train_samples_per_class, random_state=<span class="hljs-number">42</span>)
X_train_non_fraud = df_non_fraud_train.sample(n=min_train_samples_per_class, random_state=<span class="hljs-number">42</span>)

X_train = pd.concat([X_train_fraud, X_train_non_fraud]).sample(frac=<span class="hljs-number">1</span>, random_state=<span class="hljs-number">42</span>).reset_index(drop=<span class="hljs-literal">True</span>)
y_train = X_train[<span class="hljs-string">'is_fraud'</span>]
X_train = X_train.drop(<span class="hljs-string">'is_fraud'</span>, axis=<span class="hljs-number">1</span>)


print(<span class="hljs-string">"\n--- Final Dataset Shapes and Distributions ---"</span>)
print(<span class="hljs-string">f"X_train shape: <span class="hljs-subst">{X_train.shape}</span>, y_train distribution: <span class="hljs-subst">{np.unique(y_train, return_counts=<span class="hljs-literal">True</span>)}</span>"</span>)
print(<span class="hljs-string">f"X_val shape: <span class="hljs-subst">{X_val.shape}</span>, y_val distribution: <span class="hljs-subst">{np.unique(y_val, return_counts=<span class="hljs-literal">True</span>)}</span>"</span>)
print(<span class="hljs-string">f"X_test shape: <span class="hljs-subst">{X_test.shape}</span>, y_test distribution: <span class="hljs-subst">{np.unique(y_test, return_counts=<span class="hljs-literal">True</span>)}</span>"</span>)
</code></pre>
<p>After the operation, we secured 1,582 training, 400 validation, and 400 test samples, each dataset maintaining a <strong>50:50 split between fraud and non-fraud transactions</strong>:</p>
<p><img src="https://cdn-images-1.medium.com/max/1440/1*IZtK3l0hSqmkOrm9h_d9Jw.png" alt="X, y datasets shape" width="600" height="400" loading="lazy"></p>
<p>Considering the high dimensional feature space with 19 input features, we’ll apply <strong>SMOTE</strong> to resample the training data (SMOTE should not be applied to validation or test sets to avoid data leakage):</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> imblearn.over_sampling <span class="hljs-keyword">import</span> SMOTE
<span class="hljs-keyword">from</span> collections <span class="hljs-keyword">import</span> Counter

train_target = <span class="hljs-number">2000</span>

smote_train = SMOTE(
  sampling_strategy={<span class="hljs-number">0</span>: train_target, <span class="hljs-number">1</span>: train_target},  <span class="hljs-comment"># increase sample size to 2,000</span>
  random_state=<span class="hljs-number">12</span>
)
X_train, y_train = smote_train.fit_resample(X_train, y_train)

print(<span class="hljs-string">f"\nAfter SMOTE with custom sampling_strategy (target train: <span class="hljs-subst">{train_target}</span>):"</span>)
print(<span class="hljs-string">f"X_train_oversampled shape: <span class="hljs-subst">{X_train.shape}</span>"</span>)
print(<span class="hljs-string">f"y_train_oversampled distribution: <span class="hljs-subst">{Counter(y_train)}</span>"</span>)
</code></pre>
<p>We’ve secured 4,000 training samples, maintaining a 50:50 split between fraud and non-fraud transactions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748440986995/ed079321-3972-4226-b1a8-244010445162.png" alt="Training sample shape after SMOTE" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Lastly, we’ll apply <strong>column transformers</strong> to numerical and categorical features separately.</p>
<p>Column transformers are advantageous in handling datasets with multiple data types, as they can apply different transformations to different subsets of columns while preventing data leakage.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.impute <span class="hljs-keyword">import</span> SimpleImputer
<span class="hljs-keyword">from</span> sklearn.compose <span class="hljs-keyword">import</span> ColumnTransformer
<span class="hljs-keyword">from</span> sklearn.pipeline <span class="hljs-keyword">import</span> Pipeline

categorical_features = X_train.select_dtypes(include=[<span class="hljs-string">'object'</span>]).columns.tolist()
categorical_transformer = Pipeline(steps=[(<span class="hljs-string">'imputer'</span>, SimpleImputer(strategy=<span class="hljs-string">'most_frequent'</span>)),(<span class="hljs-string">'onehot'</span>, OneHotEncoder(handle_unknown=<span class="hljs-string">'ignore'</span>))])

numerical_features = X_train.select_dtypes(include=[<span class="hljs-string">'int64'</span>, <span class="hljs-string">'float64'</span>]).columns.tolist()
numerical_transformer = Pipeline(steps=[(<span class="hljs-string">'imputer'</span>, SimpleImputer(strategy=<span class="hljs-string">'mean'</span>)), (<span class="hljs-string">'scaler'</span>, StandardScaler())])

preprocessor = ColumnTransformer(
    transformers=[
        (<span class="hljs-string">'num'</span>, numerical_transformer, numerical_features),
        (<span class="hljs-string">'cat'</span>, categorical_transformer, categorical_features)
    ]
)

X_train_processed = preprocessor.fit_transform(X_train)
X_val_processed = preprocessor.transform(X_val)
X_test_processed = preprocessor.transform(X_test)
</code></pre>
<h2 id="heading-understanding-optimizers">Understanding Optimizers</h2>
<p>In deep learning, an optimizer is a crucial element that fine-tunes a neural network’s parameters during training. Its primary role is to minimize the model’s loss function, enhancing performance.</p>
<p>Various optimization algorithms, known as optimizers, employ distinct strategies to converge towards optimal parameters for improved predictions efficiently.</p>
<p>In this article, we’ll use the SGD Optimizer and Adam Optimizer.</p>
<h3 id="heading-1-how-a-sgd-stochastic-gradient-descent-optimizer-works">1. How a SGD (Stochastic Gradient Descent) Optimizer Works</h3>
<p>SGD is a major optimization algorithm that computes the gradient (partial derivative of the cost function) using a small mini-batch of examples at each epoch:</p>
<p>$$\begin{align*} w_j &amp;:= w_j - \eta \frac {\partial J} {\partial w_j} \\ \\ b &amp;:= b - \eta \frac {\partial J} {\partial b} \end{align*}$$</p><p>(w: weight, b: bias, J: cost function, <em>η</em>: learning rate)</p>
<p>In binary classification, the cost function (J) is defined with a sigmoid function (σ(z)) where z generates weighted sum of inputs and bias terms:</p>
<p>$$\begin{align*} J(y, \hat y) &amp;=−[y log(\hat y) + (1-y)log(1-\hat y)] \\ \\ \hat y &amp;= \sigma (z) = \frac {1} {1+e^{-z}} \\ \\ z &amp;= \sum_{i=1}^m w_i x_i + b \end {align*}$$</p><h3 id="heading-2-how-adam-adaptive-moment-estimation-optimizer-works">2. How Adam (Adaptive Moment Estimation) Optimizer Works</h3>
<p>Adam is an optimization algorithm that computes <strong>individual adaptive learning rates</strong> for different parameters from estimates of first and second moments of the gradients.</p>
<p>Adam optimizer combines the advantages of <a target="_blank" href="https://keras.io/api/optimizers/rmsprop/"><strong>RMSprop</strong></a> (using squared gradients to scale the learning rate) and <a target="_blank" href="https://optimization.cbe.cornell.edu/index.php?title=Momentum"><strong>Momentum</strong></a> (using past gradients to accelerate convergence):</p>
<p>$$w_{j,t+1} = w_{j,t} - \alpha \cdot \frac{\hat{m}{t,w_j}}{\sqrt{\hat{v}{t,w_j}} + \epsilon}$$</p><p>where:</p>
<ul>
<li><p><code>α</code>: The learning rate (default is 0.001)</p>
</li>
<li><p><code>ϵ</code>: A small positive constant used to avoid division by zero</p>
</li>
<li><p><code>m^</code>: First moment (mean) estimate with a bias correction, leveraging <strong>Momentum</strong>:</p>
</li>
</ul>
<p>$$\begin{align*} \hat m_t &amp;= \frac {m_t} {1 - \beta_1^t} \\ \\ m_t &amp;= \beta_1 m_{t-1} + (1-\beta_1) \underbrace{ \frac {\partial L} {\partial w_t}}_{\text{gradient}} \end{align*}$$</p><p>(<code>β1</code>​​: <strong>Decay rates</strong>, typically set to β1=0.9)</p>
<p><code>v^</code>: Second moment (variance) estimate with a bias correction, leveraging <strong>RMSprop</strong>:</p>
<p>$$\begin{align*} \hat v_t &amp;= \frac {v_t} {1 - \beta_2^t} \\ \\ v_t &amp;=\beta_2 v_{t-1} + (1- \beta_2) (\frac {\partial L} {\partial w_t})^2 \end {align*}$$</p><p>(<code>β2</code>​​: <strong>Decay rates</strong>, typically set to β2=0.999)</p>
<p>Since both <code>m</code>​​ and <code>v</code>​ are initialized at zero, Adam computes the bias-corrected estimates to prevent them being biased toward zero.</p>
<p>Learn More: <a target="_blank" href="https://medium.com/@kuriko-iwai/a-comprehensive-guide-on-neural-network-in-deep-learning-9c795a1f1648">A Comprehensive Guide on Neural Network in Deep Learning</a></p>
<h2 id="heading-how-to-build-an-mlp-classifier-with-sgd-optimizer">How to Build an MLP Classifier with SGD Optimizer</h2>
<h3 id="heading-custom-classifier">Custom Classifier</h3>
<p>This process involves a <strong>forward pass</strong> and <strong>backpropagation</strong>, during which SGD computes optimal weights and biases using gradients:</p>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">0</span>, n_samples, self.batch_size):
    <span class="hljs-comment"># SGD starts with randomly selected mini-batch for the epoch</span>
    X_batch = X_shuffled[i : i + self.batch_size]
    y_batch = y_shuffled[i : i + self.batch_size]

    <span class="hljs-comment"># A. forward pass</span>
    activations, zs = self._forward_pass(X_batch)
    y_pred = activations[<span class="hljs-number">-1</span>]  <span class="hljs-comment"># final output of the network</span>

    <span class="hljs-comment"># B. backpropagation</span>
    <span class="hljs-comment"># 1) calculating gradients for the output layer)</span>
    delta = y_pred - y_batch
    dW = np.dot(activations[<span class="hljs-number">-2</span>].T, delta) / X_batch.shape[<span class="hljs-number">0</span>]
    db = np.sum(delta, axis=<span class="hljs-number">0</span>) / X_batch.shape[<span class="hljs-number">0</span>]

    <span class="hljs-comment"># 2) update output layer parameters</span>
    self.weights[<span class="hljs-number">-1</span>] -= self.learning_rate * dW
    self.biases[<span class="hljs-number">-1</span>] -= self.learning_rate * db

    <span class="hljs-comment"># 3) iterate backward from last hidden layer to the input layer</span>
    <span class="hljs-keyword">for</span> l <span class="hljs-keyword">in</span> range(len(self.weights) - <span class="hljs-number">2</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">-1</span>):
        delta = np.dot(delta, self.weights[l+<span class="hljs-number">1</span>].T) * self._relu_derivative(zs[l]) <span class="hljs-comment"># d_activation(z)</span>
        dW = np.dot(activations[l].T, delta) / X_batch.shape[<span class="hljs-number">0</span>]
        db = np.sum(delta, axis=<span class="hljs-number">0</span>) / X_batch.shape[<span class="hljs-number">0</span>]

        self.weights[l] -= self.learning_rate * dW
        self.biases[l] -= self.learning_rate * db
</code></pre>
<p>In the process of the forward pass, the network calculates a weighted sum of weights and bias (z), applies an activation function (ReLU) to the values in each hidden layer, and then computes the predicted output (y_pred) using a sigmoid function.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_forward_pass</span>(<span class="hljs-params">self, X</span>):</span>
    activations = [X]
    zs = []

    <span class="hljs-comment"># forward through hidden layers</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(self.weights) - <span class="hljs-number">1</span>):
        z = np.dot(activations[<span class="hljs-number">-1</span>], self.weights[i]) + self.biases[i]
        zs.append(z)
        a = self._relu(z) <span class="hljs-comment"># using ReLU for hidden layers</span>
        activations.append(a)

    <span class="hljs-comment"># forward through output layer</span>
    z_output = np.dot(activations[<span class="hljs-number">-1</span>], self.weights[<span class="hljs-number">-1</span>]) + self.biases[<span class="hljs-number">-1</span>]
    zs.append(z_output)

    <span class="hljs-comment"># computes the final output using sigmoid function</span>
    y_pred = <span class="hljs-number">1</span> / (<span class="hljs-number">1</span> + np.exp(-np.clip(x, <span class="hljs-number">-500</span>, <span class="hljs-number">500</span>)))
    activations.append(y_pred)
    <span class="hljs-keyword">return</span> activations, zs
</code></pre>
<p>So the final classifier looks like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> accuracy_score

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MLP_SGD</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, hidden_layer_sizes=(<span class="hljs-params"><span class="hljs-number">10</span>,</span>), learning_rate=<span class="hljs-number">0.01</span>, n_epochs=<span class="hljs-number">1000</span>, batch_size=<span class="hljs-number">32</span></span>):</span>
        self.hidden_layer_sizes = hidden_layer_sizes
        self.learning_rate = learning_rate
        self.n_epochs = n_epochs
        self.batch_size = batch_size
        self.weights = []
        self.biases = []
        self.weights_history = []
        self.biases_history = []
        self.loss_history = []

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_sigmoid</span>(<span class="hljs-params">self, x</span>):</span>
        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span> / (<span class="hljs-number">1</span> + np.exp(-np.clip(x, <span class="hljs-number">-500</span>, <span class="hljs-number">500</span>)))

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_sigmoid_derivative</span>(<span class="hljs-params">self, x</span>):</span>
        s = self._sigmoid(x)
        <span class="hljs-keyword">return</span> s * (<span class="hljs-number">1</span> - s)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_relu</span>(<span class="hljs-params">self, x</span>):</span>
        <span class="hljs-keyword">return</span> np.maximum(<span class="hljs-number">0</span>, x)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_relu_derivative</span>(<span class="hljs-params">self, x</span>):</span>
        <span class="hljs-keyword">return</span> (x &gt; <span class="hljs-number">0</span>).astype(float)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_initialize_parameters</span>(<span class="hljs-params">self, n_features</span>):</span>
        layer_sizes = [n_features] + list(self.hidden_layer_sizes) + [<span class="hljs-number">1</span>]
        self.weights = []
        self.biases = []

        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(layer_sizes) - <span class="hljs-number">1</span>):
            fan_in = layer_sizes[i]
            fan_out = layer_sizes[i+<span class="hljs-number">1</span>]
            limit = np.sqrt(<span class="hljs-number">6</span> / (fan_in + fan_out))
            self.weights.append(np.random.uniform(-limit, limit, (fan_in, fan_out)))
            self.biases.append(np.zeros((<span class="hljs-number">1</span>, fan_out)))

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_forward_pass</span>(<span class="hljs-params">self, X</span>):</span>
        activations = [X]
        zs = []

        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(self.weights) - <span class="hljs-number">1</span>):
            z = np.dot(activations[<span class="hljs-number">-1</span>], self.weights[i]) + self.biases[i]
            zs.append(z)
            a = self._relu(z)
            activations.append(a)

        z_output = np.dot(activations[<span class="hljs-number">-1</span>], self.weights[<span class="hljs-number">-1</span>]) + self.biases[<span class="hljs-number">-1</span>]
        zs.append(z_output)
        y_pred = self._sigmoid(z_output)
        activations.append(y_pred)

        <span class="hljs-keyword">return</span> activations, zs

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_compute_loss</span>(<span class="hljs-params">self, y_true, y_pred</span>):</span>
        y_pred = np.clip(y_pred, <span class="hljs-number">1e-10</span>, <span class="hljs-number">1</span> - <span class="hljs-number">1e-10</span>)
        loss = -np.mean(y_true * np.log(y_pred) + (<span class="hljs-number">1</span> - y_true) * np.log(<span class="hljs-number">1</span> - y_pred))
        <span class="hljs-keyword">return</span> loss

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fit</span>(<span class="hljs-params">self, X, y</span>):</span>
        n_samples, n_features = X.shape
        y = np.asarray(y).reshape(<span class="hljs-number">-1</span>, <span class="hljs-number">1</span>)
        X = np.asarray(X)
        self._initialize_parameters(n_features)
        self.weights_history.append([w.copy() <span class="hljs-keyword">for</span> w <span class="hljs-keyword">in</span> self.weights])
        self.biases_history.append([b.copy() <span class="hljs-keyword">for</span> b <span class="hljs-keyword">in</span> self.biases])
        activations, _ = self._forward_pass(X)
        initial_loss = self._compute_loss(y, activations[<span class="hljs-number">-1</span>])
        self.loss_history.append(initial_loss)

        <span class="hljs-keyword">for</span> epoch <span class="hljs-keyword">in</span> range(self.n_epochs):
            <span class="hljs-comment"># shuffle datasets</span>
            permutation = np.random.permutation(n_samples)
            X_shuffled = X[permutation]
            y_shuffled = y[permutation]

            <span class="hljs-comment"># mini-batch loop</span>
            <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">0</span>, n_samples, self.batch_size):
                X_batch = X_shuffled[i : i + self.batch_size]
                y_batch = y_shuffled[i : i + self.batch_size]

                activations, zs = self._forward_pass(X_batch)
                y_pred = activations[<span class="hljs-number">-1</span>]

                delta = y_pred - y_batch
                dW = np.dot(activations[<span class="hljs-number">-2</span>].T, delta) / X_batch.shape[<span class="hljs-number">0</span>]
                db = np.sum(delta, axis=<span class="hljs-number">0</span>) / X_batch.shape[<span class="hljs-number">0</span>]
                self.weights[<span class="hljs-number">-1</span>] -= self.learning_rate * dW
                self.biases[<span class="hljs-number">-1</span>] -= self.learning_rate * db

                <span class="hljs-keyword">for</span> l <span class="hljs-keyword">in</span> range(len(self.weights) - <span class="hljs-number">2</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">-1</span>):
                    delta = np.dot(delta, self.weights[l+<span class="hljs-number">1</span>].T) * self._relu_derivative(zs[l]) <span class="hljs-comment"># d_activation(z)</span>
                    dW = np.dot(activations[l].T, delta) / X_batch.shape[<span class="hljs-number">0</span>]
                    db = np.sum(delta, axis=<span class="hljs-number">0</span>) / X_batch.shape[<span class="hljs-number">0</span>]

                    self.weights[l] -= self.learning_rate * dW
                    self.biases[l] -= self.learning_rate * db

            self.weights_history.append([w.copy() <span class="hljs-keyword">for</span> w <span class="hljs-keyword">in</span> self.weights])
            self.biases_history.append([b.copy() <span class="hljs-keyword">for</span> b <span class="hljs-keyword">in</span> self.biases])

            activations, _ = self._forward_pass(X)
            epoch_loss = self._compute_loss(y, activations[<span class="hljs-number">-1</span>])
            self.loss_history.append(epoch_loss)

            <span class="hljs-keyword">if</span> (epoch + <span class="hljs-number">1</span>) % <span class="hljs-number">100</span> == <span class="hljs-number">0</span>:
                print(<span class="hljs-string">f"Epoch <span class="hljs-subst">{epoch+<span class="hljs-number">1</span>}</span>/<span class="hljs-subst">{self.n_epochs}</span>, Loss: <span class="hljs-subst">{epoch_loss:<span class="hljs-number">.4</span>f}</span>"</span>)
        <span class="hljs-keyword">return</span> self

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict_proba</span>(<span class="hljs-params">self, X</span>):</span>
        activations, _ = self._forward_pass(X)
        <span class="hljs-keyword">return</span> activations[<span class="hljs-number">-1</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict</span>(<span class="hljs-params">self, X, threshold=<span class="hljs-number">0.5</span></span>):</span>
        probabilities = self.predict_proba(X)
        <span class="hljs-keyword">return</span> (probabilities &gt;= threshold).astype(int).flatten() <span class="hljs-comment"># for 1D output</span>
</code></pre>
<h3 id="heading-training-prediction">Training / Prediction</h3>
<p>Train the model and make a prediction using training and validation datasets:</p>
<pre><code class="lang-python"><span class="hljs-comment"># 1. define the model</span>
mlp_sgd = MLP_SGD(
  hidden_layer_sizes=(<span class="hljs-number">30</span>, <span class="hljs-number">30</span>, ), <span class="hljs-comment"># 2 hidden layers with 30 neurons each</span>
  learning_rate=<span class="hljs-number">0.001</span>,           <span class="hljs-comment"># a step size</span>
  n_epochs=<span class="hljs-number">1000</span>,                 <span class="hljs-comment"># number of epochs</span>
  batch_size=<span class="hljs-number">32</span>                  <span class="hljs-comment"># mini-batch size</span>
)

<span class="hljs-comment"># 2. train the model</span>
mlp_sgd.fit(X_train_processed, y_train)

<span class="hljs-comment"># 3. make a prediction with training and validation datasets</span>
y_pred_train = mlp_sgd.predict(X_train_processed)
y_pred_val = mlp_sgd.predict(X_val_processed)

<span class="hljs-comment"># 4. compute evaluation matrics</span>
conf_matrix = confusion_matrix(y_true, y_pred)
acc = accuracy_score(y_true, y_pred)
precision = precision_score(y_true, y_pred, pos_label=<span class="hljs-number">1</span>)
recall = recall_score(y_true, y_pred, pos_label=<span class="hljs-number">1</span>)
f1 = f1_score(y_true, y_pred, pos_label=<span class="hljs-number">1</span>)


print(<span class="hljs-string">f"\nMLP (Custom SGD) Accuracy (Train): <span class="hljs-subst">{acc_train:<span class="hljs-number">.3</span>f}</span>"</span>)
print(<span class="hljs-string">f"MLP (Custom SGD) Accuracy (Validation): <span class="hljs-subst">{acc_val:<span class="hljs-number">.3</span>f}</span>"</span>)
</code></pre>
<h3 id="heading-results-2">Results</h3>
<ul>
<li><p>Recall: <em>0.7930 — 0.6650 (from training to validation)</em></p>
</li>
<li><p>Precision: <em>0.7790 — 0.6786 (from training to validation)</em></p>
</li>
</ul>
<p>The model effectively learned and generalized the patterns, achieving a <strong>Recall of 79.3%</strong> (approximately 80% accuracy in identifying fraud transactions) with a 12-point drop on the validation set.</p>
<p><strong>Loss history:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748441103897/088deb38-846d-4026-a706-701be93036ca.png" alt="Loss by epoch, weight history, bias history (Source: Kuriko Iwai)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>We visualized the <strong>decision boundary</strong> using the first two principal components (PCA) as the x and y axes. Note that the boundary is non-linear.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748442430297/032ee809-1b7e-4bb1-81c0-8715361658a5.png" alt="Image: Decision Boundary of MLP Classifier with SGD optimizer (Source: Kuriko Iwai)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-leverage-sckitlearns-mcp-classifier">Leverage SckitLearn’s MCP Classifier</h3>
<p>We can use an MCP Classifier to define a similar model, incorporating;</p>
<ul>
<li><p><strong>Early stopping</strong> using internal validation to prevent overfitting and</p>
</li>
<li><p><strong>L2 regularization</strong> with a small tolerance.</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.neural_network <span class="hljs-keyword">import</span> MLPClassifier

<span class="hljs-comment"># define a model</span>
model_sklearn_mlp_sgd = MLPClassifier(
    hidden_layer_sizes=(<span class="hljs-number">30</span>, <span class="hljs-number">30</span>),
    activation=<span class="hljs-string">'relu'</span>,
    solver=<span class="hljs-string">'sgd'</span>,
    learning_rate_init=<span class="hljs-number">0.001</span>,
    learning_rate=<span class="hljs-string">'constant'</span>,
    momentum=<span class="hljs-number">0.9</span>,
    nesterovs_momentum=<span class="hljs-literal">True</span>,
    alpha=<span class="hljs-number">0.00001</span>,           <span class="hljs-comment"># l2 regulation strength</span>
    max_iter=<span class="hljs-number">3000</span>,           <span class="hljs-comment"># max epochs (keep it high)</span>
    batch_size=<span class="hljs-number">16</span>,           <span class="hljs-comment"># mini-batch size</span>
    random_state=<span class="hljs-number">42</span>,
    early_stopping=<span class="hljs-literal">True</span>,     <span class="hljs-comment"># apply early stopping</span>
    n_iter_no_change=<span class="hljs-number">50</span>,     <span class="hljs-comment"># stop the iteration if internal validation score doesn't improve for 50 epochs</span>
    validation_fraction=<span class="hljs-number">0.1</span>, <span class="hljs-comment"># proportion of training data for internal validation (default is 0.1)</span>
    tol=<span class="hljs-number">1e-4</span>,                <span class="hljs-comment"># tolerance for optimization</span>
    verbose=<span class="hljs-literal">False</span>,
)

<span class="hljs-comment"># training</span>
model_sklearn_mlp_sgd.fit(X_train_processed, y_train)

<span class="hljs-comment"># make a prediction</span>
y_pred_train_sklearn = model_sklearn_mlp_sgd.predict(X_train_processed)
y_pred_val_sklearn = model_sklearn_mlp_sgd.predict(X_val_processed)
</code></pre>
<h3 id="heading-results-3">Results</h3>
<ul>
<li><p>Recall: <em>0.7830 - 0.6200 (from training to validation)</em></p>
</li>
<li><p>Precision: <em>0.8208  - 0.6703 (from training to validation)</em></p>
</li>
</ul>
<p>The model showed strong performance during training, achieving a Recall <strong>of 78.30%</strong>. Its performance declined on the validation set.</p>
<p>This suggests that while the model learned effectively from the training data, it may be overfitting and not generalizing as well to unseen data.</p>
<h3 id="heading-leverage-keras-sequential-classifier">Leverage Keras Sequential Classifier</h3>
<p>For the sequential classifier, we can further enhance the classifier by:</p>
<ul>
<li><p>Initializing the output layer’s bias with the log-odds of positive class occurrences in the training data (y_train​) to address dataset imbalance and promote faster convergence,</p>
</li>
<li><p>Integrating 10% dropout between hidden layers to prevent overfitting by randomly deactivating neurons during training,</p>
</li>
<li><p>Including Precision and Recall in the model’s compilation metrics to optimize for classification performance,</p>
</li>
<li><p>Applying class weights to penalize misclassifications of the minority class more heavily, improving the model’s ability to learn rare patterns, and</p>
</li>
<li><p>Utilizing a separate validation dataset for monitoring performance during training to help detect overfitting and guides hyperparameter tuning.</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> tensorflow <span class="hljs-keyword">as</span> tf
<span class="hljs-keyword">from</span> tensorflow <span class="hljs-keyword">import</span> keras
<span class="hljs-keyword">from</span> keras.models <span class="hljs-keyword">import</span> Sequential
<span class="hljs-keyword">from</span> keras.layers <span class="hljs-keyword">import</span> Dense, Dropout, Input
<span class="hljs-keyword">from</span> keras.optimizers <span class="hljs-keyword">import</span> SGD
<span class="hljs-keyword">from</span> keras.callbacks <span class="hljs-keyword">import</span> EarlyStopping
<span class="hljs-keyword">from</span> sklearn.utils <span class="hljs-keyword">import</span> class_weight


<span class="hljs-comment"># calculates an initial bias for the output layer </span>
initial_bias = np.log([np.sum(y_train == <span class="hljs-number">1</span>) / np.sum(y_train == <span class="hljs-number">0</span>)])


<span class="hljs-comment"># defines the model</span>
model_keras_sgd = Sequential([
    Input(shape=(X_train_processed.shape[<span class="hljs-number">1</span>],)), 
    Dense(<span class="hljs-number">30</span>, activation=<span class="hljs-string">'relu'</span>),
    Dropout(<span class="hljs-number">0.1</span>), <span class="hljs-comment"># 10% of the neurons in that layer randomly dropped out</span>
    Dense(<span class="hljs-number">30</span>, activation=<span class="hljs-string">'relu'</span>),
    Dropout(<span class="hljs-number">0.1</span>),
    Dense(<span class="hljs-number">1</span>, activation=<span class="hljs-string">'sigmoid'</span>, <span class="hljs-comment"># binary classification</span>
          bias_initializer=tf.keras.initializers.Constant(initial_bias)) <span class="hljs-comment"># to address the imbalanced datasets</span>
])



<span class="hljs-comment"># compiles the model with the SGD optimizer</span>
opt = SGD(learning_rate=<span class="hljs-number">0.001</span>)
model_keras_sgd.compile(
    optimizer=opt, 
    loss=<span class="hljs-string">'binary_crossentropy'</span>,
    metrics=[
        <span class="hljs-string">'accuracy'</span>, <span class="hljs-comment"># add several metrics to return</span>
        tf.keras.metrics.Precision(name=<span class="hljs-string">'precision'</span>),
        tf.keras.metrics.Recall(name=<span class="hljs-string">'recall'</span>),
        tf.keras.metrics.AUC(name=<span class="hljs-string">'auc'</span>) 
    ]
)


<span class="hljs-comment"># defines early stopping to prevent overfitting</span>
early_stopping_callback = EarlyStopping(
    monitor=<span class="hljs-string">'val_recall'</span>,  <span class="hljs-comment"># monitor recall </span>
    mode=<span class="hljs-string">'max'</span>,         <span class="hljs-comment"># maximize recall</span>
    patience=<span class="hljs-number">50</span>,        <span class="hljs-comment"># stop after 50 epochs without loss improvement</span>
    min_delta=<span class="hljs-number">1e-4</span>,     <span class="hljs-comment"># minimum change to be considered an improvement (tol)</span>
    verbose=<span class="hljs-number">0</span>
)


<span class="hljs-comment"># compute the class weight</span>
class_weights = class_weight.compute_class_weight(
    class_weight=<span class="hljs-string">'balanced'</span>,
    classes=np.unique(y_train),
    y=y_train
)
class_weights_dict = dict(zip(np.unique(y_train), class_weights))


<span class="hljs-comment"># train the model</span>
history = model_keras_sgd.fit(
    X_train_processed, y_train,
    epochs=<span class="hljs-number">1000</span>,
    batch_size=<span class="hljs-number">32</span>,
    validation_data=(X_val_processed, y_val), <span class="hljs-comment"># use our external val set</span>
    callbacks=[early_stopping_callback], <span class="hljs-comment"># early stopping to prevent overfitting</span>
    class_weight=class_weights_dict, <span class="hljs-comment"># penarlize more misclassification on minority class</span>
    verbose=<span class="hljs-number">0</span>
)

<span class="hljs-comment"># evaluate</span>
loss_train, accuracy_train, precision_train, recall_train, auc_train = model_keras_sgd.evaluate(X_train_processed, y_train, verbose=<span class="hljs-number">0</span>)
print(<span class="hljs-string">f"\n--- Keras Model Accuracy (Train) ---"</span>)
print(<span class="hljs-string">f"Loss: <span class="hljs-subst">{loss_train:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Accuracy: <span class="hljs-subst">{accuracy_train:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Precision: <span class="hljs-subst">{precision_train:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Recall: <span class="hljs-subst">{recall_train:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"AUC: <span class="hljs-subst">{auc_train:<span class="hljs-number">.4</span>f}</span>"</span>)

loss_val, accuracy_val, precision_val, recall_val, auc_val = model_keras_sgd.evaluate(X_val_processed, y_val, verbose=<span class="hljs-number">0</span>)
print(<span class="hljs-string">f"\n--- Keras Model Accuracy (Validation) ---"</span>)
print(<span class="hljs-string">f"Loss: <span class="hljs-subst">{loss_val:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Accuracy: <span class="hljs-subst">{accuracy_val:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Precision: <span class="hljs-subst">{precision_val:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Recall: <span class="hljs-subst">{recall_val:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"AUC: <span class="hljs-subst">{auc_val:<span class="hljs-number">.4</span>f}</span>"</span>)

<span class="hljs-comment"># display model summary</span>
model_keras_sgd.summary()
</code></pre>
<h3 id="heading-results-4">Results</h3>
<ul>
<li><p>Recall: <em>0.7125 — 0.7250 (from training to validation)</em></p>
</li>
<li><p>Precision: <em>0.7607 — 0.7545 (from training to validation)</em></p>
</li>
</ul>
<p>Given that the gaps between training and validation are relatively small, the model is generalizing reasonably well.</p>
<p>It suggests that the regularization techniques are likely effective in preventing significant overfitting.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748441165170/4e0528e3-514a-454c-b52a-2a0318ba405a.png" alt="Image: Summary of the Keras Sequential Model with SGD Optimizer" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-build-an-mlp-classifier-with-adam-optimizer">How to Build an MLP Classifier with Adam Optimizer</h2>
<h3 id="heading-custom-classifier-1">Custom Classifier</h3>
<p>This iterative process of updating parameters occurs within the mini-batch loop to keep updating weights and bias:</p>
<pre><code class="lang-python"><span class="hljs-comment"># apply Adam updates for output layer parameters</span>
<span class="hljs-comment"># 1) weights (w)</span>
self.m_weights[<span class="hljs-number">-1</span>] = self.beta1 * self.m_weights[<span class="hljs-number">-1</span>] + (<span class="hljs-number">1</span> - self.beta1) * grad_w_output
self.v_weights[<span class="hljs-number">-1</span>] = self.beta2 * self.v_weights[<span class="hljs-number">-1</span>] + (<span class="hljs-number">1</span> - self.beta2) * (grad_w_output ** <span class="hljs-number">2</span>)
m_w_hat = self.m_weights[<span class="hljs-number">-1</span>] / (<span class="hljs-number">1</span> - self.beta1**t)
v_w_hat = self.v_weights[<span class="hljs-number">-1</span>] / (<span class="hljs-number">1</span> - self.beta2**t)
self.weights[<span class="hljs-number">-1</span>] -= self.learning_rate * m_w_hat / (np.sqrt(v_w_hat) + self.epsilon)

<span class="hljs-comment"># 2) bias (b)</span>
self.m_biases[<span class="hljs-number">-1</span>] = self.beta1 * self.m_biases[<span class="hljs-number">-1</span>] + (<span class="hljs-number">1</span> - self.beta1) * grad_b_output
self.v_biases[<span class="hljs-number">-1</span>] = self.beta2 * self.v_biases[<span class="hljs-number">-1</span>] + (<span class="hljs-number">1</span> - self.beta2) * (grad_b_output ** <span class="hljs-number">2</span>)
m_b_hat = self.m_biases[<span class="hljs-number">-1</span>] / (<span class="hljs-number">1</span> - self.beta1**t)
v_b_hat = self.v_biases[<span class="hljs-number">-1</span>] / (<span class="hljs-number">1</span> - self.beta2**t)
self.biases[<span class="hljs-number">-1</span>] -= self.learning_rate * m_b_hat / (np.sqrt(v_b_hat) + self.epsilon)
</code></pre>
<p>Following the principles of forward and backward passes, we construct the final classifier by initializing it with <code>beta1</code> and <code>beta2</code>, built upon an <code>MLP_SGD</code> architecture:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MLP_Adam</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, hidden_layer_sizes=(<span class="hljs-params"><span class="hljs-number">10</span>,</span>), learning_rate=<span class="hljs-number">0.001</span>, n_epochs=<span class="hljs-number">1000</span>, batch_size=<span class="hljs-number">32</span>,
                 beta1=<span class="hljs-number">0.9</span>, beta2=<span class="hljs-number">0.999</span>, epsilon=<span class="hljs-number">1e-8</span></span>):</span>
        self.hidden_layer_sizes = hidden_layer_sizes
        self.learning_rate = learning_rate
        self.n_epochs = n_epochs
        self.batch_size = batch_size
        self.beta1 = beta1
        self.beta2 = beta2
        self.epsilon = epsilon

        self.weights = [] 
        self.biases = []

        <span class="hljs-comment"># Adam optimizer internal states for each parameter (weights and biases)</span>
        self.m_weights = []
        self.v_weights = []
        self.m_biases = []
        self.v_biases = []

        self.weights_history = []
        self.biases_history = []
        self.loss_history = []

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_sigmoid</span>(<span class="hljs-params">self, x</span>):</span>
        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span> / (<span class="hljs-number">1</span> + np.exp(-np.clip(x, <span class="hljs-number">-500</span>, <span class="hljs-number">500</span>)))

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_sigmoid_derivative</span>(<span class="hljs-params">self, x</span>):</span>
        s = self._sigmoid(x)
        <span class="hljs-keyword">return</span> s * (<span class="hljs-number">1</span> - s)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_relu</span>(<span class="hljs-params">self, x</span>):</span>
        <span class="hljs-keyword">return</span> np.maximum(<span class="hljs-number">0</span>, x)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_relu_derivative</span>(<span class="hljs-params">self, x</span>):</span>
        <span class="hljs-keyword">return</span> (x &gt; <span class="hljs-number">0</span>).astype(float)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_initialize_parameters</span>(<span class="hljs-params">self, n_features</span>):</span>
        layer_sizes = [n_features] + list(self.hidden_layer_sizes) + [<span class="hljs-number">1</span>]

        self.weights = []
        self.biases = []
        self.m_weights = []
        self.v_weights = []
        self.m_biases = []
        self.v_biases = []

        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(layer_sizes) - <span class="hljs-number">1</span>):
            fan_in = layer_sizes[i]
            fan_out = layer_sizes[i+<span class="hljs-number">1</span>]
            limit = np.sqrt(<span class="hljs-number">6</span> / (fan_in + fan_out))

            self.weights.append(np.random.uniform(-limit, limit, (fan_in, fan_out)))
            self.biases.append(np.zeros((<span class="hljs-number">1</span>, fan_out)))

            self.m_weights.append(np.zeros((fan_in, fan_out)))
            self.v_weights.append(np.zeros((fan_in, fan_out)))
            self.m_biases.append(np.zeros((<span class="hljs-number">1</span>, fan_out)))
            self.v_biases.append(np.zeros((<span class="hljs-number">1</span>, fan_out)))


    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_forward_pass</span>(<span class="hljs-params">self, X</span>):</span>
        activations = [X]
        zs = []

        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(self.weights) - <span class="hljs-number">1</span>):
            z = np.dot(activations[<span class="hljs-number">-1</span>], self.weights[i]) + self.biases[i]
            zs.append(z)
            a = self._relu(z)
            activations.append(a)

        z_output = np.dot(activations[<span class="hljs-number">-1</span>], self.weights[<span class="hljs-number">-1</span>]) + self.biases[<span class="hljs-number">-1</span>]
        zs.append(z_output)
        y_pred = self._sigmoid(z_output)
        activations.append(y_pred)

        <span class="hljs-keyword">return</span> activations, zs

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_compute_loss</span>(<span class="hljs-params">self, y_true, y_pred</span>):</span>
        y_pred = np.clip(y_pred, <span class="hljs-number">1e-10</span>, <span class="hljs-number">1</span> - <span class="hljs-number">1e-10</span>)
        loss = -np.mean(y_true * np.log(y_pred) + (<span class="hljs-number">1</span> - y_true) * np.log(<span class="hljs-number">1</span> - y_pred))
        <span class="hljs-keyword">return</span> loss

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fit</span>(<span class="hljs-params">self, X, y</span>):</span>
        n_samples, n_features = X.shape
        y = np.asarray(y).reshape(<span class="hljs-number">-1</span>, <span class="hljs-number">1</span>)
        X = np.asarray(X)

        self._initialize_parameters(n_features)
        self.weights_history.append([w.copy() <span class="hljs-keyword">for</span> w <span class="hljs-keyword">in</span> self.weights])
        self.biases_history.append([b.copy() <span class="hljs-keyword">for</span> b <span class="hljs-keyword">in</span> self.biases])
        activations, _ = self._forward_pass(X)
        initial_loss = self._compute_loss(y, activations[<span class="hljs-number">-1</span>])
        self.loss_history.append(initial_loss)

        <span class="hljs-comment"># global time step for Adam bias correction</span>
        t = <span class="hljs-number">0</span>

        <span class="hljs-keyword">for</span> epoch <span class="hljs-keyword">in</span> range(self.n_epochs):
            permutation = np.random.permutation(n_samples)
            X_shuffled = X[permutation]
            y_shuffled = y[permutation]

            <span class="hljs-comment"># Mini-batch loop</span>
            <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">0</span>, n_samples, self.batch_size):
                X_batch = X_shuffled[i : i + self.batch_size]
                y_batch = y_shuffled[i : i + self.batch_size]

                t += <span class="hljs-number">1</span>

                <span class="hljs-comment"># 1. forward pass</span>
                activations, zs = self._forward_pass(X_batch)
                y_pred = activations[<span class="hljs-number">-1</span>] <span class="hljs-comment"># Output of the network</span>

                <span class="hljs-comment"># 2. backpropagation</span>
                delta = y_pred - y_batch
                grad_w_output = np.dot(activations[<span class="hljs-number">-2</span>].T, delta) / X_batch.shape[<span class="hljs-number">0</span>] <span class="hljs-comment"># Average over batch</span>
                grad_b_output = np.sum(delta, axis=<span class="hljs-number">0</span>) / X_batch.shape[<span class="hljs-number">0</span>]

                <span class="hljs-comment"># apply Adam updates to weights</span>
                self.m_weights[<span class="hljs-number">-1</span>] = self.beta1 * self.m_weights[<span class="hljs-number">-1</span>] + (<span class="hljs-number">1</span> - self.beta1) * grad_w_output
                self.v_weights[<span class="hljs-number">-1</span>] = self.beta2 * self.v_weights[<span class="hljs-number">-1</span>] + (<span class="hljs-number">1</span> - self.beta2) * (grad_w_output ** <span class="hljs-number">2</span>)
                m_w_hat = self.m_weights[<span class="hljs-number">-1</span>] / (<span class="hljs-number">1</span> - self.beta1**t)
                v_w_hat = self.v_weights[<span class="hljs-number">-1</span>] / (<span class="hljs-number">1</span> - self.beta2**t)
                self.weights[<span class="hljs-number">-1</span>] -= self.learning_rate * m_w_hat / (np.sqrt(v_w_hat) + self.epsilon)

                <span class="hljs-comment"># apply Adam updates to bias</span>
                self.m_biases[<span class="hljs-number">-1</span>] = self.beta1 * self.m_biases[<span class="hljs-number">-1</span>] + (<span class="hljs-number">1</span> - self.beta1) * grad_b_output
                self.v_biases[<span class="hljs-number">-1</span>] = self.beta2 * self.v_biases[<span class="hljs-number">-1</span>] + (<span class="hljs-number">1</span> - self.beta2) * (grad_b_output ** <span class="hljs-number">2</span>)
                m_b_hat = self.m_biases[<span class="hljs-number">-1</span>] / (<span class="hljs-number">1</span> - self.beta1**t)
                v_b_hat = self.v_biases[<span class="hljs-number">-1</span>] / (<span class="hljs-number">1</span> - self.beta2**t)
                self.biases[<span class="hljs-number">-1</span>] -= self.learning_rate * m_b_hat / (np.sqrt(v_b_hat) + self.epsilon)


                <span class="hljs-comment"># Propagate gradients backward through hidden layers</span>
                <span class="hljs-keyword">for</span> l <span class="hljs-keyword">in</span> range(len(self.weights) - <span class="hljs-number">2</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">-1</span>):
                    delta = np.dot(delta, self.weights[l+<span class="hljs-number">1</span>].T) * self._relu_derivative(zs[l]) <span class="hljs-comment"># d_activation(z)</span>
                    grad_w_hidden = np.dot(activations[l].T, delta) / X_batch.shape[<span class="hljs-number">0</span>]
                    grad_b_hidden = np.sum(delta, axis=<span class="hljs-number">0</span>) / X_batch.shape[<span class="hljs-number">0</span>]

                    <span class="hljs-comment"># apply Adam updates to weights</span>
                    self.m_weights[l] = self.beta1 * self.m_weights[l] + (<span class="hljs-number">1</span> - self.beta1) * grad_w_hidden
                    self.v_weights[l] = self.beta2 * self.v_weights[l] + (<span class="hljs-number">1</span> - self.beta2) * (grad_w_hidden ** <span class="hljs-number">2</span>)
                    m_w_hat = self.m_weights[l] / (<span class="hljs-number">1</span> - self.beta1**t)
                    v_w_hat = self.v_weights[l] / (<span class="hljs-number">1</span> - self.beta2**t)
                    self.weights[l] -= self.learning_rate * m_w_hat / (np.sqrt(v_w_hat) + self.epsilon)

                    <span class="hljs-comment"># apply Adam updates to bias</span>
                    self.m_biases[l] = self.beta1 * self.m_biases[l] + (<span class="hljs-number">1</span> - self.beta1) * grad_b_hidden
                    self.v_biases[l] = self.beta2 * self.v_biases[l] + (<span class="hljs-number">1</span> - self.beta2) * (grad_b_hidden ** <span class="hljs-number">2</span>)
                    m_b_hat = self.m_biases[l] / (<span class="hljs-number">1</span> - self.beta1**t)
                    v_b_hat = self.v_biases[l] / (<span class="hljs-number">1</span> - self.beta2**t)
                    self.biases[l] -= self.learning_rate * m_b_hat / (np.sqrt(v_b_hat) + self.epsilon)


            self.weights_history.append([w.copy() <span class="hljs-keyword">for</span> w <span class="hljs-keyword">in</span> self.weights])
            self.biases_history.append([b.copy() <span class="hljs-keyword">for</span> b <span class="hljs-keyword">in</span> self.biases])

            activations, _ = self._forward_pass(X)
            epoch_loss = self._compute_loss(y, activations[<span class="hljs-number">-1</span>])
            self.loss_history.append(epoch_loss)

            <span class="hljs-keyword">if</span> (epoch + <span class="hljs-number">1</span>) % <span class="hljs-number">100</span> == <span class="hljs-number">0</span>:
                print(<span class="hljs-string">f"Epoch <span class="hljs-subst">{epoch+<span class="hljs-number">1</span>}</span>/<span class="hljs-subst">{self.n_epochs}</span>, Loss: <span class="hljs-subst">{epoch_loss:<span class="hljs-number">.4</span>f}</span>"</span>)
        <span class="hljs-keyword">return</span> self


    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict_proba</span>(<span class="hljs-params">self, X</span>):</span>
        activations, _ = self._forward_pass(X)
        <span class="hljs-keyword">return</span> activations[<span class="hljs-number">-1</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict</span>(<span class="hljs-params">self, X, threshold=<span class="hljs-number">0.5</span></span>):</span>
        probabilities = self.predict_proba(X)
        <span class="hljs-keyword">return</span> (probabilities &gt;= threshold).astype(int).flatten()
</code></pre>
<h3 id="heading-training-prediction-1">Training / Prediction</h3>
<p>Train the model and make a prediction using training and validation datasets:</p>
<pre><code class="lang-python">mlp_adam = MLP_Adam(hidden_layer_sizes=(<span class="hljs-number">30</span>, <span class="hljs-number">10</span>), learning_rate=<span class="hljs-number">0.001</span>, n_epochs=<span class="hljs-number">500</span>, batch_size=<span class="hljs-number">32</span>)
mlp_adam.fit(X_train_processed, y_train)

y_pred_train = mlp_adam.predict(X_train_processed)
y_pred_val = mlp_adam.predict(X_val_processed)

acc_train = accuracy_score(y_train, y_pred_train)
acc_val = accuracy_score(y_val, y_pred_val)

print(<span class="hljs-string">f"\nMLP (Custom Adam) Accuracy (Train): <span class="hljs-subst">{acc_train:<span class="hljs-number">.3</span>f}</span>"</span>)
print(<span class="hljs-string">f"MLP (Custom Adam) Accuracy (Validation): <span class="hljs-subst">{acc_val:<span class="hljs-number">.3</span>f}</span>"</span>)
</code></pre>
<h3 id="heading-results-5">Results</h3>
<ul>
<li><p>Recall: <em>0.9870–0.6150 (from training to validation)</em></p>
</li>
<li><p>Precision: <em>0.9811–0.6474 (from training to validation)</em></p>
</li>
</ul>
<p>While the Adam optimizer outperformed SGD, the model exhibited significant overfitting, with both Recall and Precision falling by around 30 points between training and validation.</p>
<p><strong>Loss History</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748442341394/3183a9b1-5df0-4f74-9473-6b5b595dc9c0.png" alt="Loss by epoch, middle: weights history by epoch, right: bias history by epoch (source: Kuriko Iwai)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>We visualized the decision boundary using the first two principal components (PCA) as the x and y axes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748442311514/34f004c9-bf1d-41e5-a0af-08c62802b78c.png" alt="Decision Boundary of MLP with Adam Optimizer (source: Kuriko Iwai)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-leverage-sckitlearns-mcp-classifier-1">Leverage SckitLearn’s MCP Classifier</h3>
<p>We’ve switched the optimizer from SGD to Adam, keeping all other settings constant:</p>
<pre><code class="lang-python">model_sklearn_mlp_adam = MLPClassifier(
    hidden_layer_sizes=(<span class="hljs-number">30</span>, <span class="hljs-number">30</span>),
    activation=<span class="hljs-string">'relu'</span>,
    solver=<span class="hljs-string">'adam'</span>,             <span class="hljs-comment"># update the optimizer from SGD to Adam</span>
    learning_rate_init=<span class="hljs-number">0.001</span>,
    learning_rate=<span class="hljs-string">'constant'</span>,
    alpha=<span class="hljs-number">0.0001</span>,
    max_iter=<span class="hljs-number">3000</span>,
    batch_size=<span class="hljs-number">16</span>,
    random_state=<span class="hljs-number">42</span>,
    early_stopping=<span class="hljs-literal">True</span>,
    n_iter_no_change=<span class="hljs-number">50</span>,
    validation_fraction=<span class="hljs-number">0.1</span>,
    tol=<span class="hljs-number">1e-4</span>,
    verbose=<span class="hljs-literal">False</span>,
)

model_sklearn_mlp_adam.fit(X_train_processed, y_train)

y_pred_train_sklearn = model_sklearn_mlp_adam.predict(X_train_processed)
y_pred_val_sklearn = model_sklearn_mlp_adam.predict(X_val_processed)
</code></pre>
<h3 id="heading-results-6">Results</h3>
<ul>
<li><p><em>Recall: 0.8975–0.6400 (from training to validation)</em></p>
</li>
<li><p><em>Precision: 0.8864 —  0.6305 (from training to validation)</em></p>
</li>
</ul>
<p>Despite a performance improvement compared to the SGD optimizer, the significant drop in both Recall (from 0.8975 to 0.6400) and Precision (from 0.8864 to 0.6305) from training to validation data indicates that the model is still overfitting.</p>
<h3 id="heading-leverage-keras-sequential-classifier-1">Leverage Keras Sequential Classifier</h3>
<p>Similar to MLPClassifier, we’ve switched the optimizer from SGD to Adam with all the other conditions remaining the same:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> tensorflow <span class="hljs-keyword">as</span> tf
<span class="hljs-keyword">from</span> tensorflow <span class="hljs-keyword">import</span> keras
<span class="hljs-keyword">from</span> keras.models <span class="hljs-keyword">import</span> Sequential
<span class="hljs-keyword">from</span> keras.layers <span class="hljs-keyword">import</span> Dense, Dropout, Input
<span class="hljs-keyword">from</span> keras.optimizers <span class="hljs-keyword">import</span> Adam
<span class="hljs-keyword">from</span> keras.callbacks <span class="hljs-keyword">import</span> EarlyStopping
<span class="hljs-keyword">from</span> sklearn.utils <span class="hljs-keyword">import</span> class_weight


initial_bias = np.log([np.sum(y_train == <span class="hljs-number">1</span>) / np.sum(y_train == <span class="hljs-number">0</span>)])
model_keras_adam = Sequential([
    Input(shape=(X_train_processed.shape[<span class="hljs-number">1</span>],)), 
    Dense(<span class="hljs-number">30</span>, activation=<span class="hljs-string">'relu'</span>)),
    Dropout(<span class="hljs-number">0.1</span>),
    Dense(<span class="hljs-number">30</span>, activation=<span class="hljs-string">'relu'</span>),
    Dropout(<span class="hljs-number">0.1</span>),
    Dense(<span class="hljs-number">1</span>, activation=<span class="hljs-string">'sigmoid'</span>, 
          bias_initializer=tf.keras.initializers.Constant(initial_bias))
])


optimizer_keras = Adam(learning_rate=<span class="hljs-number">0.001</span>)
model_keras_adam.compile(
    optimizer=optimizer_keras, 
    loss=<span class="hljs-string">'binary_crossentropy'</span>, 
    metrics=[
        <span class="hljs-string">'accuracy'</span>,
        tf.keras.metrics.Precision(name=<span class="hljs-string">'precision'</span>),
        tf.keras.metrics.Recall(name=<span class="hljs-string">'recall'</span>),
        tf.keras.metrics.AUC(name=<span class="hljs-string">'auc'</span>) 
    ]
)

early_stopping_callback = EarlyStopping(
    monitor=<span class="hljs-string">'val_recall'</span>,
    mode=<span class="hljs-string">'max'</span>,
    patience=<span class="hljs-number">50</span>,
    min_delta=<span class="hljs-number">1e-4</span>,
    verbose=<span class="hljs-number">0</span>
)

class_weights = class_weight.compute_class_weight(
    class_weight=<span class="hljs-string">'balanced'</span>,
    classes=np.unique(y_train),
    y=y_train
)
class_weights_dict = dict(zip(np.unique(y_train), class_weights))

model_keras_adam.fit(
    X_train_processed, y_train,
    epochs=<span class="hljs-number">1000</span>,
    batch_size=<span class="hljs-number">32</span>,
    validation_data=(X_val_processed, y_val),
    callbacks=[early_stopping_callback],
    class_weight=class_weights_dict,
    verbose=<span class="hljs-number">0</span>
)


loss_train, accuracy_train, precision_train, recall_train, auc_train = model_keras_adam.evaluate(X_train_processed, y_train, verbose=<span class="hljs-number">0</span>)
print(<span class="hljs-string">f"\n--- Keras Model Accuracy (Train) ---"</span>)
print(<span class="hljs-string">f"Loss: <span class="hljs-subst">{loss_train:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Accuracy: <span class="hljs-subst">{accuracy_train:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Precision: <span class="hljs-subst">{precision_train:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Recall: <span class="hljs-subst">{recall_train:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"AUC: <span class="hljs-subst">{auc_train:<span class="hljs-number">.4</span>f}</span>"</span>)


loss_val, accuracy_val, precision_val, recall_val, auc_val = model_keras_adam.evaluate(X_val_processed, y_val, verbose=<span class="hljs-number">0</span>)
print(<span class="hljs-string">f"\n--- Keras Model Accuracy (Validation) ---"</span>)
print(<span class="hljs-string">f"Loss: <span class="hljs-subst">{loss_val:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Accuracy: <span class="hljs-subst">{accuracy_val:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Precision: <span class="hljs-subst">{precision_val:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"Recall: <span class="hljs-subst">{recall_val:<span class="hljs-number">.4</span>f}</span>"</span>)
print(<span class="hljs-string">f"AUC: <span class="hljs-subst">{auc_val:<span class="hljs-number">.4</span>f}</span>"</span>)


model_keras_adam.summary()
</code></pre>
<h3 id="heading-results-7">Results</h3>
<ul>
<li><p><em>Recall: 0.7995–0.7500 (from training to validation)</em></p>
</li>
<li><p><em>Precision: 0.8409–0.8065 (from training to validation)</em></p>
</li>
</ul>
<p>The model exhibits good performance, with Recall slightly decreasing from 0.7995 (training) to 0.7500 (validation), and Precision similarly dropping from 0.8409 (training) to 0.8065 (validation).</p>
<p>This indicates good generalization, with only minor performance degradation on unseen data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748441767800/fe43f181-4323-461f-b56a-125fc78e9c84.png" alt="Image: Keras Sequential Model with Adam Optimizer (Source: Kuriko Iwai)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-final-results-generalization">Final Results: Generalization</h2>
<p>Finally, we’ll evaluate the model’s ultimate performance on the test dataset, which has remained completely separate from all prior training and validation processes.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Custom classifiers</span>
y_pred_test_custom_sgd = mlp_sgd.fit(X_train_processed, y_train).predict(X_test_processed)
y_pred_test_custom_adam = mlp_adam.fit(X_train_processed, y_train).predict(X_test_processed)

<span class="hljs-comment"># MLPClassifer</span>
y_pred_test_sk_sgd = model_sklearn_mlp_sgd.fit(X_train_processed, y_train).predict(X_test_processed)
y_pred_test_sk_adam = model_sklearn_mlp_adam.fit(X_train_processed, y_train).predict(X_test_processed)

<span class="hljs-comment"># Keras Sequential</span>
_, accuracy_val_sgd, precision_val_sgd, recall_val_sgd, auc_val_sgd = model_keras_sgd.evaluate(X_test_processed, y_test, verbose=<span class="hljs-number">0</span>)
_, accuracy_val_adam, precision_val_adam, recall_val_adam, auc_val_adam = model_keras_adam.evaluate(X_test_processed, y_test, verbose=<span class="hljs-number">0</span>)
</code></pre>
<p>Overall, the Keras Sequential model, optimized with SGD, achieved the best performance with an <strong>AUPRC (Area Under Precision-Recall Curve) of 0.72.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748874699534/f0f008c4-9067-4e2a-b070-4bb5cbae8f23.png" alt="Precision-Recall Curves for Six Classifier Models (Comparing Custom, MLP, and Keras Sequential Classifiers with SGD and Adam Optimizers (Source: Kuriko Iwai)" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this exploration, we experimented with custom classifiers, Scikit-learn models, and Keras deep learning architectures.</p>
<p>Our findings underscore that effective machine learning hinges on three critical factors:</p>
<ol>
<li><p><strong>robust data preprocessing</strong> (tailored to objectives and data distribution),</p>
</li>
<li><p><strong>judicious model selection</strong>, and</p>
</li>
<li><p><strong>strategic framework or library choices</strong>.</p>
</li>
</ol>
<h3 id="heading-choosing-the-right-framework"><strong>Choosing the right framework</strong></h3>
<p>Generally speaking, choose <code>MLPClassifier</code> when:</p>
<ul>
<li><p>You’re primarily working with <strong>tabular data,</strong></p>
</li>
<li><p>You want to prioritize <strong>simplicity, quick iteration, and seamless integration,</strong></p>
</li>
<li><p>You have simple, shallow architectures, and</p>
</li>
<li><p>You have a moderate dataset size (manageable on a CPU).</p>
</li>
</ul>
<p>Choose Keras <code>Sequential</code> when:</p>
<ul>
<li><p>You’re dealing with <strong>image, text, audio, or other sequential data,</strong></p>
</li>
<li><p>You’re building <strong>deep learning models</strong> such as CNNs, RNNs, LSTMs,</p>
</li>
<li><p>You need <strong>fine-grained control</strong> over the model architecture, training process, or custom components,</p>
</li>
<li><p>You need to leverage <strong>GPU acceleration</strong>,</p>
</li>
<li><p>You’re planning for <strong>production deployment</strong>, and</p>
</li>
<li><p>You want to experiment with more advanced deep learning techniques.</p>
</li>
</ul>
<h3 id="heading-limitation-of-mlps">Limitation of MLPs</h3>
<p>While Multilayer Perceptrons (MLPs) proved valuable, their susceptibility to computational complexity and overfitting emerged as key challenges.</p>
<p>Looking ahead, we’ll delve into how Recurrent Neural Networks (RNNs) and Convolutional Neural Networks (CNNs) offer powerful solutions to these inherent MLP limitations.</p>
<p>You can find more info about me on my <a target="_blank" href="https://kuriko.vercel.app/">Portfolio</a> / <a target="_blank" href="https://www.linkedin.com/in/k-i-i">LinkedIn</a> / <a target="_blank" href="https://github.com/versionhq/multi-agent-system">Github</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ LeetCode Meditations: A Visual Handbook of Data Structures and Algorithms Concepts ]]>
                </title>
                <description>
                    <![CDATA[ It may seem like an oxymoron when the words "LeetCode" and "meditation" are used together – after all, one thing that almost everyone can agree is that LeetCode is challenging. It's called grinding LeetCode for a reason. It doesn't have anything to d... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/leetcode-dsa-concepts-handbook/</link>
                <guid isPermaLink="false">6838baed0c839b0170a2e731</guid>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                    <category>
                        <![CDATA[ leetcode ]]>
                    </category>
                
                    <category>
                        <![CDATA[ DSA ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eda Eren ]]>
                </dc:creator>
                <pubDate>Thu, 29 May 2025 19:52:13 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748548297673/2ea8ee5a-e873-4401-b024-86412bf00f8a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>It may seem like an oxymoron when the words "LeetCode" and "meditation" are used together – after all, one thing that almost everyone can agree is that <a target="_blank" href="https://leetcode.com/">LeetCode</a> is challenging. It's called <em>grinding</em> LeetCode for a reason.</p>
<p>It doesn't have anything to do with the platform, of course, but rather what it represents: tackling problems for hours on end, usually to find a solution that is even harder to understand.</p>
<p>But what is more challenging is finding a roadmap to solve those problems with very little knowledge of data structures and algorithms. This handbook is more or less based on the <a target="_blank" href="https://neetcode.io/practice?tab=blind75">Blind 75 list</a> that's included in <a target="_blank" href="http://neetcode.io">neetcode.io</a>'s practice problems. This is an amazing resource that offers an organized study roadmap for solving LeetCode problems.</p>
<p>In fact, why not take a more structured and <em>calmer</em> approach? We can treat learning about the topics on the list like taking a brief walk in nature – a sort of meditation, if you will.</p>
<p>That said, this handbook is not about specific problems. Rather it’s about understanding the concepts behind them in a casual manner. It is also language agnostic – sometimes you’ll see TypeScript, sometimes Python, and sometimes JavaScript.</p>
<p>This handbook also requires you to be patient, to relax, to take a step back and pay attention. The mid-quality GIFs used in the handbook (maybe ironically!) intend to encourage this. They are not videos, so you can wait for it to come to a moment that you didn't understand or missed instead of hastily rewinding it back or rushing to a certain point in the future.</p>
<p>Solving hundreds of LeetCode problems may be the gate to go through to get an interview at big tech companies…but learning the topics that the problems are about is not under anyone's monopoly.</p>
<p>With that said, let's start the first chapter.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-one-arrays-amp-hashing">Chapter One: Arrays &amp; Hashing</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-dynamic-arrays">Dynamic Arrays</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hash-tables">Hash Tables</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prefix-sums">Prefix Sums</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-two-two-pointers">Chapter Two: Two Pointers</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-palindrome-example">Palindrome example</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-squares-of-a-sorted-array-example">Squares of a sorted array example</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-three-sliding-window">Chapter Three: Sliding Window</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-fixed-window-size">Fixed window size</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dynamic-window-size">Dynamic window size</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-four-stack">Chapter Four: Stack</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-five-binary-search">Chapter Five: Binary Search</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-six-linked-lists">Chapter Six: Linked Lists</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-singly-linked-lists">Singly linked lists</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-doubly-linked-lists">Doubly linked lists</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-circular-linked-lists">Circular linked lists</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-interlude-fast-amp-slow-pointers">Interlude: Fast &amp; Slow Pointers</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-finding-the-middle-node-of-a-linked-list">Finding the middle node of a linked list</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-seven-trees">Chapter Seven: Trees</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-binary-trees-binary-search-trees-bsts">Binary trees, binary search trees (BSTs)</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-inserting-into-a-binary-search-tree">Inserting into a binary search tree</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-recursive-solution">Recursive solution</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-iterative-solution">Iterative solution</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-deleting-from-a-binary-search-tree">Deleting from a binary search tree</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-traversals">Traversals</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-depth-first-search-dfs">Depth-First Search (DFS)</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-preorder-traversal">Preorder traversal</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-inorder-traversal">Inorder traversal</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-postorder-traversal">Postorder traversal</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-breadth-first-search-bfs">Breadth-First Search (BFS)</a></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-eight-heap-priority-queue">Chapter Eight: Heap / Priority Queue</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-heap-properties">Heap properties</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-heaps-with-arrays">Heaps with arrays</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-insertingremoving-elements">Inserting/removing elements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-heapsort">Heapsort</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-nine-backtracking">Chapter Nine: Backtracking</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-subsets">Subsets</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-ten-tries">Chapter Ten: Tries</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-eleven-graphs">Chapter Eleven: Graphs</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-representing-graphs">Representing graphs</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-edge-list">Edge List</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adjacency-matrix">Adjacency Matrix</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adjacency-list">Adjacency List</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-traversals-1">Traversals</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-breadth-first-search">Breadth-First Search</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-depth-first-search">Depth-First Search</a></p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-twelve-dynamic-programming">Chapter Twelve: Dynamic Programming</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-thirteen-intervals">Chapter Thirteen: Intervals</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chapter-fourteen-bit-manipulation">Chapter Fourteen: Bit Manipulation</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-bitwise-operators">Bitwise operators</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-and-amp">AND (<code>&amp;</code>)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-or">OR (<code>|</code>)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-xor">XOR (<code>^</code>)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-not">NOT (<code>~</code>)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-left-shift-zero-fill-ltlt">Left shift (zero fill) (<code>&lt;&lt;</code>)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-right-shift-sign-preserving-gtgt">Right shift (sign preserving) (<code>&gt;&gt;</code>)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-right-shift-unsigned-gtgtgt">Right shift (unsigned) (<code>&gt;&gt;&gt;</code>)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-getting-a-bit">Getting a bit</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-a-bit">Setting a bit</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resources-amp-credits">Resources &amp; Credits</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving in, some familiarity with TypeScript/JavaScript and Python may be helpful, as these are the languages I use for the examples. Also, a basic understanding of Big O notation is useful as we go over time and space complexities.</p>
<p>Even though we don't go through the mathematics behind the concepts, some basic mathematical knowledge can also help. That said, it's definitely not necessary to enjoy or learn something useful from this handbook.</p>
<h2 id="heading-chapter-one-arrays-amp-hashing">Chapter One: Arrays &amp; Hashing</h2>
<p>Let's very briefly get to know our topics for this chapter: dynamic arrays, hash tables, and prefix sums.</p>
<h3 id="heading-dynamic-arrays">Dynamic Arrays</h3>
<p>Dynamic arrays are, well, dynamic. They're flexible and can change their size during execution.</p>
<p>Python's <code>list</code> type is a dynamic array. We can create an <code>items</code> list, for example:</p>
<pre><code class="lang-python">items = [<span class="hljs-number">3</span>, <span class="hljs-number">5</span>]
</code></pre>
<p>The <strong>length</strong> of <code>items</code> is 2, as you can see, but its <strong>capacity</strong> is greater than or equal to its length. In fact, capacity refers to the total size, whereas length is the actual size.</p>
<p>Since dynamic arrays are still arrays, they need a contiguous block of memory.</p>
<p>We can easily add an item to <code>items</code>:</p>
<pre><code class="lang-python">items.append(<span class="hljs-number">7</span>)
</code></pre>
<p>And add some more:</p>
<pre><code class="lang-python">items.append(<span class="hljs-number">9</span>)
items.append(<span class="hljs-number">11</span>)
items.append(<span class="hljs-number">13</span>)
</code></pre>
<p>All the while, the length and capacity of <code>items</code> keep growing dynamically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747912308935/960ff442-e095-4781-8ab5-9b84e0ecb804.gif" alt="Animated visualization of four boxes for an &quot;items&quot; array that holds the values 3 and 5 on initialization, appending 7 to the array adds four more boxes to it." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h4 id="heading-time-and-space-complexity">Time and space complexity</h4>
<p>Accessing an element is \(O(1)\) as we have <a target="_blank" href="https://en.wikipedia.org/wiki/Random_access">random access</a>.</p>
<p>Inserting a new element or deleting an element is \(O(n)\) (think about having to shift all the elements before inserting or after deleting an item). But, in order to not be too pessimistic, we can look at <a target="_blank" href="https://en.wikipedia.org/wiki/Amortized_analysis">amortized analysis</a> – in that case, inserting/deleting at the end of the array becomes \(O(1)\).</p>
<p>Space complexity is \(O(n)\), as the need for space will grow proportionately as the input increases.</p>
<p>If you need more info about time and space complexity, you can <a target="_blank" href="https://www.freecodecamp.org/news/big-o-notation-why-it-matters-and-why-it-doesnt-1674cfa8a23c/">refer to this guide</a>.</p>
<h3 id="heading-hash-tables">Hash Tables</h3>
<p>A hash table maps keys to values, implementing an <em>associative array</em>.</p>
<p>Python's <code>dict</code> is one example:</p>
<pre><code class="lang-python">number_of_petals = {
    <span class="hljs-string">'Euphorbia'</span>: <span class="hljs-number">2</span>, 
    <span class="hljs-string">'Trillium'</span>: <span class="hljs-number">3</span>, 
    <span class="hljs-string">'Columbine'</span>: <span class="hljs-number">5</span>,
}
</code></pre>
<p>Also JavaScript's "object"s:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> numberOfMoons = {
  <span class="hljs-string">'Earth'</span>: <span class="hljs-number">1</span>,
  <span class="hljs-string">'Mars'</span>: <span class="hljs-number">2</span>,
  <span class="hljs-string">'Jupiter'</span>: <span class="hljs-number">95</span>,
  <span class="hljs-string">'Saturn'</span>: <span class="hljs-number">146</span>,
  <span class="hljs-string">'Uranus'</span>: <span class="hljs-number">27</span>,
  <span class="hljs-string">'Neptune'</span>: <span class="hljs-number">14</span>,
};
</code></pre>
<p>There are two important ingredients for a hash table:</p>
<ul>
<li><p>an array of "buckets" to store the data</p>
</li>
<li><p>a hash function to map the data to a specific index in the array</p>
</li>
</ul>
<p>Hashes are usually large integers, so to find an index, we can take the result of the hash modulo the array's length.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747913143164/2a8371ba-133c-4d5a-a270-b44f935fc91b.gif" alt="Animated visualization of an array with 5 buckets, the hash function finding a bucket for each value in number_of_petals dict." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><strong>Note:</strong> The <strong>hash function</strong> that's mapping the elements to buckets is <strong>not</strong> the <code>hash()</code> used in the visual (it's just a <a target="_blank" href="https://docs.python.org/3/library/functions.html#hash">Python function</a> to calculate the hash value of an object). The hash function in this case is the modulo ( <code>%</code> ) operation.</p>
<p>Here, with the hash value of each item's key, we calculate the remainder when it's divided by the length of the array to find which "bucket" it should go to.</p>
<p>The ratio of the number of elements to the number of buckets is called the <strong>load factor</strong>, and the higher it gets, the more <strong>collisions</strong> (when elements have to be inserted at the same place in the array) occur.</p>
<p>There are some collusion resolution tactics like <strong>linear probing</strong> (probing through the array until finding an empty bucket) and <strong>chaining</strong> (chaining multiple elements as linked lists), but we'll not go into those for now.</p>
<h4 id="heading-time-and-space-complexity-1">Time and space complexity</h4>
<p>The average case for searching, inserting, and deleting operations are \(O(1)\) as we use keys to look up the values.</p>
<p>Space complexity is \(O(n)\) as it grows linearly with the amount of elements.</p>
<h3 id="heading-prefix-sums">Prefix Sums</h3>
<p>A prefix sum is the sequence of numbers we get after adding the sums of running totals of another sequence. It's also called the <strong>cumulative sum</strong>.</p>
<p>The first element of the resulting array is the first element of the input array. That's fine. We start at the second item, and add the previous numbers each time as we go. That is:</p>
<p>$$result[i] = \begin{cases} nums[0] &amp; \text{if } i \text{ is zero} \\ result[i - 1] + nums[i] &amp; \text{if } i \geq 1 \end{cases}$$</p><p>In code, we can implement that easily:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">runningSum</span>(<span class="hljs-params">nums</span>):</span>
    result = [nums[<span class="hljs-number">0</span>]]

    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>, len(nums)):
        result.append(result[i - <span class="hljs-number">1</span>] + nums[i])

    <span class="hljs-keyword">return</span> result
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747913501237/e9c95eef-6310-457c-b94e-da5a61fc890a.gif" alt="Animated visualization of runningSum of the array [1, 2, 3, 4, 5]." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h4 id="heading-time-and-space-complexity-2">Time and space complexity</h4>
<p>Time complexity for a prefix sum is \(O(n)\) because we're iterating over each of the elements in the array.</p>
<p>The space complexity is also \(O(n)\) because the need for external space grows as the length of the original array grows.</p>
<h2 id="heading-chapter-two-two-pointers">Chapter Two: Two Pointers</h2>
<p>One of the techniques of iterating through an array is the <strong>two pointers technique</strong>, and it is as simple as it sounds: we just keep two pointers, one starting from the left, and the other from the right, gradually getting closer to each other.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747913831967/b8e251a9-2e9e-41be-84ed-46f2559b1515.gif" alt="Animated visualization of two pointers technique" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-palindrome-example">Palindrome example</h3>
<p>A very basic example can be the one where we check if a string is a palindrome or not. A palindrome is a string that reads the same forwards and backwards.</p>
<p>In an imaginary world where all the inputs always consist of lowercase English letters, we can do it like this:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// s consists of lowercase English letters</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isPalindrome</span>(<span class="hljs-params">s: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">let</span> left = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">let</span> right = s.length - <span class="hljs-number">1</span>;

  <span class="hljs-keyword">while</span> (left &lt;= right) {
    <span class="hljs-keyword">if</span> (s[left++] !== s[right--]) {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>We initialize two pointers: <code>left</code> and <code>right</code>. <code>left</code> points to the start of the array, while the <code>right</code> points to the last element. As we loop while <code>left</code> is less than <code>right</code>, we check if they are equal. If not, we return <code>false</code> immediately. Otherwise, our <code>left</code> pointer is increased – that is, it's moved to the <em>right</em> one step, and our <code>right</code> pointer is decreased, meaning that it's moved to the <em>left</em> one step. When they eventually overlap, the loop terminates, and we return <code>true</code>.</p>
<p>Let's say our string is <code>'racecar'</code>, which is a palindrome. It will go like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747913932303/e60727a0-4e45-4452-8648-d35d1ae680c9.gif" alt="Animated visualization of isPalindrome, with the example 'racecar' resulting in true." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-squares-of-a-sorted-array-example">Squares of a sorted array example</h3>
<p>Another example where we can use the two pointers technique is the problem <a target="_blank" href="https://leetcode.com/problems/squares-of-a-sorted-array">Squares of a Sorted Array</a>.</p>
<p>The description says:</p>
<blockquote>
<p>Given an integer array <code>nums</code> sorted in <strong>non-decreasing</strong> order, return <em>an array of</em> <strong><em>the squares of each number</em></strong> <em>sorted in non-decreasing order</em>.</p>
</blockquote>
<p>For example, if the input is <code>[-4, -1, 0, 3, 10]</code>, the output should be <code>[0, 1, 9, 16, 100]</code>.</p>
<p>Now obviously, we can just square each one, and then sort the array with a built-in sort method, and be done with it. But a sorting operation is never better than \(O(n \ log \ n)\) runtime, so we can do it using two pointers in just \(O(n)\) time:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sortedSquares</span>(<span class="hljs-params">nums: <span class="hljs-built_in">number</span>[]</span>): <span class="hljs-title">number</span>[] </span>{
  <span class="hljs-keyword">let</span> left = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">let</span> right = nums.length - <span class="hljs-number">1</span>;
  <span class="hljs-keyword">let</span> result = [];

  <span class="hljs-keyword">while</span> (left &lt;= right) {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Math</span>.abs(nums[left]) &gt; <span class="hljs-built_in">Math</span>.abs(nums[right])) {
      result.push(nums[left++] ** <span class="hljs-number">2</span>);
    } <span class="hljs-keyword">else</span> {
      result.push(nums[right--] ** <span class="hljs-number">2</span>);
    }
  }

  <span class="hljs-keyword">return</span> result.reverse();
}
</code></pre>
<p>We compare the absolute value of the items that <code>left</code> and <code>right</code> are pointing to, and push the square of the greater one to our <code>result</code> array. And we return the reversed version of it.</p>
<p><strong>Note:</strong> The reason we return the reversed result is that the array is initially already sorted, and we get the largest absolute value first. The reason that works is related to how <em>two pointers</em> work: as we start from both ends, we initially start with the smallest and largest values in the array.</p>
<p>Because we only make one pass through the array while comparing, and then later reversing, it ends up being \(O(n)\), a better runtime than \(O(n \ log \ n)\).</p>
<h2 id="heading-chapter-three-sliding-window">Chapter Three: Sliding Window</h2>
<p>Now that we're familiar with the Two Pointers technique, we can add another one to our toolbox: the Sliding Window. It's usually used for operations done on the subsets of a given data. It also comes in two flavors: <strong>fixed window size</strong> and <strong>dynamic window size</strong>.</p>
<h3 id="heading-fixed-window-size">Fixed window size</h3>
<p>If we have a size constraint in a given problem – say, we need to check a \(k\)-sized subarray – sliding window is an appropriate technique to use.</p>
<p>For example, getting the maximum subarray (of size \(k\)) sum of a given array can be done like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747914357907/ecd51e70-e649-4856-a563-47621b950526.gif" alt="Animated visualization of fixed window size sliding window technique, array [1, 5, 4, 2, 9] with k = 3, having the maxSum of 15." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Note that the window size is \(k\), and it doesn't change throughout the operation – hence, <strong>fixed size</strong>.</p>
<p>A very cool thing to notice here is that with each <strong>slide</strong>, what happens to our sum is that we <em>add</em> the right element, and <em>decrease</em> the left element.</p>
<p>Let's look at an example for getting the maximum sum of subarray with given size <code>k</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">maxSubarray</span>(<span class="hljs-params">numbers: <span class="hljs-built_in">number</span>[], k: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">if</span> (numbers.length &lt; k) {
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
  }

  <span class="hljs-keyword">let</span> currentSum = <span class="hljs-number">0</span>;

  <span class="hljs-comment">// Initial sum of the first window </span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; k; i++) {
    currentSum += numbers[i];
  }

  <span class="hljs-keyword">let</span> maxSum = currentSum;

  <span class="hljs-keyword">let</span> left = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">let</span> right = k;

  <span class="hljs-keyword">while</span> (right &lt; numbers.length) {
    currentSum = currentSum - numbers[left++] + numbers[right++];
    maxSum = <span class="hljs-built_in">Math</span>.max(maxSum, currentSum);
  }

  <span class="hljs-keyword">return</span> maxSum;
}
</code></pre>
<p><strong>Note:</strong> Updating the pointers can be done outside the brackets as well, like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">while</span> (right &lt; numbers.length) {
  currentSum = currentSum - numbers[left] + numbers[right];
  maxSum = <span class="hljs-built_in">Math</span>.max(maxSum, currentSum);
  left++;
  right++;
}
</code></pre>
<p>Since the postfix operator returns the value first, they can be used inside the brackets to be slightly more concise.</p>
<p>Here, we first get the initial sum of our window using the <code>for</code> loop, and set it as the maximum sum.</p>
<p>Then we initialize two pointers: <code>left</code> that points to the left end of the window, and <code>right</code> that points to the right end of the window. As we loop, we update our <code>currentSum</code>, decreasing the <code>left</code> value, and adding the <code>right</code> value. When our current sum is more than the maximum sum, <code>maxSum</code> variable is updated as well.</p>
<h3 id="heading-dynamic-window-size">Dynamic window size</h3>
<p>As opposed to the fixed window size version, the size of the window changes dynamically this time.</p>
<p>For example, let's take a brief look at the problem <a target="_blank" href="https://leetcode.com/problems/best-time-to-buy-and-sell-stock">Best Time to Buy and Sell Stock</a>. We need to choose a day to buy a stock, and sell it in the <em>future</em>. The numbers in the array are prices, and we need to buy the stock at as low a price as we can, and sell it as high as we can.</p>
<p>We can initialize <code>left</code> and <code>right</code> pointers again, but this time, we'll update them depending on a condition. When the left item is less than the one on the right, that means it's good – we can buy and sell at those prices, so we get their difference and update our <code>maxDiff</code> variable that holds the maximum difference between the two.</p>
<p>If, however, the left one is greater than the right one, we update our <code>left</code> pointer to be where the <code>right</code> is at. In both cases, we'll continue updating <code>right</code> until we reach the end of the array.</p>
<p>With the blue arrow indicating the left pointer, and the red the right one, the process looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747914550588/222996a0-d2a6-414e-86cf-fe60deb908d8.gif" alt="Animated visualization of dynamic window size sliding window technique, array [7, 1, 5, 3, 6] having the maxDiff of 5." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The solution looks like this:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">maxProfit</span>(<span class="hljs-params">prices: <span class="hljs-built_in">number</span>[]</span>): <span class="hljs-title">number</span> </span>{
  <span class="hljs-keyword">let</span> left = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">let</span> right = left + <span class="hljs-number">1</span>;
  <span class="hljs-keyword">let</span> maxDiff = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">while</span> (right &lt; prices.length) {
    <span class="hljs-keyword">if</span> (prices[left] &lt; prices[right]) {
      <span class="hljs-keyword">let</span> diff = prices[right] - prices[left];
      maxDiff = <span class="hljs-built_in">Math</span>.max(maxDiff, diff);
    } <span class="hljs-keyword">else</span> {
      left = right;
    }

    right++;
  }

  <span class="hljs-keyword">return</span> maxDiff;
}
</code></pre>
<p><strong>Note:</strong> This one is also called <strong>fast/catch-up</strong> version of dynamic sliding window, because the <code>left</code> pointer jumps to catch up with the <code>right</code> pointer in the <code>else</code> block.</p>
<h4 id="heading-time-and-space-complexity-3">Time and space complexity</h4>
<p>Both examples have the same time and space complexity: The time complexity is \(O(n)\) because in the worst case we iterate through all the elements in the array. The space complexity is \(O(1)\) as we don't need additional space.</p>
<h2 id="heading-chapter-four-stack">Chapter Four: Stack</h2>
<p>A stack data type is perhaps one of the most well-known ones. A stack of books might be a good example to visualize, but insertion and deletion can only happen from the one end. A stack operates through the last-in first-out (LIFO) principle: the last item to go in is the first to go out.</p>
<p>Usually we'll have methods for <em>pushing</em> an element onto the stack, and <em>popping</em> an element from the stack.</p>
<p>For example, let's say we're looking for valid parentheses in a given string, and the operation we'll do goes like this.</p>
<p>As we iterate over the characters in the string, we <em>push</em> the character onto the stack. If we pushed a closing parenthesis (one of <code>)</code>, <code>}</code>, or <code>]</code>), then, if the previous pushed element is its opening pair, we'll <em>pop</em> that pair from the stack.</p>
<p>If, at the end, the stack is empty, the string consists of valid parentheses.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747914829489/e86baf72-22f6-41fe-9de9-f4da136a8777.gif" alt="Animated visualization of pushing to and popping off from a stack of parentheses." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>A stack can be implemented as an array or a linked list. But using linked lists is more common because with arrays, we have a potential <em>stack overflow</em> when we predefine a maximum stack size. On the other hand, linked lists are not static when it comes to memory, so they are a good candidate to implement stacks.</p>
<p>Linked lists are also efficient because we are using one end of the stack for insertion and deletion, and doing these are constant time operations.</p>
<p>Let's look at one easy stack implementation in Python.</p>
<p>Now, we can use a <code>list</code>, but <a target="_blank" href="https://docs.python.org/3/faq/design.html#how-are-lists-implemented-in-cpython">a list in Python is implemented as a dynamic array underneath</a>, so at one point, pushing an item can be an \(O(n)\) operation if the list needs to be copied into another memory location. For that reason, we'll use a <a target="_blank" href="https://docs.python.org/3/library/collections.html#collections.deque"><code>deque</code></a>, which is implemented as a doubly-linked list, so that we know push and pop operations will be \(O(1)\).</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> collections <span class="hljs-keyword">import</span> deque

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Stack</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self._stack = deque()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">push</span>(<span class="hljs-params">self, item</span>):</span>
        self._stack.append(item)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pop</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self._stack.pop()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">peek</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self._stack[<span class="hljs-number">-1</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">is_empty</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">not</span> bool(len(self._stack))

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">size</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> len(self._stack)
</code></pre>
<p>In addition to <code>push</code> and <code>pop</code>, we'll also usually have functions like <code>peek</code> to get the topmost item in the stack, <code>is_empty</code> to check if the stack is empty, and <code>size</code> to get the size of the stack.</p>
<p>We can also do it using JavaScript. Now, we can do it using an array, but we want to use a linked list instead. Since we don't have a robust built-in library like Python this time, we'll implement a very simple version of it ourselves. Even though we haven't seen linked lists so far, the basic idea is that we have nodes, each of which has a <code>data</code> value, and a <code>next</code> pointer pointing to the next node.</p>
<p>Let's create a simple node first:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Node</span> </span>{
  <span class="hljs-keyword">constructor</span>(data) {
    <span class="hljs-built_in">this</span>.data = data;
    <span class="hljs-built_in">this</span>.next = <span class="hljs-literal">null</span>;
  }
}
</code></pre>
<p>We can write our stack now:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Stack</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.top = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.length = <span class="hljs-number">0</span>;
  }

  push(item) {
    <span class="hljs-keyword">const</span> node = <span class="hljs-keyword">new</span> Node(item);
    node.next = <span class="hljs-built_in">this</span>.top;
    <span class="hljs-built_in">this</span>.top = node;
    <span class="hljs-built_in">this</span>.length++;
  }

  pop() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.isEmpty()) { <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; }

    <span class="hljs-keyword">const</span> data = <span class="hljs-built_in">this</span>.top.data;
    <span class="hljs-built_in">this</span>.top = <span class="hljs-built_in">this</span>.top.next;
    <span class="hljs-built_in">this</span>.length--;

    <span class="hljs-keyword">return</span> data;
  }

  peek() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.isEmpty()) { <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; }

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.top.data;
  }

  isEmpty() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.size() === <span class="hljs-number">0</span>;
  }

  size() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.length;
  }
}
</code></pre>
<p>Now, let’s use it:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> myStack = <span class="hljs-keyword">new</span> Stack();

myStack.push(<span class="hljs-number">5</span>);
myStack.push(<span class="hljs-number">17</span>);
myStack.push(<span class="hljs-number">55345</span>);
myStack.push(<span class="hljs-number">0</span>);
myStack.push(<span class="hljs-number">103</span>)

<span class="hljs-built_in">console</span>.log(myStack.size()) <span class="hljs-comment">// 5</span>
<span class="hljs-built_in">console</span>.log(myStack.peek()) <span class="hljs-comment">// 103</span>

myStack.pop()

<span class="hljs-built_in">console</span>.log(myStack.size()) <span class="hljs-comment">// 4</span>
<span class="hljs-built_in">console</span>.log(myStack.peek()) <span class="hljs-comment">// 0</span>
</code></pre>
<h4 id="heading-time-and-space-complexity-4">Time and space complexity</h4>
<p>Each method we defined for our stack has \(O(1)\) time complexity, and it would be the same if we were to use an array as well. However, as mentioned above, arrays have limitations in that having to allocate a predefined stack size can lead to a stack overflow. And if we were to use a dynamic array, the whole array might need to be copied to go into another memory location after a certain size is reached, leading to \(O(n)\) time. So, linked lists are ideal to implement a stack data type.</p>
<p>If the space complexity is linear – \(O(n)\)– the stack will grow linearly with the number of items in it.</p>
<h2 id="heading-chapter-five-binary-search">Chapter Five: Binary Search</h2>
<p>Binary search is one of the most well-known algorithms. It's also a <a target="_blank" href="https://brilliant.org/wiki/divide-and-conquer/">divide-and-conquer algorithm</a>, where we break the problem into smaller components.</p>
<p>The crux of binary search is to find a target element in a given sorted array. We have two pointers: <code>high</code> to point to the largest element, and <code>low</code> to point to the smallest element. We first initialize them for the whole array itself, with <code>high</code> being the last index and <code>low</code> being the first index.</p>
<p>Then, we calculate the midpoint. If the target is greater than the midpoint, then we adjust our <code>low</code> pointer to point to the <code>mid + 1</code>, otherwise if the target is less than the midpoint, we adjust <code>high</code> to be <code>mid - 1</code>. With each iteration, we eliminate half the array until the midpoint equals the target or the <code>low</code> pointer passes <code>high</code>.</p>
<p>If we find the index of the target, we can return it as soon as we find it. Otherwise, we can just return <code>-1</code> to indicate that the target doesn't exist in the array.</p>
<p>For example, if we have a <code>nums</code> array <code>[-1, 0, 3, 5, 9, 12]</code> and our <code>target</code> is <code>9</code>, the operation looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747915126875/c27134cb-ae7c-4f09-88d8-13fc64900319.gif" alt="Animated visualization of binary search, array [-1, 0, 3, 5, 9. 12] with target = 9, the result being the index 4." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>We can write it in TypeScript like this:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">search</span>(<span class="hljs-params">nums: <span class="hljs-built_in">number</span>[], target: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">number</span> </span>{
  <span class="hljs-keyword">let</span> high = nums.length - <span class="hljs-number">1</span>;
  <span class="hljs-keyword">let</span> low = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">while</span> (high &gt;= low) {
    <span class="hljs-keyword">let</span> mid = <span class="hljs-built_in">Math</span>.floor((high + low) / <span class="hljs-number">2</span>);

    <span class="hljs-keyword">if</span> (target &gt; nums[mid]) {
      low = mid + <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (target &lt; nums[mid]) {
      high = mid - <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> mid;
    }
  }

  <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
}
</code></pre>
<h4 id="heading-time-and-space-complexity-5">Time and space complexity</h4>
<p>The time complexity of a binary search algorithm is \(O(log \ n)\) in the worst case. (For example, if the target is not in the array, we'll be halving the array until there is one element left.) The space complexity is \(O(1)\) as we don't need extra space.</p>
<h2 id="heading-chapter-six-linked-lists">Chapter Six: Linked Lists</h2>
<p>A linked list is a linear data structure that you're likely to be familiar with. It is also a data structure that can grow and shrink dynamically – so unlike arrays, there's no need to allocate memory beforehand.</p>
<p>An important part of a linked list is the <strong>head pointer</strong> that points to the beginning of the list. There may or may not be a <strong>tail pointer</strong> that also points to the end of the list.</p>
<p>The core ingredient of a linked list is a simple node, which consists of two parts: data and the next pointer. So, it is an important idea to remember: <em>a node only knows about its data and its neighbor.</em></p>
<p>The very last node in the linked list points to <code>null</code> to indicate it's the end of the list.</p>
<p>But there are different types of linked lists that differ from each other slightly, so let's briefly take a look at them.</p>
<h3 id="heading-singly-linked-lists">Singly linked lists</h3>
<p>The core idea with singly linked lists is that each node, along with the data it has, has a pointer that points <em>only</em> to the next node:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Node</span> </span>{
  <span class="hljs-keyword">constructor</span>(data) {
    <span class="hljs-built_in">this</span>.data = data;
    <span class="hljs-built_in">this</span>.next = <span class="hljs-literal">null</span>;
  }
}
</code></pre>
<p>And here is an example where we have three nodes, holding the values <code>1</code>, <code>2</code>, and <code>3</code> consecutively:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747915365474/8604a69f-f24f-4dc5-b4a0-2eba452a4305.gif" alt="Animated visualization of a singly linked list, with nodes having 1, 2, and 3 as values." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Here is a simple implementation of a singly linked list in JavaScript:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SinglyLinkedList</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.head = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.tail = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.length = <span class="hljs-number">0</span>;
  }

  <span class="hljs-comment">// Add value to the end of the list</span>
  append(value) {
    <span class="hljs-keyword">let</span> node = <span class="hljs-keyword">new</span> Node(value);
    <span class="hljs-comment">// If the list is empty</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) {
      <span class="hljs-built_in">this</span>.head = node;
      <span class="hljs-built_in">this</span>.tail = <span class="hljs-built_in">this</span>.head;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">this</span>.tail.next = node;
      <span class="hljs-built_in">this</span>.tail = node;
    }

    <span class="hljs-built_in">this</span>.length++;
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
  }

  <span class="hljs-comment">// Add value to the beginning of the list</span>
  prepend(value) {
    <span class="hljs-keyword">let</span> node = <span class="hljs-keyword">new</span> Node(value);
    <span class="hljs-comment">// If the list is empty</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) {
      <span class="hljs-built_in">this</span>.head = node;
      <span class="hljs-built_in">this</span>.tail = <span class="hljs-built_in">this</span>.head;
    } <span class="hljs-keyword">else</span> {
      node.next = <span class="hljs-built_in">this</span>.head;
      <span class="hljs-built_in">this</span>.head = node;
    }

    <span class="hljs-built_in">this</span>.length++;
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
  }

  remove(value) {
    <span class="hljs-comment">// If the list is empty, return null</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) { 
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; 
    }

    <span class="hljs-comment">// If it is the first element</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head.data === value) {
      <span class="hljs-built_in">this</span>.head = <span class="hljs-built_in">this</span>.head.next;
      <span class="hljs-built_in">this</span>.length--;
      <span class="hljs-comment">// If it is the only element </span>
      <span class="hljs-comment">// (we don't have anything after removing it)</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) {
        <span class="hljs-built_in">this</span>.tail = <span class="hljs-literal">null</span>;
      } 
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.head;

    <span class="hljs-keyword">while</span> (currentNode.next) {
      <span class="hljs-keyword">if</span> (currentNode.next.data === value) {
        currentNode.next = currentNode.next.next;
        <span class="hljs-comment">// If it is the last element, update tail</span>
        <span class="hljs-keyword">if</span> (currentNode.next === <span class="hljs-literal">null</span>) {
          <span class="hljs-built_in">this</span>.tail = currentNode;
        } 
        <span class="hljs-built_in">this</span>.length--;
        <span class="hljs-keyword">return</span>;
      }
      currentNode = currentNode.next;
    }
  }

  search(value) {
    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.head;

    <span class="hljs-keyword">while</span> (currentNode) {
      <span class="hljs-keyword">if</span> (currentNode.data === value) {
        <span class="hljs-keyword">return</span> currentNode;
      }
      currentNode = currentNode.next;
    }

    <span class="hljs-comment">// If the value does not exist, return null</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }

  printList() {
    <span class="hljs-keyword">let</span> values = [];
    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.head;
    <span class="hljs-keyword">while</span> (currentNode) {
      values.push(currentNode.data);
      currentNode = currentNode.next;
    }

    <span class="hljs-built_in">console</span>.log(values);
  }
}
</code></pre>
<p><strong>Note:</strong> We'll keep a tail pointer in all these examples for convenience. It <a target="_blank" href="https://softwareengineering.stackexchange.com/a/301863">doesn't hurt</a> to have a tail pointer.</p>
<p>We can now use it:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mySinglyLinkedList = <span class="hljs-keyword">new</span> SinglyLinkedList();

mySinglyLinkedList.prepend(<span class="hljs-number">3</span>);
mySinglyLinkedList.prepend(<span class="hljs-number">143</span>);
mySinglyLinkedList.prepend(<span class="hljs-number">5</span>);

mySinglyLinkedList.printList(); <span class="hljs-comment">// [ 5, 143, 3 ]</span>

mySinglyLinkedList.append(<span class="hljs-number">21</span>);
mySinglyLinkedList.printList(); <span class="hljs-comment">// [ 5, 143, 3, 21 ]</span>

<span class="hljs-built_in">console</span>.log(mySinglyLinkedList.search(<span class="hljs-number">143</span>));
<span class="hljs-comment">// Node {</span>
<span class="hljs-comment">//   data: 143,</span>
<span class="hljs-comment">//   next: Node { data: 3, next: Node { data: 21, next: null } }</span>
<span class="hljs-comment">// }</span>

mySinglyLinkedList.remove(<span class="hljs-number">143</span>);
mySinglyLinkedList.printList(); <span class="hljs-comment">// [ 5, 3, 21 ]</span>

<span class="hljs-built_in">console</span>.log(mySinglyLinkedList.search(<span class="hljs-number">143</span>)); <span class="hljs-comment">// null</span>
</code></pre>
<h3 id="heading-doubly-linked-lists">Doubly linked lists</h3>
<p>Doubly linked lists differ from the "singly" ones in that each node also has another pointer that points to the previous element.</p>
<p>So, this time, a single node will look different:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Node</span> </span>{
  <span class="hljs-keyword">constructor</span>(data) {
    <span class="hljs-built_in">this</span>.data = data;
    <span class="hljs-built_in">this</span>.next = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.previous = <span class="hljs-literal">null</span>;
  }
}
</code></pre>
<p>Here is the same example as above, but as a doubly linked list:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747915463580/605d3541-a702-4bef-9777-28c47800b7aa.gif" alt="Animated visualization of a doubly linked list, with nodes having 1, 2 and 3 as values." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>A simple implementation might look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DoublyLinkedList</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.head = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.tail = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.length = <span class="hljs-number">0</span>;
  }

  <span class="hljs-comment">// Add value to the end of the list</span>
  append(value) {
    <span class="hljs-keyword">let</span> node = <span class="hljs-keyword">new</span> Node(value);
    <span class="hljs-comment">// If the list is empty</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) {
      <span class="hljs-built_in">this</span>.head = node;
      <span class="hljs-built_in">this</span>.tail = <span class="hljs-built_in">this</span>.head;
    } <span class="hljs-keyword">else</span> {
      node.previous = <span class="hljs-built_in">this</span>.tail;
      <span class="hljs-built_in">this</span>.tail.next = node;
      <span class="hljs-built_in">this</span>.tail = node;
    }

    <span class="hljs-built_in">this</span>.length++;
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
  }

  <span class="hljs-comment">// Add value to the beginning of the list</span>
  prepend(value) {
    <span class="hljs-keyword">let</span> node = <span class="hljs-keyword">new</span> Node(value);
    <span class="hljs-comment">// If the list is empty</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) {
      <span class="hljs-built_in">this</span>.head = node;
      <span class="hljs-built_in">this</span>.tail = <span class="hljs-built_in">this</span>.head;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">this</span>.head.previous = node;
      node.next = <span class="hljs-built_in">this</span>.head;
      <span class="hljs-built_in">this</span>.head = node;
    }

    <span class="hljs-built_in">this</span>.length++;
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
  }

  remove(value) {
    <span class="hljs-comment">// If the list is empty, return null</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) { 
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
    }

    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.head;

    <span class="hljs-comment">// If it is the first element</span>
    <span class="hljs-keyword">if</span> (currentNode.data === value) {
      <span class="hljs-built_in">this</span>.head = currentNode.next;
      <span class="hljs-comment">// If the removed element is not the only one,</span>
      <span class="hljs-comment">// make the previous pointer of the new head null</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head) {
        <span class="hljs-built_in">this</span>.head.previous = <span class="hljs-literal">null</span>;
      <span class="hljs-comment">// If the removed element was the only element,</span>
      <span class="hljs-comment">// point the tail to null as well</span>
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">this</span>.tail = <span class="hljs-literal">null</span>;
      }
      <span class="hljs-built_in">this</span>.length--;
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">while</span> (currentNode) {
      <span class="hljs-keyword">if</span> (currentNode.data === value) {
        <span class="hljs-keyword">if</span> (currentNode.previous) {
          currentNode.previous.next = currentNode.next;
        }
        <span class="hljs-keyword">if</span> (currentNode.next) {
          currentNode.next.previous = currentNode.previous;
        <span class="hljs-comment">// If it's the last element in the list, update tail</span>
        <span class="hljs-comment">// to point to the previous node</span>
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">this</span>.tail = currentNode.previous;
        }

        <span class="hljs-built_in">this</span>.length--;
        <span class="hljs-keyword">return</span>;
      }

      currentNode = currentNode.next;
    }
  }

  search(value) {
    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.head;
    <span class="hljs-keyword">while</span> (currentNode) {
      <span class="hljs-keyword">if</span> (currentNode.data === value) {
        <span class="hljs-keyword">return</span> currentNode;
      }
      currentNode = currentNode.next;
    }

    <span class="hljs-comment">// If the value does not exist, return null</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }

  printList() {
    <span class="hljs-keyword">let</span> values = [];
    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.head;

    <span class="hljs-keyword">while</span> (currentNode) {
      values.push(currentNode.data);
      currentNode = currentNode.next;
    }

    <span class="hljs-built_in">console</span>.log(values);
  }
}
</code></pre>
<h3 id="heading-circular-linked-lists">Circular linked lists</h3>
<p>With circular linked lists, we have the last node also pointing to the first element, creating circularity.</p>
<p>We'll only look at the singly circular linked list for simplicity's sake, so our node will look the same as in the first example:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Node</span> </span>{
  <span class="hljs-keyword">constructor</span>(data) {
    <span class="hljs-built_in">this</span>.data = data;
    <span class="hljs-built_in">this</span>.next = <span class="hljs-literal">null</span>;
  }
}
</code></pre>
<p>The same example, in a circular linked list fashion:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747915538225/ea4341cb-2a39-4728-b833-362455a51cdd.gif" alt="Animated visualization of a circular linked list, nodes having 1, 2, and 3 as values." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Here is a simple implementation:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CircularLinkedList</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.head = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.tail = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.length = <span class="hljs-number">0</span>;
  }

  <span class="hljs-comment">// Add value to the "end" of the list</span>
  append(value) {
    <span class="hljs-keyword">let</span> node = <span class="hljs-keyword">new</span> Node(value);
    <span class="hljs-comment">// If the list is empty</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) {
      <span class="hljs-built_in">this</span>.head = node;
      <span class="hljs-built_in">this</span>.tail = node;
      <span class="hljs-comment">// As the only node in the list, it should point to itself</span>
      node.next = node;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// As the "last" node, it should point to the head (this.tail.next)</span>
      node.next = <span class="hljs-built_in">this</span>.tail.next;
      <span class="hljs-built_in">this</span>.tail.next = node;
      <span class="hljs-built_in">this</span>.tail = node;
    }

    <span class="hljs-built_in">this</span>.length++;
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
  }

  <span class="hljs-comment">// Add value to the beginning of the list</span>
  prepend(value) {
    <span class="hljs-keyword">let</span> node = <span class="hljs-keyword">new</span> Node(value);
    node.next = <span class="hljs-built_in">this</span>.head;
    <span class="hljs-comment">// Update last node's next pointer to point to the new node</span>
    <span class="hljs-built_in">this</span>.tail.next = node;
    <span class="hljs-built_in">this</span>.head = node;
    <span class="hljs-built_in">this</span>.length++;
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
  }  

  remove(value) {
    <span class="hljs-comment">// If the list is empty, return null</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) { 
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; 
    }

    <span class="hljs-comment">// If it is the first element</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head.data === value) {
      <span class="hljs-comment">// If it's the only element</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head.next === <span class="hljs-built_in">this</span>.head) {
        <span class="hljs-built_in">this</span>.head = <span class="hljs-literal">null</span>;
        <span class="hljs-built_in">this</span>.tail = <span class="hljs-literal">null</span>;
        <span class="hljs-keyword">return</span>;
      }
      <span class="hljs-built_in">this</span>.head = <span class="hljs-built_in">this</span>.head.next;
      <span class="hljs-built_in">this</span>.tail.next = <span class="hljs-built_in">this</span>.head;
      <span class="hljs-built_in">this</span>.length--;
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.head;
    <span class="hljs-keyword">let</span> prevNode = <span class="hljs-literal">null</span>;

    <span class="hljs-comment">// Iterate until you find the value or</span>
    <span class="hljs-comment">// you don't find it after traversing the whole list</span>
    <span class="hljs-keyword">while</span> (currentNode.data !== value || prevNode === <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">if</span> (currentNode.next === <span class="hljs-built_in">this</span>.head) { 
        <span class="hljs-keyword">break</span>; 
      }
      prevNode = currentNode;
      currentNode = currentNode.next;
    }

    <span class="hljs-keyword">if</span> (currentNode.data === value) {
      <span class="hljs-comment">// If there is a previous node before the element to be removed,</span>
      <span class="hljs-comment">// update the previous node's next pointer to point to</span>
      <span class="hljs-comment">// the one after the element to be removed</span>
      <span class="hljs-comment">// (unlink it)</span>
      <span class="hljs-keyword">if</span> (prevNode) {
        prevNode.next = currentNode.next;
        <span class="hljs-comment">// If the element to be removed is the last one,</span>
        <span class="hljs-comment">// update tail to be the previous node</span>
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.tail === currentNode) {
          <span class="hljs-built_in">this</span>.tail = prevNode;
        }
      <span class="hljs-comment">// If the element to be removed is the first one in the list</span>
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// If it's the only one in the list</span>
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head.next === <span class="hljs-built_in">this</span>.head) {
          <span class="hljs-built_in">this</span>.head = <span class="hljs-literal">null</span>;
          <span class="hljs-built_in">this</span>.tail = <span class="hljs-literal">null</span>;
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">this</span>.head = <span class="hljs-built_in">this</span>.head.next;
          <span class="hljs-built_in">this</span>.tail.next = <span class="hljs-built_in">this</span>.head;
        }
      }
    }
  }

  printList() {
    <span class="hljs-keyword">let</span> nodes = [];
    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.head;
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head === <span class="hljs-literal">null</span>) { 
      <span class="hljs-built_in">console</span>.log(nodes); 
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Traverse the list once to add the values, </span>
    <span class="hljs-comment">// don't go in circles</span>
    <span class="hljs-keyword">do</span> {
      nodes.push(currentNode.data);
      currentNode = currentNode.next;
    } <span class="hljs-keyword">while</span> (currentNode !== <span class="hljs-built_in">this</span>.head);

    <span class="hljs-built_in">console</span>.log(nodes);
  }
}
</code></pre>
<h4 id="heading-time-and-space-complexity-6">Time and space complexity</h4>
<p>With linked lists, the time complexity for accessing an element is in the worst case \(O(n)\). <em>Prepending</em> and <em>appending</em> an element depends on whether we have a tail pointer. If we have it, then both operations are \(O(1)\), as we only need to arrange pointers. But if we don't have a tail pointer, <em>appending</em> an element requires traversing the whole list, so it is an \(O(n)\) operation. Removing an element is similar – in the worst case, it is \(O(n)\).</p>
<p>If the space complexity is linear – \(O(n)\)– then the amount of data to store grows linearly with the number of nodes in the list.</p>
<h2 id="heading-interlude-fast-amp-slow-pointers">Interlude: Fast &amp; Slow Pointers</h2>
<p>Let's take a quick look at a technique that comes in handy when it comes to working with linked lists.</p>
<p>We can keep two pointers while traversing a linked list: fast and slow. While the fast one increases by two steps, the slow pointer will increase by just one step.</p>
<h3 id="heading-finding-the-middle-node-of-a-linked-list">Finding the middle node of a linked list</h3>
<p>When the fast pointer reaches the end of the list, the slow pointer will be at the "middle" node.</p>
<p>Let's see how it might work:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> slow = head;
<span class="hljs-keyword">let</span> fast = head;

<span class="hljs-keyword">while</span> (fast !== <span class="hljs-literal">null</span> &amp;&amp; fast.next !== <span class="hljs-literal">null</span>) {
  slow = slow.next;
  fast = fast.next.next;
}
</code></pre>
<p>We can think of a list like <code>[1, 2, 3, 4, 5]</code> (where each value is a node in the linked list).</p>
<p>Both <code>fast</code> and <code>slow</code> start pointing to the head, that is, <code>1</code>.</p>
<p>Then, we update the slow pointer one step, which will be <code>2</code>. And, <code>fast</code> will be at <code>3</code>.</p>
<p>When we update <code>slow</code> again, it will be at <code>3</code>. When the fast pointer increases, it will be two steps ahead, and its <code>next</code> pointer will point to the <code>null</code> value, at which point our loop will stop iterating.</p>
<p><code>slow</code> will end up pointing to the node with the value <code>3</code>, which is the middle node.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747915833404/d3c562e8-36e3-459f-a29c-860e0d4869f0.gif" alt="Animated visualization of fast and slow pointers technique on a linked list of 5 nodes." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>With an even number of nodes, there are two candidates for the middle node. For example, with a list like <code>[1, 2, 3, 4]</code>, our current implementation will find the middle as <code>3</code>. This technique is also useful to detect cycles in a linked list.</p>
<h2 id="heading-chapter-seven-trees">Chapter Seven: Trees</h2>
<p>Let’s take a look at a non-linear data structure that is pretty familiar to many developers: trees.</p>
<p>Whether familiarity breeds contempt or not is arguable, so let's start with the simplest component of a tree: a node.</p>
<p>Trees, like linked lists, are made up of nodes. The simplest version of a tree is just the <strong>root node</strong> which doesn't have any edges (links) pointing to it; that is, it has no <strong>parent nodes</strong>. It is the starting point, in a way.</p>
<p>A tree can only have one root node, and when you think about it, <em>if there are</em> \(n\) <em>nodes in a tree, that means there are</em> \(n - 1\) <em>edges (links)</em> because there is no edge (link) pointing to the root node.</p>
<p>If you've looked at a tree long enough, you might've had a moment of epiphany: a tree has smaller trees within itself. A branch may as well be a trunk, having other branches for the little tree it constitutes.</p>
<p>The tree data structure is like this, it is recursive: <em>a child node can be the root of a subtree</em>.</p>
<p>Two terms that are important when it comes to a tree node are <strong>depth</strong> and <strong>height</strong>.</p>
<p>The <strong>depth</strong> of a node is how far away it is from the root node (how many edges (links) does it take to travel from the root node to it), and the <strong>height</strong> of a node is how far away it is from its furthest <strong>leaf node</strong> (which is a node that has no children).</p>
<p><strong>Note:</strong> The height of the root node is the same as the height of the whole tree.</p>
<p>A <strong>balanced tree</strong> is one where <em>the heights of the left and right subtrees of every node differ by at most 1</em>.</p>
<h3 id="heading-binary-trees-binary-search-trees-bsts">Binary trees, binary search trees (BSTs)</h3>
<p>A <strong>binary tree</strong> is a tree where each node has at most two children. That is, a node can have a left child node and a right child node, and no more.</p>
<p>The maximum number of nodes in a binary tree is \(2^h - 1\) where \(h\) is the height of the tree. This is where the <em>binary</em> of the binary tree makes sense: on each level, the number of nodes grows proportionately to the exponents of \(2\).</p>
<p>For example, the number of nodes on the first level (the 0th level) is \(2^0 = 1\), which is just the root node. The second level has at most 2 nodes: \(2^1 = 2\) (remember that we're counting from \(0\), so the second level is \(1\)).</p>
<p>A <strong>binary search tree</strong> is a binary tree where the values smaller than the node go to its left and those greater than it go to its right:</p>
<p>$$\text{left children } \lt \text{ node } \lt \text{ right children}$$</p><p>Here is an example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747916761231/c0b5301c-6832-43a9-8abd-30f578ac2a29.gif" alt="Animated visualization of a binary search tree with 8 as the root node, 3 as its left child, 10 as its right child. 3 has 1 as it left child, 6 as its right child. 6 has 4 as its left child, 7 as its right child. 10 has 14 as its right child. 14 has 13 as its left child. " class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>We can define a tree node like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> TreeNode {
  val: <span class="hljs-built_in">number</span>;
  left: TreeNode | <span class="hljs-literal">null</span>;
  right: TreeNode | <span class="hljs-literal">null</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">val: <span class="hljs-built_in">number</span>, left?: TreeNode | <span class="hljs-literal">null</span>, right?: TreeNode | <span class="hljs-literal">null</span></span>) {
    <span class="hljs-built_in">this</span>.val = val;
    <span class="hljs-built_in">this</span>.left = (left === <span class="hljs-literal">undefined</span> ? <span class="hljs-literal">null</span> : left);
    <span class="hljs-built_in">this</span>.right = (right === <span class="hljs-literal">undefined</span> ? <span class="hljs-literal">null</span> : right);
  }
}
</code></pre>
<h4 id="heading-inserting-into-a-binary-search-tree">Inserting into a binary search tree</h4>
<p>If we want to insert a new node into a binary search tree, we need to insert it into its proper place to keep the properties of a BST intact.</p>
<h5 id="heading-recursive-solution">Recursive solution:</h5>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">insertIntoBST</span>(<span class="hljs-params">root: TreeNode | <span class="hljs-literal">null</span>, val: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">if</span> (root === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> TreeNode(val);
  }

  <span class="hljs-keyword">if</span> (val &lt; root.val) {
    root.left = insertIntoBST(root.left, val);
  } <span class="hljs-keyword">else</span> {
    root.right = insertIntoBST(root.right, val);
  }

  <span class="hljs-keyword">return</span> root;
}
</code></pre>
<p>Here, we traverse the tree until we find a space (a <code>null</code> position) for our value that is waiting to be a <code>TreeNode</code>. We start with the root node. If the value of the node-to-be-inserted is less than the value of the root node, we go left (passing <code>root.left</code> as the <code>root</code> argument to the function). Otherwise, we go right (passing <code>root.right</code> as the <code>root</code> argument).</p>
<h4 id="heading-time-and-space-complexity-7">Time and space complexity</h4>
<p>The time complexity is \(O(h)\) where \(h\) is the height of the tree. On each level in the tree, we either go left or right, so we don't necessarily visit every single node. The space complexity is also \(O(h)\) because we use recursion, creating a new stack frame for each function call.</p>
<p><em>Note that if the tree is unbalanced, the time and space complexity can be said to be</em> \(O(n)\)<em>.</em></p>
<h5 id="heading-iterative-solution">Iterative solution:</h5>
<p>We can also do it iteratively, using pointers only:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">insertIntoBST</span>(<span class="hljs-params">root: TreeNode | <span class="hljs-literal">null</span>, val: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">if</span> (root === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> TreeNode(val);
  }

  <span class="hljs-keyword">let</span> prevNode: TreeNode | <span class="hljs-literal">null</span> = <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">let</span> currentNode: TreeNode | <span class="hljs-literal">null</span> = root;

  <span class="hljs-keyword">while</span> (currentNode !== <span class="hljs-literal">null</span>) {
    prevNode = currentNode;
    <span class="hljs-keyword">if</span> (val &lt; currentNode.val) {
      currentNode = currentNode.left;
    } <span class="hljs-keyword">else</span> {
      currentNode = currentNode.right;
    }
  }

  <span class="hljs-keyword">if</span> (prevNode) {
    <span class="hljs-keyword">if</span> (val &lt; prevNode.val) {
      prevNode.left = <span class="hljs-keyword">new</span> TreeNode(val);
    } <span class="hljs-keyword">else</span> {
      prevNode.right = <span class="hljs-keyword">new</span> TreeNode(val);
    }
  }

  <span class="hljs-keyword">return</span> root;
}
</code></pre>
<p>Here, we do the same thing: iterating until we find the correct place, but also keeping track of the parent node. Then, we insert the node as either the left or the right child of the parent, depending on its value.</p>
<h4 id="heading-time-and-space-complexity-8">Time and space complexity</h4>
<p>The time complexity is again \(O(h)\) (<em>or if the tree is unbalanced,</em> \(O(n)\)) for the same reason as in the recursive solution. But the space complexity is constant – \(O(1)\) – as we only use pointers.</p>
<h4 id="heading-deleting-from-a-binary-search-tree">Deleting from a binary search tree</h4>
<p>The challenging thing when deleting a node from a BST is keeping the BST as a BST. All smaller values should still go to the root node's left subtree, and all those that are larger should go to the root node's right subtree.</p>
<p>Let's take a look at how we might do it in JavaScript:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deleteNode</span>(<span class="hljs-params">root: TreeNode | null, key: number</span>) </span>{
  <span class="hljs-keyword">if</span> (root === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">return</span> root;
  }

  <span class="hljs-keyword">if</span> (key &lt; root.val) {
    root.left = deleteNode(root.left, key);
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (key &gt; root.val) {
    root.right = deleteNode(root.right, key);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Node-to-be-deleted has no children</span>
    <span class="hljs-keyword">if</span> (root.left === <span class="hljs-literal">null</span> &amp;&amp; root.right === <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
    } 

    <span class="hljs-comment">// If either the left or the right child exists,</span>
    <span class="hljs-comment">// return the one that exists as the new child </span>
    <span class="hljs-comment">// of the parent node (of the node-to-be-deleted)</span>
    <span class="hljs-keyword">if</span> (root.left === <span class="hljs-literal">null</span> || root.right === <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span> root.left ? root.left : root.right;
    }

    <span class="hljs-comment">// If both children exist, traverse the left subtree, get its maximum value...</span>
    <span class="hljs-keyword">let</span> currentNode = root.left;

    <span class="hljs-keyword">while</span> (currentNode.right !== <span class="hljs-literal">null</span>) {
      currentNode = currentNode.right;
    }

    <span class="hljs-comment">// ...replace it with the node-to-be-deleted</span>
    root.val = currentNode.val;
    <span class="hljs-comment">// ...then apply the recursion to the left subtree to get rid of the duplicate value</span>
    root.left = deleteNode(root.left, root.val);
  }

  <span class="hljs-keyword">return</span> root;
}
</code></pre>
<p>We traverse the tree until we find the node to be deleted. Once we find it, there are several things to do.</p>
<p>In the case where it doesn't have any child nodes, we can return <code>null</code> and be done with it.</p>
<p>If it has one child node, we can return the one that exists using the ternary operation (<code>return root.left ? root.left : root.right</code>).</p>
<p><strong>Note:</strong> <em>In this case, we're essentially making the root of the subtree the child of the parent node.</em></p>
<p>For example, in the image, if the node-to-be-deleted is 10 (it has only right child node with the value 14), we make 14 the right child of 8. It doesn't break our BST, because those that are larger than 8 continue to be in the right subtree of 8:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747918477656/7a66ad05-3c93-4482-987e-399f65e9dc57.png" alt="A binary tree that has the value 8 as its root node. It has a left child with the value 3 and a right child with the value 10. The left child has a left child node that has the value 1 and a right child node that has the value 6, which has a left child node with the value 4 and a right child node with the value 7. The right child of the root node that has the value 10 has a right child node with value 14, which has a left child node that has the value 13." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Otherwise, if both the left and right children of the node-to-be-deleted exist, we need to do something different.</p>
<p>In this case, we'll replace the node-to-be-deleted with the largest value in the left subtree.</p>
<p>But, after replacing, we'll have two nodes of the same value in both places, so we need to apply <code>deleteNode</code> itself to the subtree that we've taken our replacement node from.</p>
<p>This is all done to keep the BST as BST. It might be a bit difficult to wrap your head around at first, but <a target="_blank" href="https://www.youtube.com/watch?v=LFzAoJJt92M">NeetCode has a detailed explanation of this problem</a>.</p>
<p><strong>Note</strong> that we can also use the smallest value in the right subtree as well. In that case, our code would look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> currentNode = root.right;

<span class="hljs-keyword">while</span> (currentNode.left !== <span class="hljs-literal">null</span>) {
  currentNode = currentNode.left;
}

root.val = currentNode.val;
root.right = deleteNode(root.right, root.val);
</code></pre>
<h4 id="heading-time-and-space-complexity-9">Time and space complexity</h4>
<p>Similar to inserting into a BST, both the time and space complexity of deleting from a BST will be \(O(h)\) where \(h\) is the height of the tree.</p>
<h4 id="heading-traversals">Traversals</h4>
<p>We'll take a brief look at two of the most famous ways to traverse a tree where the order in which we visit the nodes matters: depth-first search and breadth-first search.</p>
<h5 id="heading-1-depth-first-search-dfs">1. Depth-First Search (DFS)</h5>
<p>In a depth-first search, we traverse through a branch until we get to a leaf node. Then, we backtrack and do the same thing with another branch.</p>
<p>There are three common ways to do a depth-first search:</p>
<ul>
<li><p>preorder traversal</p>
</li>
<li><p>inorder traversal</p>
</li>
<li><p>postorder traversal</p>
</li>
</ul>
<h6 id="heading-preorder-traversal">Preorder traversal:</h6>
<p>It goes like this: We first visit the node, then go on to its left subtree, then the right subtree.</p>
<p><strong>node ➞ left subtree ➞ right subtree</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747918872908/dedfc341-d32e-4558-abd6-00ff8d67b46f.gif" alt="Animated visualization of the same binary search tree displaying preorder traversal, with highlighted nodes in this order: 8, 3, 1, 6, 4, 7, 10, 14, 13." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>We can do a preorder walk recursively:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">preorderWalk</span>(<span class="hljs-params">node</span>) </span>{
  <span class="hljs-keyword">if</span> (node === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-built_in">console</span>.log(node.val);
  preorderWalk(node.left);
  preorderWalk(node.right);
}
</code></pre>
<h6 id="heading-inorder-traversal">Inorder traversal:</h6>
<p>It goes like this: we first visit the left subtree, then the node, then the right subtree.</p>
<p><strong>left subtree ➞ node ➞ right subtree</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747918945108/a1ff3284-8aa0-4815-928c-99cdbd8ac956.gif" alt="Animated visualization of the same binary search tree displaying inorder traversal, with highlighted nodes in this order: 1, 3, 4, 6, 7, 8, 10, 13, 14." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><strong>Note:</strong> The inorder traversal gives us the sorted values.</p>
<p>We can do an inorder walk recursively as well:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">inorderWalk</span>(<span class="hljs-params">node</span>) </span>{
  <span class="hljs-keyword">if</span> (node === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">return</span>;
  }

  inorderWalk(node.left);
  <span class="hljs-built_in">console</span>.log(node.val);
  inorderWalk(node.right);
}
</code></pre>
<h6 id="heading-postorder-traversal">Postorder traversal:</h6>
<p>It goes like this: we first visit the left subtree, then the right subtree, and finally the node.</p>
<p><strong>left subtree ➞ right subtree ➞ node</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919023258/e3c7f4a3-3ec8-47d3-aa49-e8f4b0f8d30a.gif" alt="Animated visualization of the same binary search tree displaying postorder traversal, with highlighted nodes in this order: 1, 4, 7, 6, 3, 13, 14, 10, 8." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>We can do a postorder walk recursively:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">postorderWalk</span>(<span class="hljs-params">node</span>) </span>{
  <span class="hljs-keyword">if</span> (node === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">return</span>;
  }

  postorderWalk(node.left);
  postorderWalk(node.right);
  <span class="hljs-built_in">console</span>.log(node.val);
}
</code></pre>
<h5 id="heading-2-breadth-first-search-bfs">2. Breadth-First Search (BFS)</h5>
<p>In breadth-first search, we visit the nodes level by level, that is, visiting every child of a node first before moving on.</p>
<p>A queue is used when implementing a BFS. Since we don't have edges connecting all the children on one level together, it makes sense to keep them in a queue and visit each one when their time comes. When a node is added to the queue and has not been visited yet, it's called a <strong>discovered node</strong>.</p>
<p>A simple BFS operation looks like this (which is repeated until the queue is empty):</p>
<ul>
<li><p>visit node</p>
</li>
<li><p>enqueue left child</p>
</li>
<li><p>enqueue right child</p>
</li>
</ul>
<p>Note that the breadth-first search is also known as <em>level-order traversal</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919081789/17beb70f-7302-4f32-9a9a-72d69e190ac9.gif" alt="Animated visualization of the same binary search tree displaying level-order traversal, with highlighted nodes in this order: 8, 3, 10, 1, 6, 14, 4, 7, 13." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>A simple example of a level-order traversal in JavaScript might look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">levelOrderWalk</span>(<span class="hljs-params">root</span>) </span>{
  <span class="hljs-keyword">if</span> (root === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">let</span> queue = [];
  queue.push(root);

  <span class="hljs-keyword">while</span> (queue.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">let</span> currentNode = queue[<span class="hljs-number">0</span>];

    <span class="hljs-built_in">console</span>.log(currentNode.val);

    <span class="hljs-keyword">if</span> (currentNode.left !== <span class="hljs-literal">null</span>) {
      queue.push(currentNode.left);
    }

    <span class="hljs-keyword">if</span> (currentNode.right !== <span class="hljs-literal">null</span>) {
      queue.push(currentNode.right);
    }

    <span class="hljs-comment">// Remove the current node</span>
    queue.shift();
  }
}
</code></pre>
<p>This example is based on Vaidehi Joshi's <a target="_blank" href="https://gist.github.com/vaidehijoshi/27f9fa6b6b68f70360019805b5ca3692#file-level_order_search-js">GitHub Gist</a>.</p>
<h2 id="heading-chapter-eight-heap-priority-queue">Chapter Eight: Heap / Priority Queue</h2>
<p>It’s now time to take a look at a data structure called a <em>heap</em>, which is a great way to implement an <a target="_blank" href="https://en.wikipedia.org/wiki/Abstract_data_type">abstract data type</a> called a <strong>priority queue</strong>. They're so interrelated that priority queues are sometimes referred to as heaps – because heaps are a very efficient way to create a priority queue.</p>
<h3 id="heading-heap-properties">Heap properties</h3>
<p>The kind of heap we're interested in is also called a <strong>binary heap</strong> because it's just a binary tree that has specific properties.</p>
<p>One of them is that it must be a <strong>complete binary tree</strong>, meaning that all the levels must be filled, and <em>all nodes in the last level should be as far left as possible</em>.</p>
<p>For example, when it comes to shape, this is a complete binary tree:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919274226/cdc2f987-3327-4220-8584-ad3999ea7f39.gif" alt="Animated visualization of a tree with root node having two children, both of its left and right children having two children their own. The left child of the left child has only a left child on its own." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>But heaps must also be either a <strong>max heap</strong> or a <strong>min heap</strong> – all the parent nodes must be either greater than or equal to the values of their children (if it's a max heap) or less than or equal to the values of their children (if it's a min heap).</p>
<p>A max heap might look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919301787/6a3b30cd-a686-4112-80a9-0249c7597751.gif" alt="Animated visualization of a max heap, having 42 at the top, 19 at its left, 36 at its right. 19 has 17 at its left, 3 at its right. 36 has 25 at its left, 1 at its right. 17 has 2 at its left." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><strong>Note:</strong> A left child doesn't have to be less than the right child at all, as in a binary search tree. Also, we can always have duplicate values in a heap.</p>
<p>A min heap, on the other hand, has the values of parent nodes less than those of their children:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919335031/b5b82848-ac1f-405d-9124-725a9c26af55.gif" alt="Animated visualization of a min heap, having 1 at the top, 2 at its left, 3 at its right. 2 has 17 at its left, 19 at its right. 3 has 36 at its left, 7 at its right. 17 has 42 at its left." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><strong>Note:</strong> When we have a max heap, the root node will have the maximum value. And, if we have a min heap instead, the root node will have the minimum value.</p>
<h3 id="heading-heaps-with-arrays">Heaps with arrays</h3>
<p>We can create a heap using an array. Since the root node is the most interesting element with either a maximum or minimum value, it'll be the first element in our array, residing at the 0th index.</p>
<p>What's nice about using an array is that, given a parent node's index \(i\), its left child will be at the index \(2i + 1\), and its right child will be at the index \(2i + 2\).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919415922/b98a55a9-f9cc-4e11-8d53-f5a8a411d861.gif" alt="Animated visualization of the max heap above implemented as an array. A left child's index corresponds to 2i+1, a right child's index corresponds to 2i+2." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Given that, any child node's parent will be at the index \(\lfloor{\frac{(n - 1)}{2}}\rfloor\).</p>
<p><strong>Note:</strong> \(\lfloor\) and \(\rfloor\)indicate the <a target="_blank" href="https://en.wikipedia.org/wiki/Floor_and_ceiling_functions">floor function</a>.</p>
<p>One question we might ask at this moment is that why should we use an array at all?</p>
<p>The answer lies in the word <strong>queue</strong> of a <strong>priority queue</strong>. Since a queue is mainly concerned with the first element (following the <a target="_blank" href="https://en.wikipedia.org/wiki/FIFO_\(computing_and_electronics\)">FIFO principle</a>), an array can be an ideal choice. In a priority queue, each element has a priority, and the value with the highest priority is dequeued first.</p>
<h3 id="heading-insertingremoving-elements">Inserting/removing elements</h3>
<p>Let's take a look at how we can add an element to a heap.</p>
<p>We know that we have to add the new element to the bottom leftmost place, but once we do that, it might violate the max heap or the min heap property. Then, how can we avoid violating the <strong>heap-order property</strong>?</p>
<p>We'll <strong>heapify</strong>, of course!</p>
<p>Let's say that we want to add a node with the value <code>20</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919523941/eb0959dd-fcf8-4fb7-9ebf-938fde24ba10.gif" alt="Animated visualization of the max heap above. A new item 20 is first inserted at the leftest possible place. Then it swaps places with 17 and then 19, coming to the left of 42." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>So, heapify is the swapping of nodes until we know that the heap-order property is maintained.</p>
<p>A similar thing happens when we need to remove an element. But since we're mainly concerned with the maximum or the minimum element, we just need to remove the root node. So, how are we going to do that?</p>
<p>We start off by swapping the last element (the bottom leftmost one) with the root. Now we can easily remove the "root," which resides as a leaf node. But we still need to maintain the heap-order property, so we need to <strong>heapify</strong> again.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919564248/0e7b9a38-6b23-4448-a804-cf095d16f30e.gif" alt="Animated visualization of the max heap above. 2 swapping places with 42, 42 being disappeared. 2 later swaps places with 36 and 25, now 36 coming to the top." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-heapsort">Heapsort</h3>
<p>Even better thing is that if we have a heap, and continually heapify it, we can sort an array.</p>
<p>Let's build a max heap first:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">buildMaxHeap</span>(<span class="hljs-params">arr: <span class="hljs-built_in">number</span>[]</span>) </span>{
  <span class="hljs-comment">/*
  Index of the last internal node 
  (i.e., the parent of the last leaf node, 
   or, the last non-leaf node).
  The last leaf node will reside at index arr.length - 1,
  so, we're getting its parent using the formula mentioned above.
  */</span>
  <span class="hljs-keyword">let</span> i = <span class="hljs-built_in">Math</span>.floor((arr.length - <span class="hljs-number">1</span>) / <span class="hljs-number">2</span>);

  <span class="hljs-keyword">while</span> (i &gt;= <span class="hljs-number">0</span>) {
    heapify(arr, i, arr.length);
    i--;
  }

  <span class="hljs-keyword">return</span> arr;
}
</code></pre>
<p>Then, the <code>heapify</code> function:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">heapify</span>(<span class="hljs-params">arr: <span class="hljs-built_in">number</span>[], i: <span class="hljs-built_in">number</span>, maxLength: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">while</span> (i &lt; maxLength) {
    <span class="hljs-keyword">let</span> index = i;
    <span class="hljs-keyword">let</span> leftChildIdx = <span class="hljs-number">2</span> * i + <span class="hljs-number">1</span>;
    <span class="hljs-keyword">let</span> rightChildIdx = leftChildIdx + <span class="hljs-number">1</span>;

    <span class="hljs-keyword">if</span> (leftChildIdx &lt; maxLength &amp;&amp; arr[leftChildIdx] &gt; arr[index]) {
      index = leftChildIdx;
    }

    <span class="hljs-keyword">if</span> (rightChildIdx &lt; maxLength &amp;&amp; arr[rightChildIdx] &gt; arr[index]) {
      index = rightChildIdx;
    }

    <span class="hljs-keyword">if</span> (index === i) { <span class="hljs-keyword">return</span>; }

    <span class="hljs-comment">// Swap</span>
    [arr[i], arr[index]] = [arr[index], arr[i]];

    i = index;
  }
}
</code></pre>
<p>With a given index <code>i</code>, we get its left and right children indices, and if the indices are within bounds, we check if they are out of order. In that case, we make the <code>index</code> the index of the child, and swap the two nodes. Then, we continue with that new index, assigning it to <code>i</code>.</p>
<p>Now, <code>heapify</code> is nice and all, but how can we actually use it for sorting?</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">heapSort</span>(<span class="hljs-params">arr: <span class="hljs-built_in">number</span>[]</span>) </span>{
  buildMaxHeap(arr);

  <span class="hljs-keyword">let</span> lastElementIdx = arr.length - <span class="hljs-number">1</span>;

  <span class="hljs-keyword">while</span> (lastElementIdx &gt; <span class="hljs-number">0</span>) {
    [arr[<span class="hljs-number">0</span>], arr[lastElementIdx]] = [arr[lastElementIdx], arr[<span class="hljs-number">0</span>]];

    heapify(arr, <span class="hljs-number">0</span>, lastElementIdx);
    lastElementIdx--;
  }

  <span class="hljs-keyword">return</span> arr;
}
</code></pre>
<p><strong>Note</strong> that our max heap <code>[42, 19, 36, 17, 3, 25, 1, 2]</code> won't change when used in the <code>buildMaxHeap</code> function, as it's already a max heap! But if it were to have <code>17</code> as the right child of <code>42</code>, then <code>17</code> would have <code>25</code> as a child, which breaks the heap-order property. So, using <code>buildMaxHeap</code> with this broken version will correctly swap the <code>17</code> and <code>25</code>, making it a max heap:</p>
<pre><code class="lang-typescript">buildMaxHeap([<span class="hljs-number">42</span>, <span class="hljs-number">36</span>, <span class="hljs-number">17</span>, <span class="hljs-number">19</span>, <span class="hljs-number">3</span>, <span class="hljs-number">25</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>]);

<span class="hljs-comment">// -&gt; [42, 36, 25, 19, 3, 17, 1, 2]</span>
</code></pre>
<p>In <code>heapSort</code>, with our newly built max heap, we'll start with swapping the first and last nodes. Then, we'll keep heapifying until we get all the elements in their place. If we use it with our very own max heap, we can see that it returns the sorted array:</p>
<pre><code class="lang-typescript">heapSort([<span class="hljs-number">42</span>, <span class="hljs-number">19</span>, <span class="hljs-number">36</span>, <span class="hljs-number">17</span>, <span class="hljs-number">3</span>, <span class="hljs-number">25</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>]);
<span class="hljs-comment">// -&gt; [1, 2, 3, 17, 19, 25, 36, 42]</span>
</code></pre>
<p>The examples are adapted from <a target="_blank" href="https://medium.com/basecs/heapify-all-the-things-with-heap-sort-55ee1c93af82">Vaidehi Joshi's article</a>.</p>
<h4 id="heading-time-and-space-complexity-10">Time and space complexity</h4>
<p>Heap sort, as a nice sorting algorithm it is, runs in \(O(n \ log \ n)\) time.</p>
<p>In this example, building the max heap starts from the last non-leaf node and goes up to the root node, each time calling <code>heapify</code>. The <code>heapify</code> function has a time complexity of \(O(log \ n)\) as we're working with a binary tree, and in the worst case, we get to do it for all the levels. Since we do it \(n / 2\) times, overall, <code>buildMaxHeap</code> has \(O(n \ log \ n)\) time complexity.</p>
<p>We're swapping the first and last elements, and heapifying as we go through each element, so this is also overall an \(O(n \ log \ n)\) operation — which makes the time complexity of <code>heapSort</code> \(O(n \ log \ n)\)<em>.</em></p>
<p><strong>Note:</strong> Building the max heap <a target="_blank" href="https://stackoverflow.com/questions/9755721/how-can-building-a-heap-be-on-time-complexity">can be improved to have</a> \(O(n)\) <a target="_blank" href="https://stackoverflow.com/questions/9755721/how-can-building-a-heap-be-on-time-complexity">runtime</a>.</p>
<p>Since there is no use of auxiliary space, the space complexity is constant, \(O(1)\).</p>
<h2 id="heading-chapter-nine-backtracking">Chapter Nine: Backtracking</h2>
<p>Let's start with admitting this one fact: backtracking is hard. Or rather, <em>understanding it the first time</em> is hard. Or, it's one of those concepts that you think you grasped it, only to realize later that you actually didn't.</p>
<p>We'll focus on one problem of finding the subsets of an array, but before that, let's imagine that we're walking along a path.</p>
<p>Then, we reach a fork. We pick one of the paths, and walk.</p>
<p>Then, we reach another fork in the path. We pick one of the paths again, and go on walking, then we reach a dead end. So, we <em>backtrack</em> to the last point we had a fork, then go through the other path that we didn't choose the first time.</p>
<p>Then we reach another dead end. So, we <em>backtrack</em> once more and realize that there are no other paths we can go from there. So we <em>backtrack</em> again, and explore the other path we didn't choose the first time we came to this point.</p>
<p>We reach yet another dead end, so we <em>backtrack</em>. We see that there are no more paths to explore, so we <em>backtrack</em> once more.</p>
<p>Now, we're at our starting point. There are no more paths left to explore, so we can stop walking.</p>
<p>It was a nice but tiring walk, and it went like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747920157477/3151155c-d40c-408d-bff6-5d326ad0a0f3.gif" alt="Animated visualization of the concept of backtracking." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now, let's take a look at a LeetCode problem.</p>
<h3 id="heading-subsets">Subsets</h3>
<p>The description for <a target="_blank" href="https://leetcode.com/problems/subsets">Subsets</a> says:</p>
<blockquote>
<p>Given an integer array <code>nums</code> of <strong>unique</strong> elements, return <em>all possible subsets (the power set)</em>.</p>
<p>The solution set <strong>must not</strong> contain duplicate subsets. Return the solution in <strong>any order</strong>.</p>
</blockquote>
<p>For example:</p>
<pre><code class="lang-plaintext">Input: nums = [1, 2, 3]
Output: [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
</code></pre>
<p>Or:</p>
<pre><code class="lang-plaintext">Input: nums = [0]
Output: [[], [0]]
</code></pre>
<p>Before diving into the solution code, let's take a look at how backtracking will work in this case. Let's call the <code>nums</code> array <code>items</code> instead:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747920218696/a85bf516-9bc1-4231-ab39-4a31ce1a8e6d.gif" alt="Animated visualization of backtracking for an array [1, 2, 3], exploring each possible choice." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>For each item in <code>items</code>, we have initially two choices: to include the item, or not to include it.</p>
<p>For each level \(n\) in this <em>decision tree</em>, we have the option to include the next item in <code>items</code>. We have \(2^n\) possible subsets in total.</p>
<p>Let's simplify the example a bit, and say that <code>items</code> is now <code>['a', 'b']</code> (<strong>We'll ignore the problem specifics for now</strong>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747920275978/ab435765-7b05-4939-bd72-55dcfae7a6d4.gif" alt="Animated visualization of backtracking for an array ['a', 'b'], exploring each possible choice." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In this case, we can use backtracking like this:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subsets</span>(<span class="hljs-params">items: <span class="hljs-built_in">string</span>[]</span>) </span>{
  <span class="hljs-keyword">let</span> result: <span class="hljs-built_in">string</span>[][] = [];
  <span class="hljs-keyword">let</span> currentSubset: <span class="hljs-built_in">string</span>[] = [];

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">backtrack</span>(<span class="hljs-params">idx: <span class="hljs-built_in">number</span></span>) </span>{
    <span class="hljs-keyword">if</span> (idx &gt;= items.length) {
      result.push([...currentSubset]);
      <span class="hljs-keyword">return</span>;
    }

    currentSubset.push(items[idx]);
    backtrack(idx + <span class="hljs-number">1</span>);

    currentSubset.pop();
    backtrack(idx + <span class="hljs-number">1</span>);
  }

  backtrack(<span class="hljs-number">0</span>);

  <span class="hljs-keyword">return</span> result;
}

<span class="hljs-built_in">console</span>.log(subsets([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>]));
<span class="hljs-comment">// -&gt; [['a', 'b'], ['a'], ['b'], []]</span>
</code></pre>
<p>Well, it looks simple at first glance, but what's going on?</p>
<p>One thing to notice is that we <code>pop</code> from the <code>currentSubset</code>, then call <code>backtrack</code>. In our example of walking, that's the part we go back to our previous point, and continue our walk.</p>
<p>In the first animation, we indicated a dead end with a cross mark, and in this case, a dead end is the <strong>base case</strong> we reach.</p>
<p>It might still be tough to understand, so let's add some helpful <code>console.log</code>s, and see the output:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subsets</span>(<span class="hljs-params">items: <span class="hljs-built_in">string</span>[]</span>) </span>{
  <span class="hljs-keyword">let</span> result: <span class="hljs-built_in">string</span>[][] = [];
  <span class="hljs-keyword">let</span> currentSubset: <span class="hljs-built_in">string</span>[] = [];

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">backtrack</span>(<span class="hljs-params">idx: <span class="hljs-built_in">number</span></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`======= this is backtrack(<span class="hljs-subst">${<span class="hljs-built_in">arguments</span>[<span class="hljs-number">0</span>]}</span>) =======`</span>)
    <span class="hljs-keyword">if</span> (idx &gt;= items.length) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`idx is <span class="hljs-subst">${idx}</span>, currentSubset is [<span class="hljs-subst">${currentSubset}</span>], adding it to result...`</span>);
      result.push([...currentSubset]);
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`backtrack(<span class="hljs-subst">${<span class="hljs-built_in">arguments</span>[<span class="hljs-number">0</span>]}</span>) is returning...\n`</span>)
      <span class="hljs-keyword">return</span>;
    }

    currentSubset.push(items[idx]);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`added <span class="hljs-subst">${items[idx]}</span> to currentSubset, inside backtrack(<span class="hljs-subst">${<span class="hljs-built_in">arguments</span>[<span class="hljs-number">0</span>]}</span>)`</span>);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`calling backtrack(<span class="hljs-subst">${idx + <span class="hljs-number">1</span>}</span>)...`</span>)
    backtrack(idx + <span class="hljs-number">1</span>);

    <span class="hljs-keyword">let</span> item = currentSubset.pop();
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`popped <span class="hljs-subst">${item}</span> from currentSubset, inside backtrack(<span class="hljs-subst">${<span class="hljs-built_in">arguments</span>[<span class="hljs-number">0</span>]}</span>)`</span>);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`calling backtrack(<span class="hljs-subst">${idx + <span class="hljs-number">1</span>}</span>)...`</span>)
    backtrack(idx + <span class="hljs-number">1</span>);

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`******* done with backtrack(<span class="hljs-subst">${<span class="hljs-built_in">arguments</span>[<span class="hljs-number">0</span>]}</span>) *******\n`</span>);
  }

  backtrack(<span class="hljs-number">0</span>);

  <span class="hljs-keyword">return</span> result;
}

<span class="hljs-built_in">console</span>.log(subsets([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>]));
</code></pre>
<p>The output looks like this:</p>
<pre><code class="lang-plaintext">======= this is backtrack(0) =======
added a to currentSubset, inside backtrack(0)
calling backtrack(1)...
======= this is backtrack(1) =======
added b to currentSubset, inside backtrack(1)
calling backtrack(2)...
======= this is backtrack(2) =======
idx is 2, currentSubset is [a,b], adding it to result...
backtrack(2) is returning...

popped b from currentSubset, inside backtrack(1)
calling backtrack(2)...
======= this is backtrack(2) =======
idx is 2, currentSubset is [a], adding it to result...
backtrack(2) is returning...

******* done with backtrack(1) *******

popped a from currentSubset, inside backtrack(0)
calling backtrack(1)...
======= this is backtrack(1) =======
added b to currentSubset, inside backtrack(1)
calling backtrack(2)...
======= this is backtrack(2) =======
idx is 2, currentSubset is [b], adding it to result...
backtrack(2) is returning...

popped b from currentSubset, inside backtrack(1)
calling backtrack(2)...
======= this is backtrack(2) =======
idx is 2, currentSubset is [], adding it to result...
backtrack(2) is returning...

******* done with backtrack(1) *******

******* done with backtrack(0) *******

[ [ 'a', 'b' ], [ 'a' ], [ 'b' ], [] ]
</code></pre>
<p>If you noticed, <em>Add</em> <code>'a'</code>? and <em>Go ahead?</em> arrows on the first level are calls to <code>backtrack(0)</code>.</p>
<p><em>Add</em> <code>'b'</code>? and <em>Go ahead?</em> arrows on the second level are calls to <code>backtrack(1)</code>.</p>
<p><code>backtrack(2)</code> calls are when we reach the "dead ends". In those cases, we add <code>currentSubset</code> to the <code>result</code>. We always reach the base case in a <code>backtrack(2)</code> call because it's only when the <code>idx</code> equals <code>items.length</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747920409397/0c18c7a6-1776-415b-8918-a6cafe6ba70c.gif" alt="Animated visualization of backtrack function for the array ['a', 'b']." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><strong>Note:</strong> We modified the function in the above examples to work with strings, but in the actual solution we'll only deal with numbers, so in TypeScript, <code>result</code> and <code>currentSubset</code> will look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">let</span> result: <span class="hljs-built_in">number</span>[][] = [];
<span class="hljs-keyword">let</span> currentSubset: <span class="hljs-built_in">number</span>[] = [];
</code></pre>
<p>Also, the function parameter and return types are different:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subsets</span>(<span class="hljs-params">nums: <span class="hljs-built_in">number</span>[]</span>): <span class="hljs-title">number</span>[][] </span>{ ... }
</code></pre>
<p>Otherwise, everything stays the same.</p>
<h4 id="heading-time-and-space-complexity-11">Time and space complexity</h4>
<p>A subset is, in the worst case, length \(n\) which is the length of our input. We'll have \(2^n\) subsets and since we also use a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">spread operator</a> in our example to copy <code>currentSubset</code>, the time complexity will be \(O(n \cdot 2^n)\). The space complexity is – <em>I think</em> – \(O(n \cdot 2^n)\) as well because of the recursive call stack (which is of depth <code>n</code>), and the space needed for <code>result</code> (which is in the worst case \(2^n\)).</p>
<h2 id="heading-chapter-ten-tries">Chapter Ten: Tries</h2>
<p>The trie data structure <a target="_blank" href="https://en.wikipedia.org/wiki/Trie#History,_etymology,_and_pronunciation">gets its name from the word <em>re<strong><strong>trie</strong></strong>val</em></a> – and it's usually pronounced as "try," so that we don't get confused with another familiar and friendly data structure, "tree."</p>
<p>But a trie is still a tree (or tree-like) data structure whose nodes usually store individual letters. So, by traversing the nodes in a trie, we can retrieve strings.</p>
<p>Tries are useful for applications such as autocompletion and spellchecking – and the larger our trie is, the less work we have to do for inserting a new value.</p>
<p><strong>Note:</strong> Using arrays is not very memory-efficient, but for now, we'll stick to the array implementation.</p>
<p>First, let's see what a trie looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747920685051/e152eedd-75c6-478b-8291-5510b8f1421c.gif" alt="Animated visualization of a trie having the values &quot;sea&quot; and &quot;see&quot;" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In this trie, we can retrieve the strings "sea" and "see" – but not "sew", for example.</p>
<p>There is a lot going on, but we can try to understand it piece by piece.</p>
<p>Let's look at a trie node.</p>
<p>We'll create a <code>TrieNode</code> class that has <code>children</code>, which is an array of length 26 (so that each index corresponds to a letter in the English alphabet), and a flag variable <code>isEndOfWord</code> to indicate whether that node represents the last character of a word:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> TrieNode {
  children: (TrieNode | <span class="hljs-literal">null</span>)[];
  isEndOfWord: <span class="hljs-built_in">boolean</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.children = <span class="hljs-built_in">Array</span>.from({ length: <span class="hljs-number">26</span> }, <span class="hljs-function">() =&gt;</span> <span class="hljs-literal">null</span>);
    <span class="hljs-built_in">this</span>.isEndOfWord = <span class="hljs-literal">false</span>;
  }
}
</code></pre>
<p>We're initializing <code>children</code> with <code>null</code> values. As we add a character to our trie, the index that corresponds to that character will be filled.</p>
<p><strong>Note:</strong> We're not storing the actual character itself in this implementation – it's implicit in the usage of indices.</p>
<p>In a trie, we start with an empty root node.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Trie {
  root: TrieNode;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.root = <span class="hljs-keyword">new</span> TrieNode();
  }
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>To insert a word, we're going to loop through each character, and initialize a new <code>TrieNode</code> to the corresponding index.</p>
<pre><code class="lang-typescript">insert(word: <span class="hljs-built_in">string</span>) {
  <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.root;
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> char <span class="hljs-keyword">of</span> word) {
    <span class="hljs-keyword">let</span> idx = char.charCodeAt(<span class="hljs-number">0</span>) - <span class="hljs-string">'a'</span>.charCodeAt(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">if</span> (currentNode.children[idx] === <span class="hljs-literal">null</span>) {
      currentNode.children[idx] = <span class="hljs-keyword">new</span> TrieNode();
    }
    currentNode = currentNode.children[idx];
  }

  currentNode.isEndOfWord = <span class="hljs-literal">true</span>;
}
</code></pre>
<p>Once we reach the node that indicates the last character of the word we inserted, we also mark the <code>isEndOfWord</code> variable as <code>true</code>.</p>
<p><strong>Note:</strong> <code>word</code> is going to be lowercase in these examples – otherwise, we have to convert it, such as:</p>
<pre><code class="lang-typescript">word = word.toLowerCase();
</code></pre>
<p>For searching a word's existence in the trie, we'll do a similar thing. We'll look at the nodes for each character, and if we reach the last one that has <code>isEndOfWord</code> marked as <code>true</code>. That means we've found the word:</p>
<pre><code class="lang-typescript">search(word: <span class="hljs-built_in">string</span>) {
  <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.root;
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> char <span class="hljs-keyword">of</span> word) {
    <span class="hljs-keyword">let</span> idx = char.charCodeAt(<span class="hljs-number">0</span>) - <span class="hljs-string">'a'</span>.charCodeAt(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">if</span> (currentNode.children[idx] === <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }      
    currentNode = currentNode.children[idx];
  }

  <span class="hljs-keyword">return</span> currentNode.isEndOfWord;
}
</code></pre>
<p><strong>Note:</strong> If we find the word we're looking for, then it's called a <strong>search hit</strong>. Otherwise, we have a <strong>search miss</strong> and the word doesn't exist in our trie.</p>
<p>Removing a word is a bit more challenging. Let's say that we want to remove the word "see." But, there is also another word "sea," with the same prefix ('s' and 'e'). So, we should remove only the nodes that we're allowed to.</p>
<p>For this reason, we'll define a recursive function. Once we reach the last character of the word we want to remove, we'll back up and remove the characters we can remove:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> removeRecursively = <span class="hljs-function">(<span class="hljs-params">node: TrieNode | <span class="hljs-literal">null</span>, word: <span class="hljs-built_in">string</span>, depth: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (node === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }

  <span class="hljs-keyword">if</span> (depth === word.length) {
    <span class="hljs-keyword">if</span> (node.isEndOfWord) {
      node.isEndOfWord = <span class="hljs-literal">false</span>;
    }
    <span class="hljs-keyword">if</span> (node.children.every(<span class="hljs-function"><span class="hljs-params">child</span> =&gt;</span> child === <span class="hljs-literal">null</span>)) {
      node = <span class="hljs-literal">null</span>;
    }

    <span class="hljs-keyword">return</span> node;
  }

  <span class="hljs-keyword">let</span> idx = word[depth].charCodeAt(<span class="hljs-number">0</span>) - <span class="hljs-string">'a'</span>.charCodeAt(<span class="hljs-number">0</span>);
  node.children[idx] = removeRecursively(node.children[idx], word, depth + <span class="hljs-number">1</span>);

  <span class="hljs-keyword">if</span> (node.children.every(<span class="hljs-function"><span class="hljs-params">child</span> =&gt;</span> child === <span class="hljs-literal">null</span>) &amp;&amp; !node.isEndOfWord) {
    node = <span class="hljs-literal">null</span>;
  }

  <span class="hljs-keyword">return</span> node;
}
</code></pre>
<p><code>depth</code> indicates the index of the word, or <em>the depth of the trie we reach</em>.</p>
<p>Once <code>depth</code> is equal to the word's length (one past the last character), we check if it's the end of the word. If that's the case, we'll mark it as <code>false</code> now, because that word won't exist from here on. Then, we can only mark the node as <code>null</code> if it doesn't have any children (in other words, if all of them are <code>null</code>). We'll apply this logic to each child node recursively until the word is removed as far as it can be removed.</p>
<p>Here is the final example implementation of a trie:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> TrieNode {
  children: (TrieNode | <span class="hljs-literal">null</span>)[];
  isEndOfWord: <span class="hljs-built_in">boolean</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.children = <span class="hljs-built_in">Array</span>.from({ length: <span class="hljs-number">26</span> }, <span class="hljs-function">() =&gt;</span> <span class="hljs-literal">null</span>);
    <span class="hljs-built_in">this</span>.isEndOfWord = <span class="hljs-literal">false</span>;
  }
}

<span class="hljs-keyword">class</span> Trie {
  root: TrieNode;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.root = <span class="hljs-keyword">new</span> TrieNode();
  }

  insert(word: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.root;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> char <span class="hljs-keyword">of</span> word) {
      <span class="hljs-keyword">let</span> idx = char.charCodeAt(<span class="hljs-number">0</span>) - <span class="hljs-string">'a'</span>.charCodeAt(<span class="hljs-number">0</span>);
      <span class="hljs-keyword">if</span> (currentNode.children[idx] === <span class="hljs-literal">null</span>) {
        currentNode.children[idx] = <span class="hljs-keyword">new</span> TrieNode();
      }
      currentNode = currentNode.children[idx];
    }

    currentNode.isEndOfWord = <span class="hljs-literal">true</span>;
  }

  search(word: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">let</span> currentNode = <span class="hljs-built_in">this</span>.root;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> char <span class="hljs-keyword">of</span> word) {
      <span class="hljs-keyword">let</span> idx = char.charCodeAt(<span class="hljs-number">0</span>) - <span class="hljs-string">'a'</span>.charCodeAt(<span class="hljs-number">0</span>);
      <span class="hljs-keyword">if</span> (currentNode.children[idx] === <span class="hljs-literal">null</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
      }      
      currentNode = currentNode.children[idx];
    }

    <span class="hljs-keyword">return</span> currentNode.isEndOfWord;
  }

  remove(word: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">const</span> removeRecursively = <span class="hljs-function">(<span class="hljs-params">node: TrieNode | <span class="hljs-literal">null</span>, word: <span class="hljs-built_in">string</span>, depth: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (node === <span class="hljs-literal">null</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
      }

      <span class="hljs-keyword">if</span> (depth === word.length) {
        <span class="hljs-keyword">if</span> (node.isEndOfWord) {
          node.isEndOfWord = <span class="hljs-literal">false</span>;
        }
        <span class="hljs-keyword">if</span> (node.children.every(<span class="hljs-function"><span class="hljs-params">child</span> =&gt;</span> child === <span class="hljs-literal">null</span>)) {
          node = <span class="hljs-literal">null</span>;
        }

        <span class="hljs-keyword">return</span> node;
      }

      <span class="hljs-keyword">let</span> idx = word[depth].charCodeAt(<span class="hljs-number">0</span>) - <span class="hljs-string">'a'</span>.charCodeAt(<span class="hljs-number">0</span>);
      node.children[idx] = removeRecursively(node.children[idx], word, depth + <span class="hljs-number">1</span>);

      <span class="hljs-keyword">if</span> (node.children.every(<span class="hljs-function"><span class="hljs-params">child</span> =&gt;</span> child === <span class="hljs-literal">null</span>) &amp;&amp; !node.isEndOfWord) {
        node = <span class="hljs-literal">null</span>;
      }

      <span class="hljs-keyword">return</span> node;
    }

    removeRecursively(<span class="hljs-built_in">this</span>.root, word, <span class="hljs-number">0</span>);
  }
}

<span class="hljs-keyword">let</span> t = <span class="hljs-keyword">new</span> Trie();

t.insert(<span class="hljs-string">'sea'</span>);
t.insert(<span class="hljs-string">'see'</span>);

<span class="hljs-built_in">console</span>.log(t.search(<span class="hljs-string">'sea'</span>)); <span class="hljs-comment">// true</span>
<span class="hljs-built_in">console</span>.log(t.search(<span class="hljs-string">'see'</span>)); <span class="hljs-comment">// true</span>

<span class="hljs-built_in">console</span>.log(t.search(<span class="hljs-string">'hey'</span>)); <span class="hljs-comment">// false</span>
<span class="hljs-built_in">console</span>.log(t.search(<span class="hljs-string">'sew'</span>)); <span class="hljs-comment">// false</span>

t.remove(<span class="hljs-string">'see'</span>);

<span class="hljs-built_in">console</span>.log(t.search(<span class="hljs-string">'see'</span>)); <span class="hljs-comment">// false </span>
<span class="hljs-built_in">console</span>.log(t.search(<span class="hljs-string">'sea'</span>)); <span class="hljs-comment">// true</span>
</code></pre>
<h4 id="heading-time-and-space-complexity-12">Time and space complexity</h4>
<p>The time complexity of creating a trie is going to be \(O(m * n)\) where \(m\) is the longest word and \(n\) is the total number of words. Inserting, searching, and deleting a word is \(O(a * n)\) where \(a\) is the length of the word and \(n\) is the total number of words.</p>
<p>When it comes to space complexity, in the worst case, each node can have children for all the characters in the alphabet we're representing. But, the size of the alphabet is constant, so the growth of storage needs will be proportionate to the number of nodes we have, which is \(O(n)\) where \(n\) is the number of nodes.</p>
<h2 id="heading-chapter-eleven-graphs">Chapter Eleven: Graphs</h2>
<p>A graph is probably <em>the</em> data structure that everyone is familiar with, regardless of their profession or interests.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Graph_theory#Representation">Graph theory</a> is a very broad topic, but we'll simply look at some of the main ingredients of what makes a graph and how to represent it, as well as basic graph traversals.</p>
<p>In a graph, there are two main components: vertices (or nodes) and edges that connect those vertices.</p>
<p><strong>Note:</strong> Here, we're going to use "vertex" and "node" interchangeably. The terms "adjacent vertices" and "neighbors" are used interchangeably as well.</p>
<p>A graph can be <strong>directed</strong> or <strong>undirected</strong>. With a directed edge, we have an origin and a destination vertex. On the other hand, an undirected edge is bidirectional, origin and destination are not fixed.</p>
<p><strong>Note:</strong> There might also be <a target="_blank" href="https://en.wikipedia.org/wiki/Graph_\(discrete_mathematics\)#Mixed_graph">mixed graphs</a> that have both directed and undirected edges.</p>
<p>A graph can also be weighted or unweighted, each edge can have different weights, usually representing the cost of going from one vertex to the other.</p>
<p>We can define a graph like this:</p>
<p>$$G = (V, \ E)$$</p><p>\(V\) is a set of vertices, and \(E\) is a set of edges.</p>
<p>For example, if we have a directed graph like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747921387268/fe3d0ef9-a271-4c87-9143-84e80e41af5f.gif" alt="Animated visualization of a graph with nodes A, B, C, D. A has directed edges to B and C, C has one to B and D. D has one to C. " class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Then, we have the vertices:</p>
<p>$$V = \{A, \ B, \ C, \ D\}$$</p><p>And, the edges are:</p>
<p>$$E = \{(A, \ B), \ (A, \ C), \ (C, \ B), \ (C, \ D)\, \ (D, \ C)\}$$</p><p>If we have an undirected graph such as this one:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747921458245/d2920859-81c0-4082-a49d-e41b08b81124.gif" alt="Animated visualization of the same graph above with undirected edges." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>We have the same vertices:</p>
<p>$$V = \{A, \ B, \ C, \ D\}$$</p><p>But our edges can look like this:</p>
<p>$$E = \{\{B, \ A\}, \{A, \ C\}, \{C, \ B\}, \{D, \ C\}\}$$</p><p><strong>Note:</strong> We use parentheses when it comes to directed edges, but curly braces with undirected edges as there is no direction from one vertex to the other.</p>
<p>When two vertices share an edge, they are <strong>adjacent</strong> to each other. The <strong>degree</strong> of a vertex is the number of adjacent vertices to it. We can also define the degree as the number of edges coming out of the vertex. For example, in the above image, the vertex A has a degree of 2.</p>
<p>A <strong>simple path</strong> is the one that we don't repeat any vertices while traversing the graph.</p>
<p>An example might look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747921569022/50f556e7-f954-4203-8c4b-493b2be5a353.gif" alt="Animated visualization of the same graph above with highlighted nodes in this order: A, B, C, D." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>A <strong>cycle</strong> is a simple path, except that we end up at the vertex we started with:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747921594728/717408ba-f9fb-4cef-9b52-70f487e9162d.gif" alt="Animated visualization of the same graph above with highlighted nodes in this order: A, B, C (with all the edges also highlighted)." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-representing-graphs">Representing graphs</h3>
<p>When it comes to representing graphs, there are several ways to do it, and we'll look at three of them: an edge list, an adjacency matrix, and an adjacency list.</p>
<h4 id="heading-edge-list">Edge List</h4>
<p>We can simply put all the edges in an array:</p>
<pre><code class="lang-plaintext">[ [A, B], [A, C], [B, C], [C, D] ]
</code></pre>
<p>But to find an edge in an edge list, we'll have to iterate through them, so it will have \(O(E)\) time complexity, where in the worst case, we'll search the whole list to find an edge. Similarly, it needs \(O(E)\) amount of space to represent all the edges.</p>
<h4 id="heading-adjacency-matrix">Adjacency Matrix</h4>
<p>The adjacency matrix for our example might look like this:</p>
<p>$$\left\lceil\begin{matrix}&amp; A &amp; B &amp; C &amp; D \\A &amp; 0 &amp; 1 &amp; 1 &amp; 0 \\B &amp; 1 &amp; 0 &amp; 1 &amp; 0 \\C &amp; 1 &amp; 1 &amp; 0 &amp; 1 \\D &amp; 0 &amp; 0 &amp; 1 &amp; 0\end{matrix}\right\rceil$$</p><p>Each row is for a vertex, and the matching column shows the relationship between those vertices. For example, the vertex A doesn't have an edge pointing to D, so the cell that matches A and D is 0. On the other hand, A is connected to B and C, so those cells have the value 1.</p>
<p><strong>Note:</strong> If the graph is weighted, we can simply put the weight instead of <code>1</code>, and when there is no edge, the value can stay <code>0</code>.</p>
<p><em>An adjacency matrix will have 0s in the "main diagonal," showing that there are no self-loops.</em></p>
<p>Let's try implementing it in TypeScript.</p>
<p>We'll start with a minimal graph vertex:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> GraphVertex {
  value: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">value: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span></span>) {
    <span class="hljs-built_in">this</span>.value = value;
  }
}
</code></pre>
<p>Now we can define our graph. We'll make it really simple with three properties to hold: <code>matrix</code> to represent the graph as an adjacency matrix, <code>vertices</code> to hold vertices, and <code>isDirected</code> to indicate whether our graph is directed:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Graph {
  matrix: <span class="hljs-built_in">number</span>[][];
  vertices: GraphVertex[];
  isDirected: <span class="hljs-built_in">boolean</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">vertices: GraphVertex[], isDirected = <span class="hljs-literal">true</span></span>) {
    <span class="hljs-built_in">this</span>.vertices = vertices;
    <span class="hljs-built_in">this</span>.isDirected = isDirected;
    <span class="hljs-comment">// ...</span>
  }

  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Initializing our adjacency matrix might look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.matrix = <span class="hljs-built_in">Array</span>.from({ length: vertices.length }, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.from({ length: vertices.length }, <span class="hljs-function">() =&gt;</span> <span class="hljs-number">0</span>)
});
</code></pre>
<p>We'll have an array with the length of vertices. Each item in the array is an array with the length of vertices as well, but filled with zeroes.</p>
<p>In our example with four vertices, the initial adjacency matrix looks like this:</p>
<pre><code class="lang-typescript">[ [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>], [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>], [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>], [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>] ]
</code></pre>
<p>Then, adding an edge is just marking the corresponding value as <code>1</code>, so that we can represent a connection between two vertices:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v1)][<span class="hljs-built_in">this</span>.vertices.indexOf(v2)] = <span class="hljs-number">1</span>;
</code></pre>
<p><strong>Note:</strong> This implementation assumes that all vertices are distinct.</p>
<p>If we have an undirected graph, we can have it both ways:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isDirected) {
  <span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v2)][<span class="hljs-built_in">this</span>.vertices.indexOf(v1)] = <span class="hljs-number">1</span>;
}
</code></pre>
<p>Removing an edge, in this case, will be just resetting the value to <code>0</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v1)][<span class="hljs-built_in">this</span>.vertices.indexOf(v2)] = <span class="hljs-number">0</span>;
</code></pre>
<p>And, checking for the existence of an edge is simply checking whether the corresponding value is <code>0</code> or not:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v1)][<span class="hljs-built_in">this</span>.vertices.indexOf(v2)] !== <span class="hljs-number">0</span>;
</code></pre>
<p>And, here is the whole example with additional methods for adding and removing an edge, checking if there is an edge between two vertices, and checking if a specific vertex is in the graph:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Graph {
  matrix: <span class="hljs-built_in">number</span>[][];
  vertices: GraphVertex[];
  isDirected: <span class="hljs-built_in">boolean</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">vertices: GraphVertex[], isDirected = <span class="hljs-literal">true</span></span>) {
    <span class="hljs-built_in">this</span>.vertices = vertices;
    <span class="hljs-built_in">this</span>.matrix = <span class="hljs-built_in">Array</span>.from({ length: vertices.length }, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.from({ length: vertices.length }, <span class="hljs-function">() =&gt;</span> <span class="hljs-number">0</span>)
    });
    <span class="hljs-built_in">this</span>.isDirected = isDirected;
  }

  addEdge(v1: GraphVertex, v2: GraphVertex) {
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v1);
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v2);

    <span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v1)][<span class="hljs-built_in">this</span>.vertices.indexOf(v2)] = <span class="hljs-number">1</span>;

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isDirected) {
      <span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v2)][<span class="hljs-built_in">this</span>.vertices.indexOf(v1)] = <span class="hljs-number">1</span>;
    }
  }

  <span class="hljs-comment">/* 
  For a weighted graph:

  addEdge(v1: GraphVertex, v2: GraphVertex, weight: number) {
    this._checkVertexIsInGraph(v1);
    this._checkVertexIsInGraph(v2);

    this.matrix[this.vertices.indexOf(v1)][this.vertices.indexOf(v2)] = weight;
    if (!this.isDirected) {
      this.matrix[this.vertices.indexOf(v2)][this.vertices.indexOf(v1)] = weight;
    }
  }
  */</span>

  removeEdge(v1: GraphVertex, v2: GraphVertex) {
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v1);
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v2);

    <span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v1)][<span class="hljs-built_in">this</span>.vertices.indexOf(v2)] = <span class="hljs-number">0</span>;

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isDirected) {
      <span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v2)][<span class="hljs-built_in">this</span>.vertices.indexOf(v1)] = <span class="hljs-number">0</span>;
    }
  }

  hasEdge(v1: GraphVertex, v2: GraphVertex) {
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v1);
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v2);

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.matrix[<span class="hljs-built_in">this</span>.vertices.indexOf(v1)][<span class="hljs-built_in">this</span>.vertices.indexOf(v2)] !== <span class="hljs-number">0</span>;
  }

  getAdjacencyMatrix() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.matrix;
  }

  _checkVertexIsInGraph(v: GraphVertex) {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.vertices.includes(v)) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Vertex doesn\'t exist'</span>);
    }
  }
}


<span class="hljs-keyword">let</span> a = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'A'</span>);
<span class="hljs-keyword">let</span> b = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'B'</span>);
<span class="hljs-keyword">let</span> c = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'C'</span>);
<span class="hljs-keyword">let</span> d = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'D'</span>);

<span class="hljs-keyword">let</span> graph = <span class="hljs-keyword">new</span> Graph([a, b, c, d], <span class="hljs-literal">false</span>);

graph.addEdge(a, b);
graph.addEdge(a, c);
graph.addEdge(b, c);
graph.addEdge(c, d);

<span class="hljs-built_in">console</span>.log(graph.getAdjacencyMatrix());
<span class="hljs-comment">// -&gt; [ [0, 1, 1, 0], [1, 0, 1, 0], [1, 1, 0, 1], [0, 0, 1, 0] ]</span>
</code></pre>
<p>Operations on an adjacency matrix have \(O(1)\) time complexity. But our storage needs will be \(O(V^2)\) where \(V\) is the number of vertices.</p>
<h4 id="heading-adjacency-list">Adjacency List</h4>
<p>In an adjacency list, usually a hashmap <strong>or</strong> an array of linked lists is used. For example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">let</span> graph = {
  <span class="hljs-string">'A'</span>: [<span class="hljs-string">'B'</span>, <span class="hljs-string">'C'</span>],
  <span class="hljs-string">'B'</span>: [<span class="hljs-string">'A'</span>, <span class="hljs-string">'C'</span>],
  <span class="hljs-string">'C'</span>: [<span class="hljs-string">'A'</span>, <span class="hljs-string">'B'</span>, <span class="hljs-string">'D'</span>],
  <span class="hljs-string">'D'</span>: [<span class="hljs-string">'C'</span>]
}
</code></pre>
<p>Let's see how we can modify our code above to use an adjacency list instead.</p>
<p>Instead of having a <code>matrix</code> which is an array of arrays, we can have a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"><code>Map</code></a> that maps the vertices to an array of their neighbors.</p>
<p>We can initialize it as a map that has the vertices as keys, each of which has a value of an empty array for now:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.list = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;GraphVertex, GraphVertex[]&gt;();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> v <span class="hljs-keyword">of</span> vertices) {
  <span class="hljs-built_in">this</span>.list.set(v, []);
}
</code></pre>
<p>Adding an edge will be just pushing to the array of corresponding vertex:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.list.get(v1)!.push(v2);
</code></pre>
<p>If our graph is undirected, we can do it both ways here as well:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isDirected) {
  <span class="hljs-built_in">this</span>.list.get(v2)!.push(v1);
}
</code></pre>
<p>Removing an edge will be deleting that vertex from the array:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.list.set(v1, <span class="hljs-built_in">this</span>.list.get(v1)!.filter(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> v !== v2));
</code></pre>
<p>Checking if an edge exists is just checking the existence of that vertex in the array:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.list.get(v1)!.includes(v2);
</code></pre>
<p><strong>Note:</strong> We're using a <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator">non-null assertion operator</a> as we’re using TypeScript in these examples. As we'll see below, we first check if the vertex is in the graph. And since we're adding all the vertices in the graph as keys to <code>this.list</code>, we're sure that getting that vertex from the list is not <code>undefined</code>. But TypeScript will warn us because if a key is not found in a <code>Map</code> object, it could <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get#return_value">potentially return <code>undefined</code></a>.</p>
<p>Here is our graph:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Graph {
  list: <span class="hljs-built_in">Map</span>&lt;GraphVertex, GraphVertex[]&gt;;
  vertices: GraphVertex[];
  isDirected: <span class="hljs-built_in">boolean</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">vertices: GraphVertex[], isDirected = <span class="hljs-literal">true</span></span>) {
    <span class="hljs-built_in">this</span>.vertices = vertices;
    <span class="hljs-built_in">this</span>.list = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> v <span class="hljs-keyword">of</span> vertices) {
      <span class="hljs-built_in">this</span>.list.set(v, []);
    }
    <span class="hljs-built_in">this</span>.isDirected = isDirected;
  }

  addEdge(v1: GraphVertex, v2: GraphVertex) {
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v1);
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v2);

    <span class="hljs-built_in">this</span>.list.get(v1)!.push(v2);

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isDirected) {
      <span class="hljs-built_in">this</span>.list.get(v2)!.push(v1);
    }
  }

  removeEdge(v1: GraphVertex, v2: GraphVertex) {
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v1);
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v2);

    <span class="hljs-built_in">this</span>.list.set(v1, <span class="hljs-built_in">this</span>.list.get(v1)!.filter(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> v !== v2));

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isDirected) {
      <span class="hljs-built_in">this</span>.list.set(v2, <span class="hljs-built_in">this</span>.list.get(v2)!.filter(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> v !== v1));
    }
  }

  hasEdge(v1: GraphVertex, v2: GraphVertex) {
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v1);
    <span class="hljs-built_in">this</span>._checkVertexIsInGraph(v2);

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.list.get(v1)!.includes(v2);
  }

  getAdjacencyList() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.list;
  }

  _checkVertexIsInGraph(v: GraphVertex) {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.vertices.includes(v)) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Vertex doesn\'t exist'</span>);
    }
  }
}


<span class="hljs-keyword">let</span> a = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'A'</span>);
<span class="hljs-keyword">let</span> b = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'B'</span>);
<span class="hljs-keyword">let</span> c = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'C'</span>);
<span class="hljs-keyword">let</span> d = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'D'</span>);

<span class="hljs-keyword">let</span> graph = <span class="hljs-keyword">new</span> Graph([a, b, c, d], <span class="hljs-literal">false</span>);

graph.addEdge(a, b);
graph.addEdge(a, c);
graph.addEdge(b, c);
graph.addEdge(c, d);

<span class="hljs-built_in">console</span>.log(graph.getAdjacencyList());

<span class="hljs-comment">/* Output:

Map (4) {
  GraphVertex: { "value": "A" } =&gt; [
    GraphVertex: { "value": "B" }, 
    GraphVertex: { "value": "C" }
  ], 
  GraphVertex: { "value": "B" } =&gt; [
    GraphVertex: { "value": "A" }, 
    GraphVertex: { "value": "C" }
  ], 
  GraphVertex: { "value": "C" } =&gt; [
    GraphVertex: { "value": "A" }, 
    GraphVertex: { "value": "B" }, 
    GraphVertex: { "value": "D" }
  ], 
  GraphVertex: { "value": "D" } =&gt; [
    GraphVertex: { "value": "C" }
  ]
} 

*/</span>
</code></pre>
<p>Getting the neighbors of a vertex is \(O(1)\) because we're just looking up a key in a map. But finding a particular edge can be \(O(d)\) where \(d\) is the number of degrees of the vertex, because we might need to traverse all the neighbors to find it. And, it could be \(V - 1\) where \(V\) is the number of vertices in the graph. It's the case when that vertex has all the other vertices as its neighbors.</p>
<p>The space complexity can be \(O(V + E)\) where \(V\) is the number of vertices and \(E\) is the number of edges.</p>
<h3 id="heading-traversals-1">Traversals</h3>
<p>Continuing with the adjacency list representation, let's now take a look at two (very familiar) ways to traverse a graph: breadth-first search and depth-first search.</p>
<p>But first, we'll modify our graph a little bit. We'll add a new vertex <code>'E'</code> and update some edges:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">let</span> a = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'A'</span>);
<span class="hljs-keyword">let</span> b = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'B'</span>);
<span class="hljs-keyword">let</span> c = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'C'</span>);
<span class="hljs-keyword">let</span> d = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'D'</span>);
<span class="hljs-keyword">let</span> e = <span class="hljs-keyword">new</span> GraphVertex(<span class="hljs-string">'E'</span>);


<span class="hljs-keyword">let</span> graph = <span class="hljs-keyword">new</span> Graph([a, b, c, d, e], <span class="hljs-literal">false</span>);

graph.addEdge(a, b);
graph.addEdge(a, c);
graph.addEdge(b, d);
graph.addEdge(c, e);
</code></pre>
<p>The important idea to remember is that there is no hierarchy of vertices, so we don't have a root node.</p>
<p>For a breadth-first or depth-first search, we can use an arbitrary node as a starting point.</p>
<h4 id="heading-breadth-first-search">Breadth-First Search</h4>
<p>With our new graph, a breadth-first search traversal looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747922399341/f4b9f63b-5188-48a2-83ec-51524721c2b1.gif" alt="Animated visualization for a breadth-first search of a graph with nodes A, B, C, D, E with highlighted nodes in this order: A, B, C, D, E." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>When it comes to breadth-first search, usually a queue is used, and the idea is simple: given a current node, we'll add the adjacent nodes first, marking them as visited as we go.</p>
<p>Inside the <code>Graph</code> class, we can implement a <code>bfs</code> method that does just that:</p>
<pre><code class="lang-typescript">bfs(startNode: GraphVertex) {
  <span class="hljs-keyword">const</span> visited = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>();
  <span class="hljs-keyword">const</span> queue = [startNode];
  visited.add(startNode);

  <span class="hljs-keyword">while</span> (queue.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">const</span> currentNode = queue.shift();
    <span class="hljs-comment">// console.log(currentNode);</span>
    <span class="hljs-built_in">this</span>.list.get(currentNode <span class="hljs-keyword">as</span> GraphVertex)!.forEach(<span class="hljs-function">(<span class="hljs-params">node</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (!visited.has(node)) {
        visited.add(node);
        queue.push(node);
      }
    });
  }
}
</code></pre>
<p>If we call the <code>bfs</code> method with <code>a</code> as the starting vertex (<code>graph.bfs(a)</code>), and log <code>currentNode</code> to console each time we go, it's as we expected:</p>
<pre><code class="lang-plaintext">GraphVertex { value: 'A' }
GraphVertex { value: 'B' }
GraphVertex { value: 'C' }
GraphVertex { value: 'D' }
GraphVertex { value: 'E' }
</code></pre>
<p>With the adjacency list, using a BFS has \(O(V + E)\) time complexity (sum of the vertices and edges) as we're traversing the whole graph.</p>
<h4 id="heading-depth-first-search">Depth-First Search</h4>
<p>With the same modified graph, a depth-first search looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747922463260/72e852c1-642f-4ce0-829d-e658a8a7b880.gif" alt="Animated visualization for a depth-first search of a graph with nodes A, B, C, D, E with highlighted nodes in this order: A, B, D, C, E." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>With depth-first search there is usually recursion involved as we're traversing through a path until we have visited all the nodes in that path. Once we hit a dead end, we'll <strong>backtrack</strong> and continue exploring until we have visited all the vertices in the graph.</p>
<p>Again, inside the <code>Graph</code> class, we can add a <code>dfs</code> method:</p>
<pre><code class="lang-typescript">dfs(startNode: GraphVertex, visited = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>()) {
  visited.add(startNode);
  <span class="hljs-comment">// console.log(startNode);</span>
  <span class="hljs-built_in">this</span>.list.get(startNode)!.forEach(<span class="hljs-function">(<span class="hljs-params">node</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (!visited.has(node)) {
      <span class="hljs-built_in">this</span>.dfs(node, visited);
    }
  });
}
</code></pre>
<p>Starting with a node, we check how deep we can go from there. Once we reach a dead end (when the <code>dfs</code> inside <code>forEach</code> returns), we continue checking other neighbors (with <code>forEach</code>) until none is left. We essentially do the same thing until all the vertices are visited.</p>
<p>Logging the output matches our expectation:</p>
<pre><code class="lang-plaintext">GraphVertex { value: 'A' }
GraphVertex { value: 'B' }
GraphVertex { value: 'D' }
GraphVertex { value: 'C' }
GraphVertex { value: 'E' }
</code></pre>
<p>The time complexity for a depth-first search traversal of a graph is the similar to BFS, \(O(V + E)\).</p>
<h2 id="heading-chapter-twelve-dynamic-programming">Chapter Twelve: Dynamic Programming</h2>
<p>Dynamic programming (DP) is one of those concepts that is a bit intimidating when you hear it for the first time. But the crux of it is simply breaking problems down into smaller parts and solving them. It’s also about storing those solutions so that we don't have to compute them again.</p>
<p>Breaking problems down into subproblems is nothing new – that's pretty much what problem-solving is all about. What dynamic programming is also specifically concerned with are <strong>overlapping subproblems</strong> that are repeating – we want to calculate solutions to those subproblems so that we won't be calculating them again each time. Put another way, <em>we want to remember the past so that we won't be condemned to repeat it</em>.</p>
<p>For example, calculating 1 + 1 + 1 + 1 + 1 is very easy if we have already calculated 1 + 1 + 1 + 1. We can just remember the previous solution, and use it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747922611072/bcb33cae-aed3-41bd-8823-4e10a0e13fbf.gif" alt="Animated visualization of the result of 1 + 1 + 1 + 1 constituting a subproblem of 1 + 1 + 1 + 1 + 1 ." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Calculating the Fibonacci sequence is one of the well-known examples when it comes to dynamic programming. Because we have to calculate the same functions each time for a new number, it lends itself to DP very well.</p>
<p>For example, to calculate <code>fib(4)</code> we need to calculate <code>fib(3)</code> and <code>fib(2)</code>. But calculating <code>fib(3)</code> also involves calculating <code>fib(2)</code>, so we'll be doing the same calculation, <em>again</em>.</p>
<p>A classic, good old recursive Fibonacci function might look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fib</span>(<span class="hljs-params">n: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">number</span> </span>{
  <span class="hljs-keyword">if</span> (n === <span class="hljs-number">0</span> || n === <span class="hljs-number">1</span>) {
    <span class="hljs-keyword">return</span> n;
  }

  <span class="hljs-keyword">return</span> fib(n - <span class="hljs-number">1</span>) + fib(n - <span class="hljs-number">2</span>);
}
</code></pre>
<p>Though the issue we have just mentioned remains: we'll keep calculating the same values:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747922659848/577aef57-17b5-40ad-a926-74387b0e3731.gif" alt="Animated visualization displaying repeated fibonacci calls." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>But, we want to do better.</p>
<p><strong>Memoization</strong> is remembering the problems we have solved before so that we don't have to solve them again and waste our time. We can <em>reuse</em> the solution to the subproblem we've already memoized. So, we can keep a <em>cache</em> to store those solutions and use them:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fib</span>(<span class="hljs-params">n: <span class="hljs-built_in">number</span>, cache: <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">number</span>, <span class="hljs-built_in">number</span>&gt;</span>): <span class="hljs-title">number</span> </span>{
  <span class="hljs-keyword">if</span> (cache.has(n)) {
    <span class="hljs-keyword">return</span> cache.get(n)!;
  }

  <span class="hljs-keyword">if</span> (n === <span class="hljs-number">0</span> || n === <span class="hljs-number">1</span>) {
    <span class="hljs-keyword">return</span> n;
  }

  <span class="hljs-keyword">const</span> result = fib(n - <span class="hljs-number">1</span>, cache) + fib(n - <span class="hljs-number">2</span>, cache);
  cache.set(n, result);

  <span class="hljs-keyword">return</span> result;
}
</code></pre>
<p>For example, we can initially pass an empty <code>Map</code> as the argument for <code>cache</code>, and print the first 15 Fibonacci numbers:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">let</span> m = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">number</span>, <span class="hljs-built_in">number</span>&gt;();

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">15</span>; i++) {
  <span class="hljs-built_in">console</span>.log(fib(i, m));
}

<span class="hljs-comment">/*
  0
  1
  1
  2
  3
  5
  8
  13
  21
  34
  55 
  89
  144
  233
  377 
 */</span>
</code></pre>
<p>There are two different approaches with dynamic programming: <strong>top-down</strong> and <strong>bottom-up</strong>.</p>
<p>Top-down is like what it sounds: starting with a large problem, breaking it down to smaller components, memoizing them. It's what we just did with the <code>fib</code> example.</p>
<p>Bottom-up is also like what it sounds: starting with the smallest subproblem, finding out a solution, and working our way up to the larger problem itself. It also has an advantage: with the bottom-up approach, we don't need to store every previous value – we can only keep the two elements at the bottom so that we can use them to build up to our target.</p>
<p>With the bottom-up approach, our <code>fib</code> function can look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fib</span>(<span class="hljs-params">n: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">let</span> dp = [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">2</span>; i &lt;= n; i++) {
    dp[i] = dp[i - <span class="hljs-number">1</span>] + dp[i - <span class="hljs-number">2</span>];
  }

  <span class="hljs-keyword">return</span> dp[n];
}
</code></pre>
<p>Just note that we are keeping an array whose size will grow linearly as the input increases. So, we can do better with constant space complexity, not using an array at all:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fib</span>(<span class="hljs-params">n: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">if</span> (n === <span class="hljs-number">0</span> || n === <span class="hljs-number">1</span>) {
    <span class="hljs-keyword">return</span> n;
  }

  <span class="hljs-keyword">let</span> a = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">let</span> b = <span class="hljs-number">1</span>;

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">2</span>; i &lt;= n; i++) {
    <span class="hljs-keyword">let</span> tmp = a + b;
    a = b;
    b = tmp;
  }

  <span class="hljs-keyword">return</span> b;
}
</code></pre>
<h4 id="heading-time-and-space-complexity-13">Time and space complexity</h4>
<p>The time complexities for both the top-down and bottom-up approaches in the Fibonacci example are \(O(n)\) as we solve each subproblem, each of which is of constant time.</p>
<p><strong>Note:</strong> The time complexity of the recursive Fibonacci function that doesn't use DP is exponential (in fact, \(O(\phi^{n})\) – yes <a target="_blank" href="https://en.wikipedia.org/wiki/Golden_ratio">the golden ratio</a> as its base).</p>
<p>But when it comes to space complexity, the bottom-up approach (the second version) is \(O(1)\).</p>
<p><strong>Note:</strong> The first version we've used for the bottom-up approach has \(O(n)\) time complexity as we store the values in an array.</p>
<p>The top-down approach has \(O(n)\) space complexity because we store a <code>Map</code> whose size will grow linearly as <code>n</code> increases.</p>
<h2 id="heading-chapter-thirteen-intervals">Chapter Thirteen: Intervals</h2>
<p>An interval simply has a start and an end. The easiest way to think about intervals is as time frames.</p>
<p>With intervals, the usual concern is whether they overlap or not.</p>
<p>For example, if we have an interval <code>[1, 3]</code> and another <code>[2, 5]</code>, they are clearly overlapping, so they can be merged together to create a new interval <code>[1, 5]</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747923034510/05767713-f24f-4467-82f5-89e025631be9.gif" alt="Animated visualization with interval [1, 3] merging with [2, 5], becoming [1, 5]." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In order for two intervals <strong>not to overlap</strong>:</p>
<ul>
<li>the <em>start</em> of one should be <em>strictly larger</em> than the <em>end</em> of the other</li>
</ul>
<pre><code class="lang-plaintext">newInterval[0] &gt; interval[1]
</code></pre>
<p>Or:</p>
<ul>
<li>the <em>end</em> of the one should be <em>strictly smaller</em> than the <em>start</em> of the other</li>
</ul>
<pre><code class="lang-plaintext">newInterval[1] &lt; interval[0]
</code></pre>
<p>If both of these are false, they are overlapping.</p>
<p>If they are overlapping, the new (merged) interval will have the minimum value from both intervals as its start, and the maximum as its end:</p>
<pre><code class="lang-plaintext">[
  min(newInterval[0], interval[0]),
  max(newInterval[1], interval[1])
]
</code></pre>
<h2 id="heading-chapter-fourteen-bit-manipulation">Chapter Fourteen: Bit Manipulation</h2>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Bitwise_operation">A bitwise operation</a> operates on a bit string, a bit array, or a binary numeral (considered as a bit string) at the level of its individual bits.</p>
<p>Let's first represent a number in binary (base 2). We can use <code>toString</code> method on a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number">number</a>, and specify the <strong>radix</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> n = <span class="hljs-number">17</span>;

<span class="hljs-built_in">console</span>.log(n.toString(<span class="hljs-number">2</span>)); <span class="hljs-comment">// 10001</span>
</code></pre>
<p>We can also parse an integer giving it a base:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">parseInt</span>(<span class="hljs-number">10001</span>, <span class="hljs-number">2</span>)); <span class="hljs-comment">// 17</span>
</code></pre>
<p><strong>Note:</strong> We can also represent a binary number with the prefix <code>0b</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-number">0b10001</span>); <span class="hljs-comment">// 17</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-number">0b101</span>); <span class="hljs-comment">// 5</span>
</code></pre>
<p>For example, these are the same number:</p>
<pre><code class="lang-javascript"><span class="hljs-number">0b1</span> === <span class="hljs-number">0b00000001</span> <span class="hljs-comment">// true</span>
</code></pre>
<p>All bitwise operations are performed on 32-bit binary numbers in JavaScript. That is, <em>before a bitwise operation is performed, JavaScript converts numbers to 32-bit</em> <strong><em>signed</em></strong> <em>integers.</em></p>
<p>So, for example, <code>17</code> won't be simply <code>10001</code> but <code>00000000 00000000 00000000 00010001</code>.</p>
<p><em>After the bitwise operation is performed, the result is converted back to 64-bit JavaScript numbers.</em></p>
<h3 id="heading-bitwise-operators">Bitwise operators</h3>
<h4 id="heading-and-amp">AND (<code>&amp;</code>)</h4>
<p>If two bits are <code>1</code>, the result is <code>1</code>, otherwise <code>0</code>.</p>
<p>The GIFs below show the numbers as 8-bit strings, but when doing bitwise operations, remember they are converted to 32-bit numbers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747923324409/4415a01f-6129-4dcf-a1ce-b8e898bdba9a.gif" alt="Animated visualization of an AND operation. 00010001 &amp; 00000101 = 00000001." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> x1 = <span class="hljs-number">0b10001</span>;
<span class="hljs-keyword">const</span> x2 = <span class="hljs-number">0b101</span>;

<span class="hljs-keyword">const</span> result = x1 &amp; x2; <span class="hljs-comment">// 1 (0b1)</span>
</code></pre>
<h4 id="heading-or">OR (<code>|</code>)</h4>
<p>If either of the bits is <code>1</code>, the result is <code>1</code>, otherwise <code>0</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747923365941/22e562c4-e195-4567-b8ef-4b94cb4a394f.gif" alt="Animated visualization of an OR operation. 00010001 | 00000101 = 00010101." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> x1 = <span class="hljs-number">0b10001</span>;
<span class="hljs-keyword">const</span> x2 = <span class="hljs-number">0b101</span>;

<span class="hljs-keyword">const</span> result = x1 | x2; <span class="hljs-comment">// 21 (0b10101)</span>
</code></pre>
<h4 id="heading-xor">XOR (<code>^</code>)</h4>
<p>If the bits are different (one is <code>1</code> and the other is <code>0</code>), the result is <code>1</code>, otherwise <code>0</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747923403151/52090e1a-98ee-4303-a433-6318ec6f18d2.gif" alt="Animated visualization of a XOR operation. 00010001 ^ 00000101 = 00010100." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> x1 = <span class="hljs-number">0b10001</span>;
<span class="hljs-keyword">const</span> x2 = <span class="hljs-number">0b101</span>;

<span class="hljs-keyword">const</span> result = x1 ^ x2; <span class="hljs-comment">// 20 (0b10100)</span>
</code></pre>
<h4 id="heading-not">NOT (<code>~</code>)</h4>
<p>Flips the bits (<code>1</code> becomes <code>0</code>, <code>0</code> becomes <code>1</code>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747923458432/2807b821-8069-45e6-a37b-d8167ec722e7.gif" alt="Animated visualization of a NOT operation. ~00010001 = 11101110." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> n = <span class="hljs-number">17</span>;

<span class="hljs-keyword">const</span> result = ~n; <span class="hljs-comment">// -18</span>
</code></pre>
<p><strong>Note:</strong> Bitwise NOTing any 32-bit integer <code>x</code> yields <code>-(x + 1)</code>.</p>
<p>If we use <a target="_blank" href="https://stackoverflow.com/a/33758875">a helper function</a> to see the binary representations, it is as we expected:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(createBinaryString(n));
<span class="hljs-comment">// -&gt; 00000000 00000000 00000000 00010001</span>

<span class="hljs-built_in">console</span>.log(createBinaryString(result));
<span class="hljs-comment">// -&gt; 11111111 11111111 11111111 11101110</span>
</code></pre>
<p>The leftmost bit indicates the signal – whether the number is negative or positive.</p>
<p>Remember that we said JavaScript uses 32-bit <strong>signed</strong> integers for bitwise operations. <strong>The leftmost bit is</strong> <code>1</code> for negative numbers and <code>0</code> for positive numbers. Also, the operator operates on the operands' bit representations in <a target="_blank" href="https://en.wikipedia.org/wiki/Two's_complement">two's complement</a>. The operator is applied to each bit, and the result is constructed bitwise.</p>
<p><strong>Note:</strong> Two's complement allows us to get a number with an inverse signal. One way to do it is to invert the bits of the number in the positive representation and add 1 to it:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">twosComplement</span>(<span class="hljs-params">n</span>) </span>{
  <span class="hljs-keyword">return</span> ~n + <span class="hljs-number">0b1</span>;
}
</code></pre>
<h4 id="heading-left-shift-zero-fill-ltlt">Left shift (zero fill) (<code>&lt;&lt;</code>)</h4>
<p>Shifts the given number of bits to the left, adding zero bits shifted in from the right.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> n = <span class="hljs-number">17</span>;
<span class="hljs-keyword">const</span> result = n &lt;&lt; <span class="hljs-number">1</span>; <span class="hljs-comment">// 34</span>


<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">17</span>));
<span class="hljs-comment">// -&gt; 00000000 00000000 00000000 00010001</span>

<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">34</span>));
<span class="hljs-comment">// -&gt; 00000000 00000000 00000000 00100010</span>
</code></pre>
<p><strong>Note</strong> that the 32nd bit (the leftmost one) is discarded.</p>
<h4 id="heading-right-shift-sign-preserving-gtgt">Right shift (sign preserving) (<code>&gt;&gt;</code>)</h4>
<p>Shifts the given number of bits to the right, preserving the sign when adding bits from the left.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> n = <span class="hljs-number">17</span>;
<span class="hljs-keyword">const</span> result = n &gt;&gt; <span class="hljs-number">1</span>; <span class="hljs-comment">// 8</span>


<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">17</span>));
<span class="hljs-comment">// -&gt; 00000000 00000000 00000000 00010001</span>

<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">8</span>));
<span class="hljs-comment">// -&gt; 00000000 00000000 00000000 00001000</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> n = <span class="hljs-number">-17</span>;
<span class="hljs-keyword">const</span> result = n &gt;&gt; <span class="hljs-number">1</span>; <span class="hljs-comment">// -9</span>

<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">-17</span>));
<span class="hljs-comment">// -&gt; 11111111 11111111 11111111 11101111</span>

<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">-9</span>));
<span class="hljs-comment">// -&gt; 11111111 11111111 11111111 11110111</span>
</code></pre>
<h4 id="heading-right-shift-unsigned-gtgtgt">Right shift (unsigned) (<code>&gt;&gt;&gt;</code>)</h4>
<p>Shifts the given number of bits to the right, adding <code>0</code>s when adding bits in from the left, no matter what the sign is.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> n = <span class="hljs-number">17</span>;
<span class="hljs-keyword">const</span> result = n &gt;&gt;&gt; <span class="hljs-number">1</span>; <span class="hljs-comment">// 8</span>


<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">17</span>));
<span class="hljs-comment">// -&gt; 00000000 00000000 00000000 00010001</span>

<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">8</span>));
<span class="hljs-comment">// -&gt; 00000000 00000000 00000000 00001000</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> n = <span class="hljs-number">-17</span>;
<span class="hljs-keyword">const</span> result = n &gt;&gt;&gt; <span class="hljs-number">1</span>; <span class="hljs-comment">// 2147483639</span>

<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">-17</span>));
<span class="hljs-comment">// -&gt; 11111111 11111111 11111111 11101111</span>

<span class="hljs-built_in">console</span>.log(createBinaryString(<span class="hljs-number">2147483639</span>));
<span class="hljs-comment">// -&gt; 01111111 11111111 11111111 11110111</span>
</code></pre>
<h3 id="heading-getting-a-bit">Getting a bit</h3>
<p>To get a specific bit, we first need to create a <strong>bitmask</strong>. We can do this by shifting <code>1</code> to the left by the index of the bit we want to get. The result is the <strong>AND</strong> of the binary number and the bitmask.</p>
<p>But using JavaScript, we can also do an unsigned right shift by the index to put the bit in the first place (so that we don't get the actual value that is in that position, but whether it is a <code>1</code> or a <code>0</code>):</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getBit</span>(<span class="hljs-params">number, idx</span>) </span>{
  <span class="hljs-keyword">const</span> bitMask = <span class="hljs-number">1</span> &lt;&lt; idx;
  <span class="hljs-keyword">const</span> result = number &amp; bitMask;

  <span class="hljs-keyword">return</span> result &gt;&gt;&gt; idx;
}
</code></pre>
<p>For example, let's try <code>13</code>, which is <code>1101</code> in binary:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> binaryNumber = <span class="hljs-number">0b1101</span>;

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Bit at position 0:'</span>, getBit(binaryNumber, <span class="hljs-number">0</span>));
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Bit at position 1:'</span>, getBit(binaryNumber, <span class="hljs-number">1</span>));
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Bit at position 2:'</span>, getBit(binaryNumber, <span class="hljs-number">2</span>));
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Bit at position 3:'</span>, getBit(binaryNumber, <span class="hljs-number">3</span>));

<span class="hljs-comment">/*
Output:

Bit at position 0: 1
Bit at position 1: 0
Bit at position 2: 1
Bit at position 3: 1
*/</span>
</code></pre>
<h3 id="heading-setting-a-bit">Setting a bit</h3>
<p>If we want to turn a bit to <code>1</code> (in other words, to "<em>set a bit</em>"), we can do a similar thing.</p>
<p>First, we can create a bitmask again by shifting <code>1</code> to the left by the index of the bit we want to set to <code>1</code>. The result is the <strong>OR</strong> of the number and the bitmask:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setBit</span>(<span class="hljs-params">number, idx</span>) </span>{
  <span class="hljs-keyword">const</span> bitMask = <span class="hljs-number">1</span> &lt;&lt; idx;
  <span class="hljs-keyword">return</span> number | bitMask;    
}
</code></pre>
<p>Remember that in our example <code>13</code> was <code>1101</code> in binary, let's say we want to set the <code>0</code> at index 1:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> binaryNumber = <span class="hljs-number">0b1101</span>;
<span class="hljs-keyword">const</span> newBinaryNumber = setBit(binaryNumber, <span class="hljs-number">1</span>);

<span class="hljs-built_in">console</span>.log(createBinaryString(newBinaryNumber));
<span class="hljs-comment">// -&gt; 00000000 00000000 00000000 00001111</span>

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Bit at position 1:'</span>, getBit(newBinaryNumber, <span class="hljs-number">1</span>));
<span class="hljs-comment">// -&gt; Bit at position 1: 1</span>
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>With some detours here and there, we took a look at fourteen (or fifteen, if you count our <em>interlude</em>) different concepts, from arrays and hashing to bit manipulation.</p>
<p>Although I have to say that eventually, with time, it’s easy to forget all that we learned. But, that's not a problem in itself, because as you might have realized, if there is one idea that should stick with you with this handbook, it’s that problems are best solved when they are broken into smaller parts. And, as with anything else, writing or talking to yourself (see <a target="_blank" href="https://www.freecodecamp.org/news/rubber-duck-debugging/">duck debugging</a>) works miracles.</p>
<p>Now, it's time to take a deep breath.</p>
<p>It was a delightful adventure to explore data structures and algorithms, and hopefully you found some value in it.</p>
<p>Have a beautiful journey ahead, and until then, happy coding.</p>
<h3 id="heading-resources-amp-credits">Resources &amp; Credits</h3>
<p>This handbook was mainly inspired by the amazing <a target="_blank" href="https://medium.com/basecs">BaseCS series</a> by Vaidehi Joshi, which is an incredible resource for learning basic computer science concepts.</p>
<p>The visualization idea was inspired by Lydia Hallie's <a target="_blank" href="https://dev.to/lydiahallie/series/3341">JavaScript Visualized series</a>.</p>
<p>Of course, you can also check out <a target="_blank" href="https://neetcode.io/courses">NeetCode's courses</a> which can be incredibly helpful for a serious study.</p>
<p>There are many other resources to check out if you want to go further, here are some of the ones I used in our exploration:</p>
<ul>
<li><p><a target="_blank" href="http://brilliant.org">brilliant.org</a></p>
</li>
<li><p><a target="_blank" href="http://leetcodethehardway.com">leetcodethehardway.com</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference">MDN Web Docs</a></p>
</li>
<li><p><a target="_blank" href="http://baeldung.com/cs">baeldung.com/cs</a></p>
</li>
<li><p><a target="_blank" href="https://lucasfcosta.com/2018/12/25/bitwise-operations.html"><em>The Absolute Essentials for Bit Manipulation in JavaScript</em> by Lucas F. Costa</a></p>
</li>
<li><p><a target="_blank" href="https://condor.depaul.edu/ntomuro/courses/402/notes/heap.html">Heap &amp; HeapSort - Noriko Tomuro</a></p>
</li>
<li><p><a target="_blank" href="https://faculty.cs.niu.edu/~freedman/340/340notes/340heap.htm">Heaps - Professor Reva Freedman</a></p>
</li>
<li><p><a target="_blank" href="https://realpython.com/how-to-implement-python-stack/#using-collectionsdeque-to-create-a-python-stack">"Using <code>collections.deque</code> to Create a Python Stack" - Jim Anderson</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Architecture of Mathematics – And How Developers Can Use it in Code ]]>
                </title>
                <description>
                    <![CDATA[ "To understand is to perceive patterns." - Isaiah Berlin Math is not just numbers. It is the science of finding complex patterns that shape our world. This means that to truly understand it, we need to see beyond numbers, formulas, and theorems and ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-architecture-of-mathematics-and-how-developers-can-use-it-in-code/</link>
                <guid isPermaLink="false">68308ee8ccde6bc325c82393</guid>
                
                    <category>
                        <![CDATA[ Mathematics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Math ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ history ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tiago Capelo Monteiro ]]>
                </dc:creator>
                <pubDate>Fri, 23 May 2025 15:06:16 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748012748947/1df613bf-93e7-4f03-b0f0-47ff49f38504.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <blockquote>
<p>"To understand is to perceive patterns." - Isaiah Berlin</p>
</blockquote>
<p>Math is not just numbers. It is the science of finding complex patterns that shape our world. This means that to truly understand it, we need to see beyond numbers, formulas, and theorems and understand its structures.</p>
<p>The main goal of this article is to show how math is just like a growing tree of ideas. I want to show that math is a living system of logic, not just formulas to memorize. With analogies, history, and code examples, I want to help you understand math more deeply and how you can apply it to programming.</p>
<p>I’ve also included some code examples here to help you connect theory and practice. I show them to demonstrate how math ideas are applied to real problems. Whether you are new to more advanced math or are more experienced, these code examples will help you understand how to apply math in programming.</p>
<p>This link across theory and application reflects my own studies. I am a finalist in an undergraduate degree in Electrical and Computer Engineering at NOVA FCT, one of the best engineering faculties in Portugal.</p>
<p>My engineering degree is one with more math and physics. This is because it’s key to get a solid grasp of math to understand electronics, telecommunications, control theory, and other areas of engineering.</p>
<p>Here’s a brief overview of some of the math and physics subjects I’ve learned:</p>
<ul>
<li><p><strong>Partial Differential Equations (PDEs):</strong> These equations model real-world phenomena, from heat diffusion to the economy of a country.</p>
</li>
<li><p><strong>Harmonic Analysis (Fourier &amp; Laplace):</strong> Integral transforms like the Fourier and Laplace transform allow us to understand problems in new domains.</p>
</li>
<li><p><strong>Complex Analysis:</strong> Extending calculus into the complex plane gives rise to powerful tools used in physics and engineering.</p>
</li>
<li><p><strong>Numerical Analysis:</strong> When analytical solutions are impossible or inefficient, numerical methods provide computer-based approximations. This is crucial for real-world applications.</p>
</li>
<li><p><strong>Control and Signal Theory:</strong> These areas show us how to design stable systems like rockets, trains, and robots.</p>
</li>
<li><p><strong>Physics:</strong> Courses in Classical Mechanics and Electromagnetism helped bridge theoretical math to physical laws</p>
</li>
</ul>
<p>During my years of study, besides technical skills, I’ve developed a deeper understanding of how the world works and the structure of the field of mathematics. And I’ve started to find patterns in how math is a framework of interconnected logic.</p>
<h3 id="heading-in-this-article-well-explore">In this article, we’ll explore:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-simple-analogy-the-tree-of-mathematics">Simple Analogy: The Tree of Mathematics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-structure-and-history-of-mathematics">The Structure and History of Mathematics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-an-tree-example-foundations-of-relativity-by-albert-einstein">An Tree example: Foundations of Relativity by Albert Einstein</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-biggest-paradox-of-math-discovered-by-kurt-godel">The Biggest Paradox of Math, Discovered by Kurt Gödel</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-about-applied-math-and-engineering">What About Applied Math and Engineering?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-code-examples-analytical-and-numerical-approaches">Code Examples – Analytical and Numerical Approaches</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-impact-of-a-grand-unified-theory-of-mathematics">The Impact of a Grand Unified Theory of Mathematics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-final-lesson-from-history">A Final Lesson From History</a></p>
</li>
</ul>
<h2 id="heading-simple-analogy-the-tree-of-mathematics">Simple Analogy: The Tree of Mathematics</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747518175609/78838825-d872-42df-9dc8-736fa012a630.jpeg" alt="Photo of two trees by Johannes Plenio: https://www.pexels.com/photo/two-brown-trees-1632790/" class="image--center mx-auto" width="5456" height="3632" loading="lazy"></p>
<p>Imagine math as a vast tree growing forever.</p>
<p>The roots of the tree are the foundations of mathematics: logic and set theory. From this foundation emerge the main basic fields of math: arithmetic, algebra, geometry, and analysis.</p>
<p>As the tree divides further and further into more branches, new, more complex subfields start to appear, like topology, abstract algebra, and complex analysis. Sometimes the branches are connected to each other.</p>
<p>And remember: this tree is always growing in many directions. From branches creating new branches to branches connecting to other branches. Little by little, it grows.</p>
<p>Throughout history, there have been times that, due to some big scientific discoveries, parts of the math tree started to grow very fast. Other times, decades and even centuries passed without many new branches. This is the case for imaginary numbers, for example.</p>
<p>And you might wonder: How many more branches and connections between them will keep appearing?</p>
<h2 id="heading-the-structure-and-history-of-mathematics">The Structure and History of Mathematics</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747518363058/9911acd4-ad4f-4da2-a62b-9fa87e219c35.jpeg" alt="Photo of a writing desk and notebook on Pixabay: https://www.pexels.com/photo/brown-wooden-desk-159618/" class="image--center mx-auto" width="2480" height="1806" loading="lazy"></p>
<p>The first mathematical ideas appeared independently across ancient civilizations. For example:</p>
<ul>
<li><p>India’s invention of zero</p>
</li>
<li><p>Islamic algebraic advances</p>
</li>
<li><p>Greek geometric rigor</p>
</li>
</ul>
<p>Over time, many different great mathematicians created and shared them by writing and giving lectures.</p>
<p>Eventually, these new ideas were shared widely with new generations and these new generations created new math based on old math.</p>
<p>This is is how new branches are continuously born from previous branches of the tree of mathematics.</p>
<p>And this is why Isaac Newton wrote, in a letter to Robert Hooke in 1675:</p>
<blockquote>
<p>If I have seen further, it is by standing on the shoulders of giants</p>
</blockquote>
<p>He meant that by working from previous knowledge, he was able to create and (re)discover new ideas.</p>
<p>Yet, the real power of math lies in practicing it over and over and understanding it more and more deeply. As one of my professors once explained:</p>
<blockquote>
<p><em>More important than knowing the theorems is knowing the ideas behind them and the history of how they were created.</em></p>
</blockquote>
<p>Very often, to solve problems, it is necessary to think in terms of first principles and build from there. Math teaches exactly that. In this way, math is not just an academic subject. It is a language spoken by scientists and engineers around the globe.</p>
<p>By having it well preserved and shared, it is still possible to create new math from previous ideas. And it’s possible for the big tree to continue growing based on previous branches or nodes.</p>
<h2 id="heading-an-tree-example-foundations-of-relativity-by-albert-einstein">An Tree example: Foundations of Relativity by Albert Einstein</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747518865627/e84ff108-b383-405b-8bb0-73ffb50b4dcf.jpeg" alt="Albert Einstein, one of the greatest physics giants in history" class="image--center mx-auto" width="1920" height="1200" loading="lazy"></p>
<p>Albert Einstein created the general and special theories of relativity. These have big consequences nowadays:</p>
<ul>
<li><p>GPS and Global Communication</p>
</li>
<li><p>Advancements in Satellite Telecommunications</p>
</li>
<li><p>Space Exploration and Satellite Launches</p>
</li>
</ul>
<p>But this was only possible through the unification of geometry with calculus, called <strong>differential geometry.</strong> The evolution of differential geometry happened over the centuries, thanks to many great mathematicians. Below are some of them, but this is not a complete list:</p>
<ul>
<li><p><strong>Euclid (circa 300 BCE):</strong> Contributed to geometry, laying the groundwork for later mathematical systems</p>
</li>
<li><p><strong>Archimedes (circa 287–212 BCE):</strong> Pioneered the understanding of volume, surface area, and the principles of mechanics</p>
</li>
<li><p><strong>René Descartes (1596–1650):</strong> Developed Cartesian coordinates and analytical geometry</p>
</li>
<li><p><strong>Isaac Newton (1642–1727) &amp; Gottfried Wilhelm Leibniz (1646–1716):</strong> Newton’s laws of motion and gravitation, alongside Leibniz’s development of calculus, formed the basis of classical mechanics that Einstein sought to extend and modify in his theory of relativity.</p>
</li>
<li><p><strong>Leonhard Euler (1707–1783):</strong> Contributed to the development of differential equations, which are essential in the mathematical foundations of physics.</p>
</li>
<li><p><strong>Gaspard Monge (1746–1818):</strong> The father of differential geometry and pioneer in descriptive geometry</p>
</li>
<li><p><strong>Carl Friedrich Gauss (1777–1855):</strong> Made groundbreaking advances in geometry, including the concept of curved surfaces.</p>
</li>
<li><p><strong>Bernhard Riemann (1826–1866):</strong> Introduced Riemannian geometry, a branch of differential geometry.</p>
</li>
</ul>
<p>Once again, as Isaac Newton wrote, in a letter to Robert Hooke in 1675:</p>
<blockquote>
<p>If I have seen further, it is by standing on the shoulders of giants.</p>
</blockquote>
<p>Albert Einstein saw what no one else in his time saw, thanks to these great math giants and countless others.</p>
<h2 id="heading-the-biggest-paradox-of-math-discovered-by-kurt-godel">The Biggest Paradox of Math, Discovered by Kurt Gödel</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747518411126/df53f84c-f920-4b42-9081-5aeb1017f543.jpeg" alt="Kurt Gödel, one of the greatest math giants in history" class="image--center mx-auto" width="900" height="750" loading="lazy"></p>
<p>The biggest paradox in math, in my opinion, is what Kurt Gödel discovered. His early 20th century research revealed a limitation within this cycle.</p>
<p>This paradox – that is, <a target="_blank" href="https://en.wikipedia.org/wiki/G%C3%B6del%27s_incompleteness_theorems">his incompleteness theorems</a> – shows that in any consistent formal system capable of expressing simple arithmetic, there will always be true mathematical statements that cannot be proven within the system itself.</p>
<p>This means that in ALL systems, there are limits to what you can actually prove as to what is true and false. For for mathematicians, this means that the tree will never be completed. There are truths that are beyond formal truths, and yet we still assume that they are true (albeit unproven).</p>
<p>This way, it proves that no matter how many mathematicians work in the field or how much AI is used to find new mathematics, there will always exist limitations. Some things are impossible to prove that are true, and we just know that they are due to approximation estimations and other non logical exact methods.</p>
<h2 id="heading-what-about-applied-math-and-engineering">What About Applied Math and Engineering?</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747518581076/606f3bce-d7db-4ac3-9322-833673a734b0.jpeg" alt="Photo by JESHOOTS.com: https://www.pexels.com/photo/person-holding-a-chalk-in-front-of-the-chalk-board-714699/" class="image--center mx-auto" width="5472" height="3648" loading="lazy"></p>
<p>Applied math and engineering involves interpreting the same pure math ideas in real-world scenarios. Actually, in many cases, it is the combination of many math ideas. Let’s consider some examples:</p>
<p>Principal component analysis (PCA) is a widely used tool in data science. Yet, it is a mixture of linear algebra (in PCA, eigenvalues) with optimization (order eigenvalues that represent more data with less data) in order to make datasets shorter.</p>
<p>In machine learning, logistic regression is a mixture of calculus with statistics and probability.</p>
<p>In harmonic analysis, Laplace, Fourier, and Z-transforms are a way to see the same thing in a new domain to get new insights. In this case, integrals are used to make this mapping.</p>
<p>In deep learning, neural networks are just many matrices multiplying and updating themselves that adapt to model a dataset representing a system. This optimization of matrix values happens with activation functions, a gradient descent-based optimization method (tells how much values need to change), and backpropagation (applies those alterations to all matrix values).</p>
<p>I have actually written an article where I teach <a target="_blank" href="https://www.freecodecamp.org/news/activation-functions-in-neural-networks/">why activation functions are important</a> if you want to check it out.</p>
<p>But the best example of this fusion of math with engineering is in <a target="_blank" href="https://www.freecodecamp.org/news/basic-control-theory-with-python/">control theory</a>.</p>
<p>Control theory is the study of the architecture of systems. From trains to cars to airplanes, everything is based on control theory. It is everywhere in nearly all modern electronic devices. In electric circuits, control theory is also used heavily to guarantee circuit stability in the face of electric disturbances.</p>
<p>So as you can probably start to see, many of the tools we now have are just a mixture of many pure math ideas. Just many combinations and recipes of pure math ideas. In essence, applied math is the application of pure math as “ingredients“ in "recipes" to solve problems.</p>
<p>So, we’ve explored the structure and evolution of mathematics. Yet, it is important to see how these ideas can be applied in real life. Pure math makes the framework, and applied math applies that framework to solve problems. To understand this, we’ll examine two code examples that show how you can use math ideas as programming tools.</p>
<h2 id="heading-code-examples-analytical-and-numerical-approaches">Code Examples – Analytical and Numerical Approaches</h2>
<p>These code examples demonstrate a couple ways you can use Python to solve math equations.</p>
<p>In the first code example, we’ll solve the problem in the same way that kids in school solve math exercises: essentially, by hand with a pencil. Moving variables from left to right to find their values. In the second example, we’ll solve the problem using numerical analysis.</p>
<h3 id="heading-example-1-solve-a-problem-analytically">Example 1: Solve a Problem Analytically</h3>
<p>When we solve math problems analytically, like we did in school, we are manipulating symbols to get exact values. Often there symbols are x, y and z. In Python, we can do this using the SymPy library:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sympy <span class="hljs-keyword">import</span> symbols, Eq, solve

x, y = symbols(<span class="hljs-string">'x y'</span>)
eq1 = Eq(<span class="hljs-number">2</span>*x + <span class="hljs-number">3</span>*y, <span class="hljs-number">6</span>)
eq2 = Eq(-x + y, <span class="hljs-number">1</span>)

solution = solve((eq1, eq2), (x, y))
print(solution)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747160359386/7a21cddc-f4ba-4f9f-afa0-d1cc11fb27d6.png" alt="7a21cddc-f4ba-4f9f-afa0-d1cc11fb27d6" class="image--center mx-auto" width="2080" height="1224" loading="lazy"></p>
<p>Essentially, we are finding x and y based on this equation:</p>
<p>$$\begin{align*} 2x + 3y &amp;= 6 \\ -x + y &amp;= 1 \end{align*}$$</p><p>Which gives us the following result:</p>
<pre><code class="lang-python">{x: <span class="hljs-number">3</span>/<span class="hljs-number">5</span>, y: <span class="hljs-number">8</span>/<span class="hljs-number">5</span>}
</code></pre>
<p>Or:</p>
<ul>
<li><p>x= 0.6</p>
</li>
<li><p>y = 1.6</p>
</li>
</ul>
<p>When we say that we’re solving this analytically, it means that we’re finding an exact mathematical solution using formulas or equations.</p>
<p>But many times, problems are harder and can be solved by adding symbols to the right or left of the equation.</p>
<p>Sometimes, there can be so many symbols and transformed versions of them, with things like derivatives and integrals, that it can become very hard to manage and takes a lot of time.</p>
<p>For this reason, there is an area of mathematics devoted to finding approximations of already created mathematical formulas called numerical analysis. It makes it faster to solve these problems. And this is the method we will explore next.</p>
<h3 id="heading-example-2-solve-numerically-approximation">Example 2: Solve Numerically (Approximation)</h3>
<p>We’ll now use SciPy to solve the same system with numerical methods:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
<span class="hljs-keyword">from</span> scipy.linalg <span class="hljs-keyword">import</span> solve

A = np.array([[<span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>],
              [<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">-2</span>],
              [<span class="hljs-number">4</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>],
              [<span class="hljs-number">5</span>, <span class="hljs-number">3</span>, <span class="hljs-number">-2</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>],
              [<span class="hljs-number">2</span>, <span class="hljs-number">-3</span>, <span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>]])

b = np.array([<span class="hljs-number">12</span>, <span class="hljs-number">5</span>, <span class="hljs-number">7</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>])

solution = solve(A, b)

print(solution)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747160347486/d1f17aa6-b288-4e41-9be7-0810c45e778c.png" alt="d1f17aa6-b288-4e41-9be7-0810c45e778c" class="image--center mx-auto" width="2080" height="1764" loading="lazy"></p>
<p>In this code example, this line of code:</p>
<pre><code class="lang-python">solution = solve(A, b)
</code></pre>
<p>Uses the <a target="_blank" href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.solve.html">solve</a> method from the <a target="_blank" href="https://scipy.org/">SciPy</a> Python library:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> scipy.linalg <span class="hljs-keyword">import</span> solve
</code></pre>
<p>It’s a method that helps you find the values of x in an equation A⋅x=b, where a is a square grid of numbers and b is a list of numbers. Which gives us the following:</p>
<pre><code class="lang-python">[ <span class="hljs-number">1.35022026</span> <span class="hljs-number">-0.79955947</span> <span class="hljs-number">-1.17180617</span>  <span class="hljs-number">3.14317181</span> <span class="hljs-number">-0.83920705</span>]
</code></pre>
<p>Now imagine, in this simple case, that a matrix like A could represent the <strong>traffic flow</strong> between cities or intersections, and b could represent the <strong>traffic entering or leaving</strong> each city.</p>
<p>By solving the system, it could help us determine the distribution of traffic between cities to meet desired traffic conditions.</p>
<p>Of course, these types of problems are far more complex in real life. But to understand and solve the big problems, you need to first understand the smaller problems.</p>
<p>And by the way, a system of equations is the same thing as a matrix. We just represent systems of equations as matrices to make the findings of properties and clarity easier to understand.</p>
<p>The thing is that by using matrices, it is easier to make calculations and to perform linear algebra math to check for characteristics of the matrix and understand it better.</p>
<p>In essence, a matrix represents a system of equations. Also, systems of equations can represent real life phenomena like the economy of a country or the weather.</p>
<p>If you want to know more, I wrote an <a target="_blank" href="https://www.freecodecamp.org/news/numerical-analysis-explained-how-to-apply-math-with-python/">entire article on numerical analysis</a> that you can check out.</p>
<h2 id="heading-the-impact-of-a-grand-unified-theory-of-mathematics">The Impact of a Grand Unified Theory of Mathematics</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747518681068/54a9556c-2a79-441c-a6d6-27ff38e1f4ff.jpeg" alt="Photo by Porapak Apichodilok: https://www.pexels.com/photo/person-holding-world-globe-facing-mountain-346885/" class="image--center mx-auto" width="5184" height="3456" loading="lazy"></p>
<p>Despite the biggest paradox in mathematics, what would happen with a <a target="_blank" href="https://www.scientificamerican.com/article/the-evolving-quest-for-a-grand-unified-theory-of-mathematics/">Grand Unified Theory of Mathematics</a>?</p>
<p>Remember that such a theory tells us that there are things that are true that are impossible to formally prove, and we need to just accept it. But even with this assumption, it is still possible to unify all math.</p>
<p>This is what <a target="_blank" href="https://en.wikipedia.org/wiki/Langlands_program">the Langland's program</a> is trying to solve. A kind of attempt to interconnect the largest parts of the big tree of math to uncover new patterns in math.</p>
<p>With a Grand Unified Theory of Mathematics, we would be able to understand how every branch of the tree connects with the others and all the relationships between them.</p>
<h3 id="heading-what-is-the-value-of-this-big-unification-for-society">What is the value of this big unification for society?</h3>
<p>By studying history, we can find patterns. The unification of various fields has created many massive impacts on society, such as:</p>
<ul>
<li><p>In the 19th century, James Clerk Maxwell united the fields of <em>electricity</em> and <em>magnetism</em> with his famous Maxwell equations. This allowed the creation of radios and electric grids around the globe. In turn, it served as a foundation for all technological progress in the 20th and 21th century.</p>
</li>
<li><p>In the 20th century, the unification of <em>algebra</em> with <em>logic</em> led to the rise of digital systems. In turn, digital systems gave the rise of processors and the evolution of computers to the modern laptop.</p>
</li>
<li><p>Also in the 20th century, the unification of <em>probability</em> and <em>communication</em> led to information theory. This became the foundation for the internet. This unification was carried out by a great mathematician called Clause Shannon.</p>
</li>
</ul>
<p>In the end, a Grand Unified Theory of Mathematics could be one of the biggest achievements in modern society.</p>
<p>It could lead to new discoveries in physics, such as in string theory or quantum gravity, where deep mathematical structures are needed to create new physics. In AI, it could help unify all machine learning models in a common architecture. This would help accelerate the development of new AI models. It could also open the door to new cryptographic methods and material science advances, revealing, with math, the deep patterns still not found in these fields.</p>
<p>Just as uniting electricity and magnetism led to modern technology, a unified math framework would lead to a wave of innovation.</p>
<h2 id="heading-a-final-lesson-from-history">A Final Lesson From History</h2>
<p>From Greek geometry to AI, math has grown like a tree over centuries. By understanding its structure, it is possible to see its role in finding the patterns of our universe. I hope I was able to make you see math in this way.</p>
<p>In addition, we can conclude that the unification of scientific fields makes the foundations for the creation of new innovations to help society go forward. Many profound societal transformations only came to be thanks to abstract math ideas. When these are shared and refined, they become the hidden architecture of progress in society. Innovation begins when disconnected ideas are united, well-linked, and widely shared.</p>
<p>Find the full code <a target="_blank" href="https://github.com/tiagomonteiro0715/freecodecamp-my-articles-source-code">here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Write Math Equations in Google Docs ]]>
                </title>
                <description>
                    <![CDATA[ Math equations are a critical part of academic papers, research reports, and technical documentation. While LaTeX is widely used for professional typesetting, Google Docs offers a robust set of features for inserting and formatting math equations and... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/write-math-equations-in-google-docs/</link>
                <guid isPermaLink="false">68275cf15ed8a1db0cf8769b</guid>
                
                    <category>
                        <![CDATA[ Google Docs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mathematics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ chatgpt ]]>
                    </category>
                
                    <category>
                        <![CDATA[ chatgptguide ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Vikram Aruchamy ]]>
                </dc:creator>
                <pubDate>Fri, 16 May 2025 15:42:41 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747410133881/93c8cf6e-6cbb-4890-8023-fcc2fdaa0fa2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Math equations are a critical part of academic papers, research reports, and technical documentation. While LaTeX is widely used for professional typesetting, Google Docs offers a robust set of features for inserting and formatting math equations and also supports LaTeX-style input.</p>
<p>Whether you're a student submitting a math assignment or a professional documenting formulas, Google Docs provides multiple ways to insert and format equations efficiently.</p>
<p>In this article, you'll learn how to <a target="_blank" href="https://support.google.com/docs/answer/160749">write math equations in Google Docs</a> using different methods, including using Google Docs’ built-in equation editor and typing LaTeX-style commands directly, inserting complex equations with the help of the Auto-LaTeX add-on, and copying math equations from <a target="_blank" href="https://chromewebstore.google.com/detail/chatgpt-to-google-docs-or/oibghjgooccojibfacdonaoipegckdeg">ChatGPT to Google Docs</a> without losing formatting by using the ChatGPT to Google Docs or PDF Chrome extension.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-write-equations-using-the-built-in-equation-editor">How to Write Equations Using the Built-in Equation Editor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-write-equations-using-latex-commands">How to Write Equations Using LaTeX Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-auto-latex-add-on-for-writing-advanced-math-equations">How to Use Auto Latex Add-on for Writing Advanced Math Equations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-copy-math-equations-from-chatgpt-to-google-docs">How to Copy Math Equations from ChatGPT to Google Docs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-watch-how-to-write-equations-in-google-docs">Watch: How to Write Equations in Google Docs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tips-for-formatting-math-equations-in-google-docs">Tips for Formatting Math Equations in Google Docs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-how-to-write-equations-using-the-built-in-equation-editor"><strong>How to Write Equations Using the Built-in Equation Editor</strong></h2>
<p>Google Docs has a built-in equation editor that makes it easy to insert mathematical symbols and expressions.</p>
<p>To insert an equation editor box:</p>
<ol>
<li><p>Open your Google Docs document.</p>
</li>
<li><p>Go to the top menu and click <em>Insert</em> → <em>Equation</em>.</p>
</li>
<li><p>An equation editor will appear, and a new toolbar will show up with common math symbols like fractions, exponents, Greek letters, and more.</p>
</li>
</ol>
<p>Alternatively, you can use the following keyboard shortcuts to insert an equation editor box. </p>
<ul>
<li><p><strong>Windows/Linux:</strong> <code>Alt</code> + <code>I</code>, then <code>E</code></p>
</li>
<li><p><strong>Mac:</strong> <code>Control</code> + <code>Option</code> + <code>I</code>, then <code>E</code></p>
</li>
</ul>
<p>This shortcut quickly opens the equation editor without clicking through menus.</p>
<p><strong>Toolbar Symbols:</strong></p>
<p>Once the toolbar appears, you’ll find buttons for:</p>
<ul>
<li><p>Greek letters</p>
</li>
<li><p>Miscellaneous operations</p>
</li>
<li><p>Relations</p>
</li>
<li><p>Math operations</p>
</li>
<li><p>Arrows</p>
</li>
</ul>
<p>The equation editor box and a toolbar look like the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747198733591/9defa152-16a5-42a1-b098-228a9d1f7f79.png" alt="Google Docs Equation Editor and Toolbar" class="image--center mx-auto" width="1340" height="450" loading="lazy"></p>
<p>Now let’s learn how to write equations using the equation editor with a practical example.</p>
<h3 id="heading-example-typing-the-quadratic-formula">Example: Typing the Quadratic Formula</h3>
<p>Follow these steps to insert the following quadratic formula in Google Docs:</p>
<p>$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$</p><ol>
<li><p>Go to <em>Insert</em> → <em>Equation</em> to insert an equation editor and enable the equation toolbar.</p>
</li>
<li><p>Type <code>x=</code></p>
</li>
<li><p>Click the Math Operations dropdown (the one with templates like square roots, brackets), then select the fraction template. This inserts a placeholder with two parts: a numerator and a denominator.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747199319708/142157c9-f997-494a-be4a-efb619b7c12d.png" alt="Screenshot of Google Docs displaying a feature to insert math equations. A dropdown menu shows various mathematical symbols like fractions, square roots, and integrals. " class="image--center mx-auto" width="721" height="418" loading="lazy"></p>
</li>
<li><p>Click inside the numerator field. Begin by typing <code>-b</code>.</p>
</li>
<li><p>Now insert the ± (plus-minus) symbol. To do this:</p>
<ul>
<li><p>Click the Miscellaneous Operations dropdown</p>
</li>
<li><p>Select the ± symbol from the list.<br>  Your numerator should now show: <code>-b ±</code> as in the following image:</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747199567908/be5daee7-2ed2-4c86-be16-d90e033a05be.png" alt="Screenshot of Google Docs showing a math equation being edited with the equation toolbar open. Various symbols are visible on the toolbar, and part of a quadratic formula is visible in the document area." class="image--center mx-auto" width="766" height="362" loading="lazy"></p>
</li>
</ul>
</li>
<li><p>After the ± symbol, insert a square root:</p>
<ul>
<li><p>Go back to the Math Operations dropdown and select the square root template.</p>
</li>
<li><p>Inside the root, type <code>b^2 - 4ac</code>.</p>
<ul>
<li><p>Use <code>^</code> to enter exponents. For example, <code>b^2</code> will be rendered as <em>b²</em>.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747199681755/cc2084c2-5856-4999-9905-d825b5c276d9.png" alt="Screenshot of Google Docs showing a math equation being inserted. A dropdown menu displays mathematical symbols like fractions, square roots, and integrals. A formula for the quadratic equation is in the document area on the right." class="image--center mx-auto" width="775" height="393" loading="lazy"></p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Move to the denominator field and type <code>2a</code>.</p>
</li>
</ol>
<p>Now your full equation should appear as:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747199739733/2c1ef4f8-9730-491f-9120-5ba6daee41c3.png" alt="Screenshot of Google Docs showing an open document. It has menu options at the top and a formula for the quadratic equation displayed on the page." class="image--center mx-auto" width="805" height="352" loading="lazy"></p>
<p>The equation will be properly formatted using Google Docs’ equation rendering, making it easy to read and mathematically accurate. You can continue typing more text below or beside the equation as needed – it behaves like any other element in your document.</p>
<p>This approach is useful for inserting neatly formatted equations without relying on add-ons or external tools. It’s especially helpful for students, teachers, and professionals preparing technical documents directly in Google Docs.</p>
<h2 id="heading-how-to-write-equations-using-latex-commands">How to Write Equations Using LaTeX Commands</h2>
<p>If you're familiar with <a target="_blank" href="https://www.latex-project.org/"><strong>LaTeX</strong></a>, you can take advantage of Google Docs’ support for a subset of LaTeX-style commands inside the built-in equation editor. This can greatly speed up the process of entering complex mathematical expressions, especially if you're already comfortable with LaTeX syntax.</p>
<h3 id="heading-how-to-use-latex-commands-in-google-docs">How to Use LaTeX Commands in Google Docs</h3>
<ol>
<li><p>Open your Google Docs document.</p>
</li>
<li><p>Go to Insert → Equation to activate the equation toolbar and equation input field.</p>
</li>
<li><p>Click inside the equation box. Instead of using the toolbar buttons, type LaTeX-style commands directly.</p>
</li>
<li><p>As you type, Google Docs will automatically render the commands into formatted math once you press space or enter after each command or expression.</p>
</li>
</ol>
<h4 id="heading-commonly-supported-latex-commands-in-google-docs">Commonly Supported LaTeX Commands in Google Docs:</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Instruction</strong></td><td><strong>Result</strong></td></tr>
</thead>
<tbody>
<tr>
<td>To insert a fraction</td><td><code>\frac{a}{b}</code> → 𝑎⁄𝑏</td></tr>
<tr>
<td>To insert a square root</td><td><code>\sqrt{x}</code> → √𝑥</td></tr>
<tr>
<td>To insert Greek letters like α, β</td><td><code>\alpha, \beta</code> → α, β</td></tr>
<tr>
<td>To insert an integral with limits</td><td><code>\int_a^b f(x)\,dx</code> → ∫ᵃᵇ 𝑓(𝑥) 𝑑𝑥</td></tr>
<tr>
<td>To insert x superscript 2</td><td><code>x^2</code> → 𝑥²</td></tr>
<tr>
<td>To insert x subscript 1</td><td><code>x_1</code> → 𝑥₁</td></tr>
</tbody>
</table>
</div><p>Type these commands in the equation box, and when you press space or enter, they will be converted to properly formatted mathematical notation.</p>
<h4 id="heading-example-typing-the-quadratic-formula-using-latex-commands">Example: Typing the Quadratic Formula Using LaTeX Commands</h4>
<p>Let’s walk through how to enter the following quadratic formula using LaTeX-style commands:</p>
<p>$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$</p><p><strong>Steps:</strong></p>
<ol>
<li><p>Insert the equation box: Go to Insert → Equation.</p>
</li>
<li><p>In the equation input area, type the following:</p>
</li>
</ol>
<pre><code class="lang-javascript">x = \frac{-b \pm \sqrt{b^<span class="hljs-number">2</span> - <span class="hljs-number">4</span>ac}}{<span class="hljs-number">2</span>a}
</code></pre>
<ul>
<li><p><code>\frac</code> creates a fraction.</p>
</li>
<li><p><code>-b</code> is the numerator’s first term.</p>
</li>
<li><p><code>\pm</code> inserts the plus-minus symbol.</p>
</li>
<li><p><code>\sqrt</code> creates a square root.</p>
</li>
<li><p><code>b^2</code> formats <em>b squared</em>.</p>
</li>
<li><p><code>- 4ac</code> is written normally inside the square root.</p>
</li>
<li><p><code>2a</code> is the denominator.</p>
</li>
</ul>
<ol start="3">
<li>As you type, press space or enter after each LaTeX command. Google Docs will automatically convert the code into properly formatted math notation.</li>
</ol>
<p>After rendering, the equation will appear as:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747199739733/2c1ef4f8-9730-491f-9120-5ba6daee41c3.png" alt="A Google Docs interface showing the quadratic formula being entered as a math equation" width="805" height="352" loading="lazy"></p>
<p>This method is ideal for users who prefer keyboard-based input over clicking toolbar icons. It also allows you to enter complex expressions faster and more accurately, especially if you're familiar with standard LaTeX syntax.</p>
<h4 id="heading-notes">Notes:</h4>
<ul>
<li><p>Not all LaTeX features are supported in Google Docs. The supported commands are limited to basic math formatting, Greek letters, and common symbols.</p>
</li>
<li><p>Make sure to press <strong>space</strong> after each LaTeX command so that Docs knows to render it.</p>
</li>
</ul>
<h2 id="heading-how-to-use-auto-latex-add-on-for-writing-advanced-math-equations">How to Use Auto Latex Add-on for Writing Advanced Math Equations</h2>
<p>When generating mathematical content using tools like ChatGPT, you'll notice that equations are <strong>rendered visually on the webpage</strong>, but behind the scenes they’re created using <a target="_blank" href="https://www.latex-project.org/">LaTeX</a> code. So when you copy content from ChatGPT into Google Docs, the equations come through as raw LaTeX code rather than rendered math expressions.</p>
<p>For example, a quadratic formula provided by ChatGPT might look like this when pasted into your document:</p>
<pre><code class="lang-javascript">x = \frac{-b \pm \sqrt{b^<span class="hljs-number">2</span> - <span class="hljs-number">4</span>ac}}{<span class="hljs-number">2</span>a}
</code></pre>
<p>While this format is ideal for precision, Google Docs doesn’t support LaTeX rendering by default.</p>
<p>This is where the <strong>Auto-LaTeX Equations</strong> add-on becomes essential, especially if you're moving content from <a target="_blank" href="https://www.docstomarkdown.pro/chatgpt-to-google-docs/">ChatGPT to Google Docs</a>. It’s also incredibly useful when importing LaTeX-based documents into Google Docs, such as content originally written in <a target="_blank" href="https://overleaf.com/">Overleaf</a> or other LaTeX editors.</p>
<p>Instead of manually reformatting equations, the add-on automatically renders LaTeX code into properly formatted math equations, preserving the typesetting and structure you’d expect from a LaTeX environment.</p>
<h3 id="heading-what-is-auto-latex-equations">What is Auto-LaTeX Equations?</h3>
<p><a target="_blank" href="https://workspace.google.com/marketplace/app/autolatex_equations/850293439076"><strong>Auto-LaTeX Equations</strong></a> is a free and open-source Google Docs add-on that scans your document for LaTeX expressions and converts them into a properly formatted equations.</p>
<p>It recognizes LaTeX code wrapped in these delimiters:</p>
<ul>
<li><p>Inline: <code>$$ ... $$</code></p>
</li>
<li><p>Display: <code>\[ ... \]</code></p>
</li>
</ul>
<p>Once detected, it renders the equations seamlessly within your document, eliminating the need to retype or manually format them.</p>
<ol>
<li><p>Paste your LaTeX expression into the Google Docs document. Make sure the expression is enclosed using one of the supported delimiters:</p>
<p> <code>$$ ... $$ or \[ ... \]</code></p>
</li>
<li><p>Open the add-on sidebar by clicking Extensions <strong>→</strong> Auto-LaTeX Equations <strong>→</strong> Start.</p>
</li>
<li><p>Once the sidebar opens, you’ll see a dropdown labeled “Delimiters” and a button called “Render Equations.”</p>
</li>
<li><p>Select the delimiter you used when enclosing your LaTeX equations – for example, <code>$$</code> or <code>\[ \]</code>.</p>
</li>
<li><p>Click the “Render Equations” button.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747203253430/e96fe9ac-ccba-4ed5-ab10-1e759d737d7d.png" alt="Auto Latex Equations add-on sidebar" class="image--center mx-auto" width="297" height="387" loading="lazy"></p>
<p>The add-on will automatically scan your document and convert all valid LaTeX expressions into properly formatted equation images.</p>
<p>This step-by-step process allows you to take any LaTeX-based math copied from ChatGPT and render it cleanly within Google Docs – ready for export to Word or PDF.</p>
<h3 id="heading-example-converting-a-latex-coded-equation-to-rendered-math-equations">Example: Converting a LaTeX coded Equation to Rendered Math Equations</h3>
<p>Paste the following equation into Google Docs:</p>
<p><code>$$ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$</code></p>
<p>To convert it:</p>
<ol>
<li><p>Go to Extensions → Auto-LaTeX Equations → Start.</p>
</li>
<li><p>Select the Delimitor as <code>$$</code> ..<code>$$</code> and click on the Render Equations button. The equation will be rendered and look as follows:</p>
</li>
</ol>
<p>$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$</p><h4 id="heading-how-to-install-auto-latex-equations">How to Install Auto-LaTeX Equations</h4>
<ol>
<li><p>In Google Docs, click Extensions → Add-ons → Get add-ons.</p>
</li>
<li><p>Search for Auto-LaTeX Equations.</p>
</li>
<li><p>Click Install and follow the prompts.</p>
</li>
<li><p>After installation, access it from Extensions → Auto-LaTeX Equations.</p>
</li>
</ol>
<h2 id="heading-how-to-copy-math-equations-from-chatgpt-to-google-docs">How to Copy Math Equations from ChatGPT to Google Docs</h2>
<p>To easily transfer math equations and the surrounding content from ChatGPT into Google Docs without losing formatting, use the free <a target="_blank" href="https://chromewebstore.google.com/detail/chatgpt-to-google-docs-or/oibghjgooccojibfacdonaoipegckdeg"><strong>ChatGPT to Google Docs or PDF</strong></a> Chrome extension.</p>
<p>This extension allows you to:</p>
<ul>
<li><p>Export a single response (with equations and tables) into Google Docs while preserving formatting</p>
</li>
<li><p>Export an entire conversation, including math, code, and text, into a clean, one organized Google Docs, no need to export responses separately and <a target="_blank" href="https://www.freecodecamp.org/news/merge-multiple-google-docs-with-apps-script-or-google-docs-api/">merge multiple Google Docs into one</a> later</p>
</li>
<li><p>Save ChatGPT canvas content as a Google Docs or PDF</p>
</li>
<li><p>Export ChatGPT deep research documents directly into Google Docs</p>
</li>
<li><p>Export <a target="_blank" href="https://www.docstomarkdown.pro/chatgpt-to-pdf/">ChatGPT content directly into PDF format</a> when no further edits are necessary, eliminating the need to first export to Google Docs and then convert <a target="_blank" href="https://workspace.google.com/marketplace/app/docs_to_pdf_pro/302636103705">Google Docs to PDF</a></p>
</li>
</ul>
<p>It’s especially useful for students, researchers, and professionals who want to keep their AI-generated math, notes, and research well-organized in Google Docs or PDF format with minimal effort.</p>
<h2 id="heading-watch-how-to-write-equations-in-google-docs">Watch: How to Write Equations in Google Docs</h2>
<p>If you prefer visual learning, here’s a helpful video walkthrough that demonstrates all the methods discussed above – using the built-in equation editor, LaTeX-like commands, and the Auto-LaTeX Equations add-on.</p>
<p>This step-by-step tutorial covers:</p>
<ul>
<li><p>Opening and using the built-in equation toolbar</p>
</li>
<li><p>Typing LaTeX-style commands directly in the equation editor</p>
</li>
<li><p>Converting AI-generated LaTeX (e.g., from ChatGPT) into clean equations</p>
</li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/Y_2q45Qscp0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-tips-for-formatting-math-equations-in-google-docs">Tips for Formatting Math Equations in Google Docs</h2>
<p><strong>Use inline equations when:</strong></p>
<ul>
<li><p>Inserting short expressions like <code>x²</code>, <code>a/b</code>, or single variables</p>
</li>
<li><p>Including math within a sentence to maintain the flow of text</p>
</li>
</ul>
<p><strong>Use block equations when:</strong></p>
<ul>
<li><p>Writing complex or multi-line formulas (e.g., the quadratic formula)</p>
</li>
<li><p>You want the equation to be clearly separated from the surrounding text for readability</p>
</li>
</ul>
<p><strong>Wrapping tips for rendered equations:</strong></p>
<ul>
<li><p>Rendered equations are treated as images in Google Docs, which may disrupt the document layout if not positioned correctly</p>
</li>
<li><p>To fix this:</p>
<ul>
<li><p>Click the equation image</p>
</li>
<li><p>Choose from:</p>
<ul>
<li><p><strong>In line</strong> – aligns the equation with surrounding text (best for inline use)</p>
</li>
<li><p><strong>Wrap text</strong> – wraps paragraph text around the equation image</p>
</li>
<li><p><strong>Break text</strong> – places the equation on its own line, isolating it</p>
</li>
</ul>
</li>
<li><p>Use the margin handles or spacing options to fine-tune the layout and prevent overlap or crowding</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Google Docs offers several flexible ways to write and manage math equations:</p>
<ul>
<li><p><strong>Use the built-in equation editor</strong> for basic symbols, fractions, exponents, and common operations. It’s easy to access and great for straightforward math tasks without needing special syntax.</p>
</li>
<li><p><strong>Try LaTeX-like commands</strong> inside the equation editor for faster input. You can type commands like <code>\frac</code>, <code>\sqrt</code>, or <code>\alpha</code> to quickly insert structured equations without navigating menus.</p>
</li>
<li><p><strong>Install add-ons like Auto-LaTeX Equations</strong> for advanced LaTeX rendering. This is especially useful if you're copying equations from Overleaf, ChatGPT, or LaTeX documents into Google Docs, as it preserves formatting and converts code into clean equation images.</p>
</li>
<li><p><strong>Use external tools when copying from other formats</strong>, like the <em>ChatGPT to Google Docs or PDF</em> Chrome extension, which helps retain equation formatting when moving content from ChatGPT or other platforms.</p>
</li>
</ul>
<p>Whether you’re completing math homework, preparing teaching materials, or writing a research paper, Google Docs, combined with these tools, gives you everything you need to create clear, professional-looking documents with math content.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Cryptography Handbook: Exploring RSA PKCSv1.5, OAEP, and PSS ]]>
                </title>
                <description>
                    <![CDATA[ The RSA algorithm was introduced in 1978 in the seminal paper, "A Method for Obtaining Digital Signatures and Public-Key Cryptosystems". Over the decades, as RSA became integral to secure communications, various vulnerabilities and attacks have emerg... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-cryptography-handbook-rsa-algorithm/</link>
                <guid isPermaLink="false">67edb47680c0ce2ff2faebb9</guid>
                
                    <category>
                        <![CDATA[ Cryptography ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hamdaan Ali ]]>
                </dc:creator>
                <pubDate>Wed, 02 Apr 2025 22:04:38 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743630655223/f7e0c094-2103-42cd-97bd-be79d14fff67.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The RSA algorithm was introduced in 1978 in the seminal paper, "A Method for Obtaining Digital Signatures and Public-Key Cryptosystems". Over the decades, as RSA became integral to secure communications, various vulnerabilities and attacks have emerged, underscoring the importance of understanding and implementing RSA correctly.</p>
<p>This handbook will help you understand the internal workings of the RSA algorithm, how they have evolved over the years, and the schemes defined under various RFCs. This knowledge will help you make informed choices about the most suitable RSA schemes depending on your business requirements.</p>
<p>In this handbook, we’ll begin by exploring the foundational principles of the RSA algorithm. By examining its mathematical underpinnings and historical evolution, you will gain insight into the diverse array of attacks that have emerged over the years.</p>
<p>The narrative unfolds as an evolutionary journey: from the original, straightforward (textbook) RSA implementation, through the discovery of vulnerabilities, to the development of effective countermeasures, and further refinements as new challenges were encountered. This progression illuminates how RSA has transformed over time and also demonstrates how modern cryptographic libraries have integrated these advancements to achieve secure implementations in today’s applications.</p>
<p>You can also watch the associated video here:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/jpcLbsuHWbU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-alice-bob-paradigm">The Alice-Bob Paradigm</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-birth-of-the-rsa-cryptosystem">The Birth of the RSA Cryptosystem</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-prime-numbers-and-composite-moduli">Prime Numbers and Composite Moduli</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-euler-totient-function">The Euler Totient Function</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-computing-the-keys">Computing the Keys</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-rsa-operations">RSA Operations</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-encryption">Encryption</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-decryption">Decryption</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-digital-signatures">Digital Signatures</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-issues-with-eulers-totient-function-in-rsa">Issues with Euler’s Totient Function in RSA</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-carmichael-function">The Carmichael Function</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-mathematical-implication-of-the-carmichael-function">Mathematical Implication of The Carmichael function</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-carmichael-function-in-modern-implementations">The Carmichael Function in Modern Implementations</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-issues-with-raw-rsa">Issues with Raw RSA</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-exploiting-textbook-rsas-determinism-and-malleability">Exploiting Textbook RSA’s Determinism and Malleability</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-key-generation-setup">Key Generation (Setup)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-encryption-process">Encryption Process</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-determinism-exploit-ciphertext-guessing-attack">Determinism Exploit (Ciphertext Guessing Attack)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-malleability-exploit-ciphertext-manipulation-attack">Malleability Exploit (Ciphertext Manipulation Attack)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-low-exponent-attacks">Low-Exponent Attacks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hastads-broadcast-attack-low-exponent-meets-multiple-recipients">Håstad’s Broadcast Attack: Low Exponent Meets Multiple Recipients</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introduction-to-padding-schemes-in-rsa">Introduction to Padding Schemes in RSA</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-public-key-cryptography-standards-pkcs1-v15">Public Key Cryptography Standards (PKCS#1 v1.5)</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-the-mathematics-behind-pkcs1-v15">The Mathematics Behind PKCS#1 v1.5</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-the-bleichenbacher-attack">The Bleichenbacher Attack</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-optimal-asymmetric-encryption-padding-oaep">Optimal Asymmetric Encryption Padding (OAEP)</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-the-mathematics-behind-oaep">The Mathematics Behind OAEP</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-why-sha-1-or-md5-are-safe-in-rsa-oaep">Why SHA-1 or MD5 Are Safe in RSA-OAEP</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-label-hashing">Label Hashing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mask-generation-function-mgf1">Mask Generation Function (MGF1)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-adoption-in-cryptographic-libraries-pkcs1-v15-vs-oaep">Adoption in Cryptographic Libraries (PKCS#1 v1.5 vs OAEP)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-enhancing-digital-signatures-the-transition-to-pss">Enhancing Digital Signatures: The Transition to PSS</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-problems-with-early-rsa-signature-schemes">Problems with Early RSA Signature Schemes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-birth-of-the-probabilistic-signature-scheme-pss">Birth of the Probabilistic Signature Scheme (PSS)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-mathematics-behind-pss">The Mathematics Behind PSS</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-the-road-ahead-assessing-rsas-long-term-viability">The Road Ahead: Assessing RSA’s Long-Term Viability</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ol>
<li><strong>Linear Algebra:</strong> A foundational understanding of Linear Algebra and Modular Arithmetic will help you understand certain sections of the handbook, though it is not an absolute requirement. This handbook provides comprehensive explanations of mathematical expressions and their underlying concepts as they arise.</li>
</ol>
<p>For a concise and relevant introduction to the Chinese Remainder Theorem (CRT) in the context of the handbook, you may find this resource helpful: <a target="_blank" href="https://www.youtube.com/watch?v=Mt9v7-xBuaA">CRT, RSA, and Low Exponent Attacks | YouTube</a>.</p>
<ol start="2">
<li><strong>Patience (and a Sense of Adventure):</strong> RFCs can sometimes get dull to read, and research papers can feel intimidating at first glance. This handbook is designed to make standard cryptographic concepts accessible to everyone, guiding you through each step with clarity and intuition. Every concept is reinforced with clear, step-by-step examples, ensuring not only a thorough understanding but also familiarity with widely used standard notations. So take your time, take a deep breath, and embrace the journey.</li>
</ol>
<p>For visual learners, the associated video may offer a more engaging experience.</p>
<h2 id="heading-the-alice-bob-paradigm"><strong>The Alice-Bob Paradigm</strong></h2>
<p>Throughout this handbook, you will come across numerous sequence diagrams and mathematical proofs that use the Alice-Bob Paradigm.</p>
<p>The Alice-Bob paradigm is a common convention in cryptography where two generic entities, often named Alice and Bob, are used to illustrate various scenarios, protocols, or cryptographic principles.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742677993632/c9312974-4cb9-4496-8b23-b6d0d61c0a45.png" alt="The Alice Bob Paradigm" class="image--center mx-auto" width="818" height="483" loading="lazy"></p>
<p>These characters represent two parties engaged in communication, with Alice typically representing the sender or initiator, and Bob representing the receiver or responder.</p>
<p>We often introduce Eve as a third party, symbolizing an eavesdropper or potential attacker, adding an element of security risk, and illustrating scenarios where external entities might attempt to intercept or manipulate the communication.</p>
<h2 id="heading-the-birth-of-the-rsa-cryptosystem">The Birth of the RSA Cryptosystem</h2>
<p>The year 1978 witnessed the birth of a new era in cryptography with the introduction of the RSA cryptosystem, named after its inventors (Rivest, Shamir, and Adleman).</p>
<p>This development, introduced in the paper "A Method for Obtaining Digital Signatures and Public-Key Cryptosystems", provided a method for secure digital communication and laid the foundation for modern public-key cryptography.  </p>
<p>At the heart of RSA lies elementary number theory – specifically, the properties of prime numbers and modular arithmetic. Let’s first understand how these key concepts form its mathematical foundations.</p>
<h3 id="heading-prime-numbers-and-composite-moduli">Prime Numbers and Composite Moduli</h3>
<p>The algorithm starts by selecting two large prime numbers, denoted as <em>p</em> and <em>q</em>. Their product (\(n = p \times q\)) forms the modulus for both the public and private keys.  </p>
<p>The security of RSA depends heavily on the fact that, while multiplying these primes is computationally straightforward, factoring the resulting large composite number <em>n</em> is considered infeasible for sufficiently large primes.  </p>
<p>At this point, it’s important to note that p and q must be large prime numbers to ensure RSA’s security. Fortunately, modern libraries handle this automatically by using well-established prime-generation algorithms. As a result, you can focus on higher-level aspects of your applications without having to manage the low-level details of prime selection.</p>
<p>For instance, let’s have a look at OpenSSL’s RSA key generation routine which performs several checks to ensure that the resulting modulus \(n = p \times q \) meets the desired bit-length requirements:</p>
<p>The below snippet right-shifts the product of the generated primes (stored in <code>r1</code>) by <code>bitse - 4</code> bits to isolate the top 4 bits, which are then checked to ensure that the modulus meets the desired size criteria.</p>
<pre><code class="lang-c"><span class="hljs-keyword">if</span> (!BN_rshift(r2, r1, bitse - <span class="hljs-number">4</span>))
    <span class="hljs-keyword">goto</span> err;
bitst = BN_get_word(r2);
</code></pre>
<p>The extracted bits (<code>bitst</code>) are then compared against a predefined range (from <code>0x9</code> to <code>0xF</code>). This range ensures that the most significant byte of the modulus isn’t too small or too large.</p>
<pre><code class="lang-c"><span class="hljs-keyword">if</span> (bitst &lt; <span class="hljs-number">0x9</span> || bitst &gt; <span class="hljs-number">0xF</span>) {
    bitse -= bitsr[i];
</code></pre>
<p>If the significant bits do not fall within the desired range, the bit length is adjusted and the prime-generation process is retried. If the number of retries exceeds a set limit, the entire process is restarted.</p>
<pre><code class="lang-c"><span class="hljs-keyword">if</span> (!BN_GENCB_call(cb, <span class="hljs-number">2</span>, n++))
    <span class="hljs-keyword">goto</span> err;
<span class="hljs-keyword">if</span> (primes &gt; <span class="hljs-number">4</span>) {
    <span class="hljs-keyword">if</span> (bitst &lt; <span class="hljs-number">0x9</span>)
        adj++;
    <span class="hljs-keyword">else</span>
        adj--;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (retries == <span class="hljs-number">4</span>) {
    i = <span class="hljs-number">-1</span>;
    bitse = <span class="hljs-number">0</span>;
    sk_BIGNUM_pop_free(factors, BN_clear_free);
    factors = sk_BIGNUM_new_null();
    <span class="hljs-keyword">if</span> (factors == <span class="hljs-literal">NULL</span>)
        <span class="hljs-keyword">goto</span> err;
    <span class="hljs-keyword">continue</span>;
}
retries++;
<span class="hljs-keyword">goto</span> redo;
</code></pre>
<p>To ensure that the numbers are necessarily primes, these libraries use a combination of probabilistic tests, including the Rabin-Miler Primality Testing, and sieving methods to quickly eliminate non-prime candidates.</p>
<h3 id="heading-the-euler-totient-function">The Euler Totient Function</h3>
<p>For a number <em>n</em> that is the product of two primes, the Euler totient function is given by:</p>
<p>$$\varphi(n) = (p-1)(q-1)$$</p><p>This function counts the number of integers less than \(n\) that are co-prime to \(n\). Euler’s theorem, which states that for any integer <em>a</em> co-prime to <em>n</em>, \( a^{\varphi(n)} \equiv 1 \pmod{n}\) plays a central role in proving why RSA’s operations are reversible.</p>
<p>But most modern RSA cryptosystems use the Carmichael function instead of the Euler’s Totient Function. We will examine the reasoning behind this shift in the next few sections.</p>
<h3 id="heading-computing-the-keys">Computing the Keys</h3>
<p>Now we select an integer \(e\) such that \(1 &lt; e &lt; \varphi(n)\)and \(\gcd(e, \varphi(n)) = 1\). This \(e\) becomes the public exponent you see as a parameter in the RSA function calls you make.</p>
<p>With that done, now let’s determine \(d\) as the modular multiplicative inverse of \(e \, \, modulo \, \varphi(n)\). In other words, \(d\) is computed such that:</p>
<p>$$e \times d \equiv 1 \pmod{\varphi(n)}$$</p><p>This step is the mathematical linchpin ensuring that decryption is the inverse operation of encryption.</p>
<p>In the 1978 paper, the authors explicitly provided these formulas and steps. They showed that if you encrypt a message m using \(c = m^e \mod n\) and then decrypt using \(m = c^d \mod n \) , the original message is recovered – thanks to the properties of modular exponentiation and Euler’s theorem. This mathematical framework was novel at the time and immediately set the stage for a new era in cryptography.</p>
<h2 id="heading-rsa-operations">RSA Operations</h2>
<p>Now that the mathematical foundations are laid, the RSA algorithm can be seen as a set of three core operations: Encryption, Decryption, and Signing. Throughout this handbook's next sections, we will critically analyze these operations and learn about several pitfalls in each. Then we will examine how these were averted with the birth of new schemes, each to solve a new issue discovered on the way.</p>
<h3 id="heading-encryption">Encryption</h3>
<p>With the public key \((n, e)\) available to everyone, any user can encrypt a message \(m\) (where \(m\) is first encoded as an integer in the range \(0 \leq m &lt; n\) ) using the formula:</p>
<p>$$c = m^e \mod n$$</p><p>Here, c is the ciphertext. Because the operation is based on modular exponentiation, even if m is known, recovering m from c without knowing d is computationally hard.</p>
<h3 id="heading-decryption">Decryption</h3>
<p>The intended recipient, who possesses the private key \(d\), decrypts the cipher text \(c\) by computing:</p>
<p>$$m = c^d \bmod n$$</p><p>Using the relationship (\(e \times d \equiv 1 \pmod{\varphi(n)}\)) and properties from Euler’s theorem, the above operation exactly inverts the encryption step, recovering the original message \(m\).</p>
<p>This ensures that only the holder of the private key can read the encrypted message. This is the backbone of RSA’s use in secure communication.</p>
<p>The sequence diagram below wraps up our discussion so far:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742754978876/9b007639-8595-4d11-93ff-355820cb98c7.png" alt="Sequence Diagram: Textbook RSA Encryption" class="image--center mx-auto" width="732" height="540" loading="lazy"></p>
<h3 id="heading-digital-signatures">Digital Signatures</h3>
<p>Digital signatures fulfill a different security goal: authenticity and integrity rather than confidentiality. While encryption and decryption use the public key for “locking” and the private key for “unlocking,” digital signatures reverse these roles.</p>
<h4 id="heading-1-signing">1. Signing</h4>
<p>The author of a message uses their private key \(d\) to compute a signature \(s\) on the message \(m\), guided by the formula mentioned below:</p>
<p>$$s = m^d \bmod n$$</p><p>This can later be verified by others using the corresponding public key. The purpose here is not to recover a secret message but to create a proof of authenticity.</p>
<h4 id="heading-2-verification">2. Verification:</h4>
<p>Anyone with the public key \((n, e)\) can verify that the signature s indeed belongs to the message \(m\) by computing:</p>
<p>$$m \equiv s^e \bmod n$$</p><p>If the equivalence holds, it confirms two key points: That the message has not been tampered with (that is, integrity), and that the signature must have been generated using the private key d (that is, authenticity).<br>As long as \(d\) is kept secret, only the legitimate signer can produce a valid signature. Take at look at the sequence diagram below to understand the complete process.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742755268516/6dea4239-f214-42c4-96c7-5fc55c7249d9.png" alt="Sequence Diagram: Textbook RSA Signatures" class="image--center mx-auto" width="732" height="540" loading="lazy"></p>
<h2 id="heading-issues-with-eulers-totient-function-in-rsa">Issues with Euler’s Totient Function in RSA</h2>
<p>While using Euler’s Totient Function works well in theory, implementers of the scheme realized its practical downsides. Simply put, the primary issue was that Euler’s Totient Function can lead to a larger private exponent \(d\) than what was necessary.</p>
<p>To completely appreciate this fact, let’s take a step back to understand why the size of the private exponent \(d\) matters in RSA.</p>
<p>RSA decryption (or signing) involves computing \(m^d ~~mod ~n\) which is done via modular exponentiation. The time complexity of exponentiation algorithms (like square-and-multiply) grows with the number of bits in \(d\). A larger \(d\) means more multiplications and squarings, that is slower decryption.</p>
<p>In practice, if using the Euler’s Totient Function makes \(d\) roughly twice as large as what is required, then decryption can be almost twice as slow compared to using the minimal \(d\). This inefficiency is especially noticeable when \(e\) is small (common public exponents like 3 or 65537). A small \(e\) leads to a very large \(d\) under \(φ(n)\).</p>
<p>Beyond performance, having an unnecessarily large \(d\) can increase storage size slightly (a few more bytes for the key). This can also lead to interoperability quirks, which is why standards and protocols such as FIPS 186-4 [1] and RFC 8017 [2] expect \(d\) to be below a certain size. We will take a detailed look at this in the next section.</p>
<p>To combat these issues, cryptographers utilized the Carmichael function to generate RSA keys. Before we dive into how the Carmichael function helps our case, let’s quickly understand what the Carmichael function actually is.</p>
<h2 id="heading-the-carmichael-function">The Carmichael Function</h2>
<p>The Carmichael Function, represented by \(λ(n)\), also known as the reduced totient or least universal exponent, is defined as the smallest positive integer \(m\) such that for every integer \(a\) co-prime to \(n\), \( a^m ≡ 1 (mod n)\).</p>
<p>To put this in easy terms, \(λ(n)\) is the exponent of the multiplicative group modulo \(n\) (the least common multiple of the orders of all elements). For RSA-style moduli (product of primes), the Carmichael function is guided by the formula:</p>
<p>$$\lambda(n) = \operatorname{lcm}(p-1,\,q-1)$$</p><p>where \(n = p . q\) with \(p\) and \(q\) being the large primes.</p>
<p>You may now understand the Carmichael function better if we put it in the following way: \(λ(n)\) is the least common multiple of \(λ(n)\) of each prime power dividing n. So for a prime \(p\), \(λ(p) = φ(p) = p – 1\), and for two primes, we take the \(lcm\) of \(p-1 \) and \(q-1.\)</p>
<h3 id="heading-mathematical-implication-of-the-carmichael-function">Mathematical Implication of The Carmichael function</h3>
<p>The Carmichael function \(λ(n)\) is a “tighter” bound. What this means is that \(λ(n)\) divides \(φ(n)\) (since the exponent of a finite group always divides the group order by Lagrange’s Theorem [3])</p>
<p>If \(p\) and \(q\) are both odd primes, then \(p–1\) and \(q–1 \) are even, so their least common multiple is roughly half of \((p–1)(q–1)\). Mathematically:</p>
<p>$$λ(n) = \dfrac{(p–1)(q–1)} {gcd(p–1, q–1)}$$</p><p>We can observe that this \(λ(n)\) is lesser than or equal to \(φ(n)\) and often considerably smaller. This means \(λ(n)\) provides the minimal exponent needed for RSA’s correctness, whereas \(φ(n)\)might be a larger number that still works but isn’t necessary.</p>
<p>When you choose two large random primes \(p\) and \(q\), you have:</p>
<p>$$\varphi(n) = (p-1)(q-1) \approx n,$$</p><p>because for large primes, the subtracted ones make only a small difference compared to \(p\) and \(q\) themselves.</p>
<p>Now, since both \(p-1\) and \(q-1 \) are even, they each have a factor of 2. If those are their only common factors (which is often the case for random primes), then:</p>
<p>$$\lambda(n) = \mathrm{lcm}(p-1, q-1) \approx \frac{\varphi(n)}{2}.$$</p><p>When you compute the private exponent \(d\) as the modular inverse of \(e\) (a small number) modulo \( \varphi(n)\) versus modulo \(\lambda(n)\), the range from which \(d\) is chosen is roughly twice as large in the former case. That means the typical \(d\) when computed modulo \(\varphi(n)\) can be about twice as large as when computed modulo \(\lambda(n)\). A larger \(d\) means that during decryption (or signing) the modular exponentiation \(c^d \mod n\) takes slightly more time.</p>
<p>Intuitively, using \(λ(n)\) ensures we don’t “overshoot” the exponent required for the modular arithmetic to cycle back to 1.</p>
<p>A smaller \(d\) makes every RSA decryption and signature operation faster. For instance, if \(λ(n)\) is roughly half of \(φ(n)\), then \(d\) will have one less bit than it would otherwise, cutting the exponentiation work by about 50%. This is a free performance gain, as we aren’t changing the security assumptions or the key size \(n\), just using the mathematically tight value for the exponent. The RSA algorithm’s security is not weakened by this and now the \(d\) is different but functionally equivalent.</p>
<h3 id="heading-the-carmichael-function-in-modern-implementations">The Carmichael Function in Modern Implementations</h3>
<p>The critical property for RSA (\(e·d ≡ 1 ~mod ~~λ(n)\)) is both necessary and sufficient for correct decryption, thanks to Carmichael’s theorem. So there’s no need for \(d\) to also satisfy the stronger condition modulo \(φ(n)\).</p>
<p>By switching to computing \(d ~ modulo ~~ λ(n)\) (i.e., \(d = e^{-1} ~mod ~~λ(n)\)), we directly get the smallest working private exponent. Ronald Rivest himself noted this optimization in his 1999 seminal paper [4], stating that solving for \(d\) using \( λ(n)\) instead of \(φ(n)\) is slightly preferable because it can result in a smaller value for d.</p>
<p>Over time, the use of \( λ(n)\) in RSA moved from an academic suggestion to an industry standard. Today’s cryptographic standards explicitly acknowledge or require the \(λ(n)\) approach.</p>
<p>For example, the official RSA standard (PKCS #1 v2.2, RFC 8017 [2]) defines the RSA key generation in terms of \(λ(n)\). It specifies that the private exponent \(d\) is chosen such that \(e·d ≡ 1 (mod λ(n))\) (with \(λ(n) = lcm(p–1, q–1)\)). In other words, PKCS #1 expects the Carmichael function to be used for the modulus of the exponent. Likewise, NIST’s FIPS 186-4 (Digital Signature Standard) mandates that \(d\) be less than \(λ(n)\).</p>
<p>Any RSA key where \(d\) is larger than \(λ(n)\) is considered non-compliant in those strict contexts. This effectively forces implementations to use the smaller \(λ(n)\)-based exponent, since any “oversized” \(d\) can be reduced \(mod ~~λ(n)\) to meet the criterion.</p>
<p>Standards such as FIPS 186-4 [1] (the Digital Signature Standard) and RFC 8017 [2] (which specifies PKCS#1 v2.2 for RSA Cryptography) include requirements or recommendations that imply the private exponent \(d\) should be as small as possible and ideally less than \( \lambda(n)\). Using \(\lambda(n)\) (the least common multiple of \(p-1\) and \(q-1\)) directly produces the smallest valid \(d\), whereas using \(\varphi(n)\) often results in a \(d\) that is larger than necessary. This not only improves performance (by reducing the number of modular multiplications needed during decryption/signing) but also helps maintain interoperability with protocols that expect d to be below a certain size.</p>
<p>The Python cryptography library (PyCA cryptography) explicitly documents [5] that it uses Carmichael’s totient to generate the “smallest working value of \(d\),” noting that older implementations (including the original RSA paper) used Euler’s totient and ended up with larger exponents. OpenSSL also uses the Carmichael function in their low-level RSA APIs [6].</p>
<p>This shift to the Carmichael function ensures that under the hood your RSA key is a bit more efficient than the ones from the late 1970s while providing the same level of security.</p>
<h2 id="heading-issues-with-raw-rsa">Issues with Raw RSA</h2>
<p>Raw or “Textbook” RSA soon turned out to be insecure when two major weaknesses were discovered.</p>
<p>The operations involved in RSA are entirely deterministic, which means that for a given plaintext \(m\), encryption always produces the same cipher text \(C = m^e \mod n\).</p>
<p>An eavesdropper or an attacker, say Eve, can guess or derive plain texts by exploiting the predictability of outputs. Since RSA encryption is a public operation, an attacker can encrypt likely messages and compare results to a target cipher text – a trivial chosen plaintext <em>attack</em>.</p>
<p>Besides this, textbook RSA is also malleable. This means that its algebraic structure allows attackers to manipulate cipher texts in meaningful ways. For instance, given a cipher text \(C = RSA(M)\), an attacker can multiply it by the encryption of a known value (say, r) to produce a new cipher text \(C’ = C · r^e ~~mod ~n\), which decrypts to the plaintext \(M·r\). When the legitimate receiver decrypts \(C'\), the result is \(M·r\), from which the attacker can often recover \(M\).</p>
<p>Let’s understand these vulnerabilities with a small practical example.</p>
<h2 id="heading-exploiting-textbook-rsas-determinism-and-malleability">Exploiting Textbook RSA’s Determinism and Malleability</h2>
<h3 id="heading-key-generation-setup"><strong>Key Generation (Setup)</strong></h3>
<p>For our toy example, we’ll choose small prime numbers and generate an RSA key pair:</p>
<p>Let’s select the values of \(p =3\) and \(q=11\). Both of these values are prime. Now, compute the modulus and Totient Function as follows:</p>
<p>$$\begin{gather} \begin{split} n = p × q = 3 × 11 = 33 \\ φ(n) = (p – 1) × (q – 1) = 2 × 10 = 20 \end{split} \end{gather}$$</p><p>Now choose the public exponent. Let’s consider \(e=3\) since it is coprime with \( φ(n) = 20\), and \(gcd(3, 20) = 1\).</p>
<p>Now let’s compute the private exponent. We know that d is the modular inverse of \(e ~~mod ~φ(n)\). We need to find d such that \((d × e) ≡ 1~~ (mod ~20)\). Using this knowledge we can compute \(d = 7\) as \(3 × 7 = 21 ≡ 1 ~~ (mod~ 20)\).</p>
<p>Finally, the public key is \((n = 33, ~ e = 3)\) and the private key (secret) is \(d = 7\).</p>
<h3 id="heading-encryption-process">Encryption Process</h3>
<p>Now, let’s encrypt a simple message using the above key. Let us select our plaintext to be \(M = 4\). The cipher text in this case would be:</p>
<p>$$\begin{gather} \begin{split} C = 4^3 ~~mod ~33 \\ C = 64 ~~mod ~33 \\ C = 64 – 33×1 = 31 \end{split} \end{gather}$$</p><p>To consolidate the findings so far, if we encrypt message \(4\) with the public key \((e=3, n=33)\), we will produce the cipher text \(31\). Now, let’s try the exploits.</p>
<h3 id="heading-determinism-exploit-ciphertext-guessing-attack">Determinism Exploit (Ciphertext Guessing Attack)</h3>
<p>Textbook RSA is deterministic – the same plaintext always yields the same ciphertext (with no randomness involved). An attacker who intercepts the ciphertext \(C=31\) can exploit this by encrypting likely plaintext guesses and comparing results:</p>
<p>The adversary, say Eve, will try encrypting candidate plaintexts with the public key and see which one produces \(31\). They may pick randomized values to increase their efficiency:</p>
<p>$$\begin{gather} \begin{aligned} Guess~ M = 1 ⇒ 1^3~~ mod ~33 = 1 \\ Guess~ M = 2 ⇒ 2^3~~ mod ~33 = 8 \\ Guess~ M = 3 ⇒ 3^3~~ mod ~33 = 27 \\ Guess~ M = 4 ⇒ 4^3~~ mod ~33 = 31 \\ \end{aligned} \end{gather}$$</p><p>By simply comparing ciphertexts, the attacker finds that encrypting \(4\) yields 31, which matches the intercepted ciphertext. Thus, the attacker learns the original plaintext \(M\) was \(4\). This is possible because there’s no randomization in textbook RSA – an eavesdropper can identify a message by trial encryption of guesses, breaking confidentiality if the message space is small or guessable.</p>
<h3 id="heading-malleability-exploit-ciphertext-manipulation-attack">Malleability Exploit (Ciphertext Manipulation Attack)</h3>
<p>Raw RSA is also malleable. This means an attacker can take a ciphertext and modify it in a way that results in a predictable change in the decrypted plaintext. Let’s understand how this works.</p>
<p>RSA has a multiplicative property, that is, multiplying two ciphertexts corresponds to multiplying their plaintexts before encryption:</p>
<p>$$E(M_1) \cdot E(M_2) \mod n = (M_1^e \mod n)\times(M_2^e \mod n) \mod n = (M_1 \cdot M_2)^e \mod n$$</p><p>The sequence diagram below explains how the malleability exploit works in naive RSA.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741314973046/6be306c5-3ca6-4ea8-8daf-d1937b6459df.png" alt="Sequence Diagram: Malleability Exploit" class="image--center mx-auto" width="828" height="640" loading="lazy"></p>
<p>Alice sends a ciphertext to Bob after the initialization phase. Note that by this point, n and e are public knowledge. Eve intercepts this ciphertext by using mechanisms such as a MiTM (Man in the Middle) attack.</p>
<p>Now, Eve picks a known value to manipulate the message. Let’s say the attacker chooses \(X = 2\) (with the intent to double the original plaintext).</p>
<p>Then they compute the encryption of X using the public key:</p>
<p>$$E(X) = 2^3 \mod 33 = 8.$$</p><p>Now, Eve multiplies the original ciphertext by this value (mod n) to get a new ciphertext:</p>
<p>$$\begin{gather} \begin{split} C{\prime} = C \times E(X) \mod n = 31 \times 8 \mod 33 \\ C{\prime} = 248~~ mod~ 33 = 248 – 33×7 = 248 – 231 = 17 \end{split} \end{gather}$$</p><p>This new ciphertext \(C{\prime}\) is the encryption of the product of the original plaintext and \(2\). If we directly encrypted \(M \times X = 4 \times 2 = 8\) with RSA, we would get \(8^3 \mod 33 = 512 \mod 33 = 17\). This means that \(C′\) corresponds to the plaintext \(8\), which is the original message \(4\) multiplied by \(2\).</p>
<p>In a real-world chosen ciphertext attack, the attacker may have access to a decryption oracle or observe a system response that reveals information about \(M{\prime}\). The decryption result \(8\) is exactly \(M \times 2\) (the original message multiplied by the attacker’s chosen factor). Knowing the factor \(X = 2\), the attacker can deduce the original message by dividing: \(8/ 2 = 4\).</p>
<p>Note that Eve has not broken the mathematical foundations behind RSA here. They have only used the public key to compute an encryption of \(2\), and then combined it with the intercepted ciphertext. They don’t know the original plaintext yet, but they have manipulated the ciphertext in a way that they know the new plaintext is twice the original message.</p>
<h2 id="heading-low-exponent-attacks">Low-Exponent Attacks</h2>
<p>Beyond determinism and malleability exploits, textbook RSA is also vulnerable to Low-Exponent Attacks. Using a small public exponent like \(e = 3\) (or sometimes \(17\)) was popular because it used to speed up encryption and signature verification. But this soon turned out to be a security concern.</p>
<p>When RSA uses a small public exponent (say, \(e = 3\)) and the plaintext is very short (so that \(M^3\) is smaller than the modulus \(n\)), the encryption does not “wrap around” modulo \(n\). Mathematically:</p>
<p>$$c = M^3 \mod n = M^3 \quad \text{(if \( M^3 &lt; n \))}$$</p><p>Let’s understand this with an easy example:</p>
<p>Consider our plaintext to be: \(M = 5\). We compute \(M^3\) as \(M^3 = 5^3 = 125\).</p>
<p>Now assume \(n\) is a \(4096\)‑bit number which is large compared to \(125\). In this case, the ciphertext is simply \(c = 125\). Eve intercepting \(c = 125\) can compute the cube root of \(125\) to get the plaintext: \(\sqrt[3]{125} = 5\) thus recovering \(M\) directly.</p>
<p>This shows that if \(M\) is small enough, the ciphertext leaks the plaintext when \(e\) is low.</p>
<h2 id="heading-hastads-broadcast-attack-low-exponent-meets-multiple-recipients">Håstad’s Broadcast Attack: Low Exponent Meets Multiple Recipients</h2>
<p>In 1985, Johan Håstad’s highlighted the broadcast attack that illustrates the danger of a low exponent, \(e\), when the same message is sent to multiple parties as a broadcast.</p>
<p>Imagine Alice wants to send the same plaintext message M to three different recipients. Each recipient has their own RSA public key with modulus \(N_1, N_2, N_3,\) but for speed all use \(e = 3\) (a common practice historically). Alice encrypts \(M\) with each public key, yielding ciphertexts:</p>
<p>$$\begin{gather} \begin{split} C_1 = M^3 \bmod N_1 \\ C_2 = M^3 \bmod N_2 \\ C_3 = M^3 \bmod N_3 \end{split} \end{gather}$$</p><p>Eve, who intercepts all three \(C_1, C_2, C_3\) can recover <em>M</em> without breaking any single RSA key.</p>
<p>Since each \(N_i \) is different (and we assume they are pairwise coprime, as RSA keys should be), the attacker can use the Chinese Remainder Theorem (CRT) to combine the three congruences \(x \equiv C_i \pmod{N_i}\). Note that at this point Eve only has \(C_1\), \(C_2\) and \(C_3\). They do not have the plaintext \(M\) or \(M^3\) and yet they can reconstruct \(M^3\) with the intercepted data. To understand the Chinese Remainder Theorem and this reconstruction, you may follow this: <a target="_blank" href="https://www.youtube.com/watch?v=Mt9v7-xBuaA">CRT, RSA, and Low Exponent Attacks | Youtube</a>.</p>
<p>There is a unique solution modulo \(N_1N_2N_3\) for \(x\), and that solution turns out to be an integer, \(x = M^3\) (because the true integer \(M^3\) is smaller than the product \(N_1N_2N_3\) of each \(M &lt; N_i \) ). In essence, CRT lets Eve reconstruct \(M^3\) exactly. Once they have \(M^3\) as an ordinary integer, they simply take the cube root to find \(M\). There’s no need to factor any modulus or invert the RSA function – the math falls out due to the low exponent.</p>
<p>The sequence diagram below aims to provide a high-level understanding of the attack:</p>
<p><img src="https://mermaid.ink/img/pako:eNqNlN9P2zAQx_-VmyWkIpWqSeostTQkFvawh-6h7AFNEcgk19RSY3e2A3RV__dd-gNoExB5is_f-_jum3PWLDcFMsEc_q1R53itZGlllWmgZymtV7laSu3haqFyhN4N6gLteXv_u3mA3hRppZCWQYckldYs3orCDtG1fMS3mqhD86ORXBWPaJ20KxLsJL-MRzAUPSlW7NdPlOvAG3AUhwqdkyXCpIn4uUUEezjVDQ7MYxJcXF62OxXQbDugyBytx2cPvfQ-gG8wuYugMgXo-4MfHbwOW7qJ4RExfJ_Y9rAbGB0Boxcjz862Fivt0ea4JNP2ZjnoFcZ7LEBaa57cvoST7wEX79j08xVI_nyQ22nJUXb4QXZX-0fJUXtiTkACKN1o522dU0rvdufUOdRO6RLS6e8DolUBFdCCpaZa1p7sS-sHhKkxHswMbj9RRhOgsWw2aXTnJLOqVFouXuf3S6ZZn1VoK6kKusrrBpoxEleYMUGvBc5kvfAZy_SGpLL25malcyaoPeyzellIf7j5TMzkwlGUbhoTa_bMRMCHAx7xMU84j5I4HPfZiolRMhgNR-NkxOM45FE8ijd99s8YIgwH4zCMEp4EQTLkX0MebHF_tpu7M7FQ3tjJ7uez_Qf1mTV1OX85v7RNNzu13Q53amrtmUjGm_8gLoH2?type=png" alt="Sequence Diagram: Håstad’s Broadcast Attack" width="1329" height="664" loading="lazy"></p>
<p>Now let’s see this attack in action with a sample:</p>
<p>Suppose three different RSA public keys all use exponent \(e=3\), with moduli \( n_b = 187\) (for Bob),<br>\(n_c = 115 \) (for Carol), and \(n_d = 87\)  (for Dave).</p>
<p>These \(n_i\) are pairwise coprime (\(gcd\) of each pair is \(1\)). Now assume the same plaintext message \(M\) is encrypted with each public key. Let’s take a concrete \(M\). For example with \(M=42\), we will have:</p>
<p>$$\begin{gather} \begin{split} c_b = M^3 \bmod n_b \\ c_c = M^3 \bmod n_c \\ c_d = M^3 \bmod n_d \\ \end{split} \end{gather}$$</p><p>On calculating these, we have:</p>
<p>$$\begin{gather} \begin{split} c_b = 42^3 \bmod 187 = 36 \\ c_c = 42^3 \bmod 115 = 28 \\ c_d = 42^3 \bmod 87 = 51 \\ \end{split} \end{gather}$$</p><p>So the three ciphertexts observed are \(36\), \(28\), and \(51\), respectively. Eve who knows \(n_b, n_c, n_d\) and these ciphertexts can now recover \(M\) as follows:</p>
<ol>
<li><p>Eve will compute the total modulus \(N = n_b \cdot n_c \cdot n_d = 187 \times 115 \times 87 = 1,870,935.\) (This is the modulus for the combined system of congruences).</p>
</li>
<li><p>Now Eve will compute the partial products for each congruence:</p>
</li>
</ol>
<p>$$\begin{gather} \begin{split} N_b = \frac{N}{n_b} = \frac{1,870,935}{187} = 10,005 \\ N_c = \frac{N}{n_c} = \frac{1,870,935}{115} = 16,269 \\ N_d = \frac{N}{n_d} = \frac{1,870,935}{87} = 21,505 \end{split} \end{gather}$$</p><ol start="3">
<li><p>At this point, Eve needs the inverses of each \(N_i\) modulo its corresponding \(n_i\):</p>
<ul>
<li><p>First Eve computes \(M_b = (N_b)^{-1} \bmod n_b\), i.e. the number \(M_b\) such that \(N_b \cdot M_b \equiv 1 \pmod{187}\). In this case, \(N_b = 10005\). Using the extended Euclidean algorithm, Eve can find \(M_b = 2\) (since \(10005 \times 2 = 20010 \equiv 1 \pmod{187}\)).</p>
</li>
<li><p>Then Eve computes \(M_c = (N_c)^{-1} \bmod n_c\). Here \(N_c = 16269\). The inverse mod \(115\) turns out to be \(M_c = 49\) (For verification: \(16269 \times 49 \equiv 1 \pmod{115}\)).</p>
</li>
<li><p>Next up, Eve computes \(M_d = (N_d)^{-1} \bmod n_d\). For \(N_d = 21505\), the inverse mod \(87\) is \(M_d = 49\) as well (coincidentally the same value in this case, since \(21505 \times 49 \equiv 1 \pmod{87}\)).</p>
</li>
</ul>
</li>
</ol>
<p>Now Eve reconstructs the combined value using the Chinese Remainder Theorem for three congruencies. The construction of this formula is beyond the scope of this handbook, but to completely understand how this springs into action, you may go through this video: <a target="_blank" href="https://www.youtube.com/watch?v=Mt9v7-xBuaA">CRT, RSA and Low Exponent Attacks | Youtube</a>.</p>
<p>$$C \;=\; c_b \cdot N_b \cdot M_b \;+\; c_c \cdot N_c \cdot M_c \;+\; c_d \cdot N_d \cdot M_d \pmod{N}$$</p><p>On substituting the numbers:</p>
<p>$$C = 36 \cdot 10005 \cdot 2 \;+\; 28 \cdot 16269 \cdot 49 \;+\; 51 \cdot 21505 \cdot 49 \pmod{1,870,935}$$</p><p>Let’s carefully evaluate each term:</p>
<p>$$\begin{gather} \begin{split} 36 \cdot 10005 \cdot 2 = 720,360 \\ 28 \cdot 16269 \cdot 49 = 22,341,348 \\ 51 \cdot 21505 \cdot 49 = 5,37,40,995 \\ \end{split} \end{gather}$$</p><p>Summing these gives a raw total of \(7,20,360 +  2,23,21,068 + 5,37,40,995 = 7,67,82,423\). Now reduce this modulo \(N = 1,870,935\):</p>
<p>$$\begin{align} \begin{split} C \equiv 7,67,82,423 \pmod{1,870,935}\\ C = 74,088 \\ \end{split} \end{align}$$</p><p>Now Eve will simply take the cube root of \(C: \sqrt[3]{74088} = 42\), which is the original plaintext.<br>Eve has successfully recovered \(M\).</p>
<p>The key takeaway from these attacks is that without proper defenses. RSA alone does not satisfy modern definitions of security. It is not resistant to chosen-plaintext or chosen-cipher text attacks. This gap between the theoretical one-way function (RSA’s trapdoor permutation) and a secure encryption scheme became evident as implementers found that naive RSA could be “broken” by various clever tricks.</p>
<p>To counter these weaknesses, standards bodies introduced padding schemes to strengthen RSA encryption. In the following sections, you will learn about each of these paddings schemes and how they’ve been exploited over the years.</p>
<h2 id="heading-introduction-to-padding-schemes-in-rsa">Introduction to Padding Schemes in RSA</h2>
<p>Before we dive into the padding schemes and how it helps our case, let’s quickly recap the need for padding in RSA.</p>
<p>Textbook RSA encryption is deterministic. The same plaintext always produces the same ciphertext under a given public key. This determinism makes raw RSA insecure. An attacker can guess possible messages, encrypt them with the public key, and compare with the target ciphertext to see which guess matches.</p>
<p>Beyond determinism, small-exponent attacks illustrate why padding is critical. If the message \(m\) is too small relative to the modulus, raising it to a small public exponent (like \(e=3\)) might not wrap around \(N\). Padding the plaintext with random data before encryption remedies these problems by making the ciphertext unpredictable and ensuring \(m^e\) spans the modulus’ range.</p>
<h2 id="heading-public-key-cryptography-standards-pkcs1-v15"><strong>Public Key Cryptography Standards (PKCS#1 v1.5)</strong></h2>
<p>In 1998, Kaliski and RSA Laboratories introduced PKCS#1 v1.5 to the world in a public publication [7]. In PKCS#1 v1.5, every RSA‐encrypted message is wrapped inside a special “encryption block” \(EB\). This block ensures that the raw message is both the right size for RSA and padded in a way that’s hard to tamper with.</p>
<p>In this scheme, the plaintext is padded to the size of the modulus \(N\) (in bytes) as:</p>
<p>$$EB = 00 ~||~ BT ~||~ PS ~||~ 00 ~||~ M$$</p><p>Here, \(0x00\) (Leading Zero Byte) is always at the front. It ensures that, when the concatenated string \(EB\) is converted to a big‐endian integer, the value is less than the RSA modulus (that is, we don’t end up with a number too large for RSA to handle). You will better appreciate this fact when we dive into the mathematics behind this.</p>
<p>The next octet is the Block Type, \(BT\), which tells us the “type” of padding being used. The standard defines three possible \(BT\) values: \(00, 01, \) and \(02\)- to support different operations. For example, \(BT=00\) and \(BT = 01\) is used for private-key operations (such as digital signatures) and \(BT = 02\) is used for public-key operations. For encryption under PKCS#1 v1.5, this is always \(0x02\). It’s basically a label that says, “This is an encryption block, not something else”.</p>
<p>The next block is the Padding String \(PS\). This is a string of nonzero random bytes. This is crucial for security because it introduces randomness into each encryption. If the same message is encrypted multiple times, these random bytes ensure that each ciphertext looks different, foiling many simple attacks that rely on seeing repeated patterns.</p>
<p>The next octet, \(0x00\), is a Delimiter<strong>.</strong> This single zero byte marks the end of the padding. During decryption, this helps the recipient quickly identify where the padding stops and the real message begins.</p>
<p>Finally, we have the actual data you want to protect – \(M\). Once the recipient has verified the padding, they know exactly where to find this message.</p>
<p>This mechanism helped solve the deterministic issue of naive RSA. In the next sections, let’s understand the mathematics involved in PKCS#1 v1.5 padding and its security implications.</p>
<h3 id="heading-the-mathematics-behind-pkcs1-v15">The Mathematics Behind PKCS#1 v1.5</h3>
<p>Before we begin, let’s get our symbols and abbreviations correct. We will use upper-case symbols (such as \(EB\)) to denote octet strings and bit strings. We will use lower-case symbols (such as \(n\)) to denote integers.</p>
<p>In PKCS#1 v1.5, we will use \(k\) to represents the length of the RSA modulus \(n\) in bytes. For example, if you have a \(1024\)-bit RSA key, then the RSA modulus \(n\) is a \(1024\)-bit number. Since there are \(8\) bits in a byte, if your RSA modulus is \(L\) bits long, then:</p>
<p>$$k = \left\lceil \frac{L}{8} \right\rceil = \frac{1024}{8} = 128 \text{ bytes}$$</p><p>The total length of the encryption block will be equal to this RSA key length \(k\) (in bytes). Now here the length of the data \(M\) shall not be more than \(k-11\) octets, since the 11 bytes are consumed by the blocks – \(0x00  ~||~ 0x02 ~||~ PS ~||~ 0x00\). This limitation guarantees that the length of the padding string \(PS\) is at least eight octets, which is a security condition in PKCS#1v1.5:</p>
<p>$$∣PS∣=k~−∣M∣−~3$$</p><p>For example, with a \(1024\)-bit RSA modulus, the value of \(k\) comes out to be \(128\). Here Alice could encrypt up to \(128 - 11 = 117\) bytes of data. The \(11\) bytes are used for the \(0x00  ~||~ 0x02 ~||~ PS ~||~ 0x00\) structure. The random \(PS \) ensures that each encryption of the same message produces a different ciphertext, preventing the deterministic encryption problem.</p>
<p>RSA doesn’t directly operate on the bytes. Once the padded string \(EB\) is ready, it needs to be converted into an integer guided by the Octet String to Integer Primitive (OS2IP) formula:</p>
<p>$$x = \sum_{i=1}^{k} 2^{8(k - i)} \,\mathrm{EB}_i$$</p><p>where \(EB_i\) are the octets of \(EB\) from first to last. In other words, \(EB_1\) (the first byte) is the most significant byte, and \(EB_k\) (the last byte) is the least significant. Now Alice can simply encrypt this block using \(C = x^c \mod n\).</p>
<p>To solidify our learnings so far, let’s apply this to a sample plaintext and find the padded blocks.</p>
<p>Let’s assume the RSA modulus is \(8\) bytes long (\(k=8\)). Suppose we want to encrypt a message \(M\) that is \(2\) bytes long. Then the padding string \(PS\) must fill the remaining space:</p>
<p>$$Total ~ bytes=k=8=1(0x00)+1(BT)+∣PS∣+1(delimiter)+∣M∣$$</p><p>Since \(∣M∣=2\) and there are \(∣M∣=2∣\) fixed bytes, can find the required length of the padding string:</p>
<p>$$∣PS∣=8−3−2=3 ~ bytes$$</p><p>Let’s pick 3 arbitrary nonzero bytes for \(PS\), say - \(0xA3, ~0x5F, ~0xC2\). And let’s say the message is the ASCII text “Hi”. In hexadecimal, that’s: \(0x48\) for 'H' and \(0x69\) for 'i'.</p>
<p>Thus, the complete encryption block becomes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742368983011/f682532c-6664-4197-8e77-60ea034f82c5.png" alt="Sample Encryption Block in PKCS#1 v1.5" class="image--center mx-auto" width="1191" height="437" loading="lazy"></p>
<p>Now we will convert this octet string to an integer using the OS2IP formula we discussed above:</p>
<p>$$x = \sum_{i=1}^{k} 2^{8(k - i)} \,\mathrm{EB}_i$$</p><p>For our example, with \(k=8\) the conversion is:</p>
<p>$$x=  0x00×256^7+0x02×256^6+0xA3×256^5+0x5F×256^4+0xC2×256^3+0x00×256^2+0x48×256^1+0x69×256^0$$</p><p>Note that the hexadecimal values can be converted to decimal as needed. For instance, \(0xA3 = 163, 0x5F = 95, 0xC2 = 194, 0x48 = 72,\) and \(0x69 = 105\).</p>
<p>There is an interesting observation in the application of this formula. Because the first two bytes are fixed (\(0x00\) and \(0x02\)), the integer \(x\) has a known lower bound. The contribution of the first two bytes is:</p>
<p>$$0×256^ 7 +2×256^ 6 =2×256^ 6$$</p><p>The rest of the bytes (\(PS\), the delimiter, and \(M\)) add some value that is at least \(0\) and at most just less than \(256^6\) (since the second byte is fixed as \(0x02\) and cannot be \(0x03\)). Thus, \(x\) is in the range:</p>
<p>$$2×256 ^ 6 ≤x&lt;3×256 ^ 6$$</p><p>This property which makes the range predictable, paved the way for the Bleichenbacher attack (also known as the “padding oracle” attack). If a system reveals whether a decrypted block is “correctly padded,” an attacker can systematically probe different ciphertexts and narrow down the plaintext – because the attacker knows it must lie in that narrow range. Let’s take a detailed look at the Bleichenbacher attack in the next sections and understand how the exploit works.</p>
<h2 id="heading-the-bleichenbacher-attack">The Bleichenbacher Attack</h2>
<p>In 1998, Daniel Bleichenbacher published a seminal paper [8] demonstrating an adaptive chosen-ciphertext attack against RSA with PKCS#1 v1.5 padding. The Bleichenbacher Attack, also dubbed as the “million messages” attack, demonstrated that if an attacker has access to an oracle that tells whether a submitted ciphertext decrypts to a properly padded plaintext (that is, whether the PKCS#1 v1.5 formatting is correct), the attacker can gradually recover the full plaintext. Let’s break down how this attack works:</p>
<p>First, Eve needs an Oracle. The attack assumes the attacker can query a system, such as an SSL/TLS server, and find out if a given ciphertext \(C\) is PKCS#1 v1.5 conformant. In the 1998 paper, Bleichenbacher exploited the fact that a TLS server, when presented with an improperly padded RSA-encrypted premaster secret, would respond with a specific error alert if the padding was wrong. Essentially, the server acted as an oracle: it would decrypt \(C\) with its private key and simply tell the attacker “padding OK” or “padding error” (the error could be timing-based or an explicit alert).</p>
<p>Note that the oracle does not reveal the plaintext. It only reveals a single bit of information at a time: “valid padding or not.” This might seem harmless, but Bleichenbacher showed that it’s enough to eventually recover the plaintext.</p>
<p>To quickly recap, the attacker’s goal is to find the unknown message integer \(m\) (the PKCS#1-padded plaintext as an integer) given its ciphertext \(C = m^e \bmod N\), using the oracle. We know that if \(m\) is properly padded, it lies in a specific numeric range: \(2B \le m &lt; 3B\) where \(B = 2^{8*(k-2)}\), as defined earlier.</p>
<p>If \(k=128\) bytes, then \(B=2^{8*126}\), and a correctly padded \(m\) will start with \(0x00 ~||~0x02\), so it’s between \(2B\) and \(3B\). The attacker, Eve, initially only knows that \(m\) is in the range \([2B, 3B)\).</p>
<p>In the Bleichenbacher Attack, Eve will exploit RSA’s multiplicative property. They will choose a number \(s\) (called the multiplier) and compute a new ciphertext \(C' = (C  s^e) \bmod N\). This \(C'\) here corresponds to a new plaintext: \(m' = m  s \bmod N\) (because \(C' \equiv m^e * s^e \equiv (ms)^e \pmod{N}\)).</p>
<p>To begin the attack, Eve finds some \(s_0\) such that \(C_0 = C * (s_0)^e \mod N\) yields a valid padding. This is referred to as the Blinding step. This is usually easy – for example, \(s_0\) can be chosen so that \(m * s_0\) is just slightly above \(N\), which almost certainly will wrap around and land in \([2B,3B)\). The attacker does not know \(m\) to verify this directly. They rely on the padding oracle’s yes/no response to infer that the blinded plaintext \((m×s_0)\mod  N\) falls in the correct range.</p>
<p>If the oracle returns “valid padding” for a given \( s_0\), it tells the attacker that \(s_0 \mod N\)lies between \(2B\)and \(3B\). Mathematically:</p>
<p>$$2B≤(m×s_0)~mod  N&lt;3B$$</p><p>Now, Eve will try to try to narrow down this range in a loop, which is often referred to as the interval having step. Initially, Eve had one wide interval \([a, b] = [2B, 3B)\) that contains \(m\). In each iteration, Eve tries increasing values of \(s\) (starting from a certain minimum) until the oracle returns “padding OK” for \(C' = C_0 * s^e\). Suppose this happens at some \(s = s_i\). Given this feedback, Eve now knows:</p>
<p>$$2𝐵 ≤  (𝑚 × 𝑠_i) ~ mod 𝑁 &lt; 3𝐵$$</p><p>This congruence implies there exists some integer \(r\) such that:</p>
<p>$$2B  ≤ ( m×s_i)−rN  &lt;  3B$$</p><p>Rearranging, we get a constraint on \(m\):</p>
<p>$$\frac{2B+rN}{s_i}  ≤  m  &lt;  \frac{3B+rN}{s_i}$$</p><p>Eve doesn’t know \(r\) outright, but they can solve for the possible range of \(r\) by considering the current interval \([a,b]\) for \(m\). Essentially, Eve uses the previous bounds on \(m\) to guess which \(r\) would make the inequality true, then updates the new bounds \([a, b]\) as the intersection of all possible solutions for \(m\). This dramatically shrinks the interval.</p>
<p>Each oracle query yields such a constraint. Eventually, the interval \([a,b]\) collapses to a single value, \([a,a]\). Now, Eve can find the plaintext using:</p>
<p>$$m = (a × s_i^{-1}) ~ mod N$$</p><p>At that point, Eve has recovered the entire padded plaintext \(m\), and by stripping off the padding, the original message itself.</p>
<p>The sequence diagram below consolidates our learning of the attack:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742498318544/6e297215-ca3e-451d-9574-117c0f8a12cb.png" alt="Sequence Diagram: The Bleichenbacher’s Attack" class="image--center mx-auto" width="819" height="640" loading="lazy"></p>
<p>The Bleichenbacher attack showed that the format of the padding in PKCS#1 v1.5 leaked just enough info to enable a full private-key operation (decrypting the message) without ever factoring N. The attack leveraged the fact that it’s possible to craft ciphertexts that will decrypt to a valid-looking plaintext without knowing the plaintext​. In essence, PKCS#1 v1.5 padding allowed about \(1\) in \(2^{16}\) chance (roughly) for a random blob to appear as “valid padding.” That was enough for an adaptive attack to succeed with feasible queries.</p>
<p>This is precisely what later padding designs like OAEP fixed. OAEP’s design makes such random valid ciphertexts astronomically unlikely (plaintext aware). We will learn about RSA-OAEP in the next sections.</p>
<p>To mitigate the Bleichenbacher attack without immediately changing the padding scheme, practitioners implemented defensive measures. For example, TLS should treat all decryption failures the same way (so an attacker can’t distinguish padding vs. other errors), and servers would generate a fake premaster secret on padding failure to continue the handshake and avoid timing leaks. Nonetheless, the safest course has been to deprecate PKCS#1 v1.5 encryption in favor of schemes like RSA-OAEP.<a target="_blank" href="https://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf#:~:text=plaintext%20is%20PKCS%20conforming,chosen%20ciphertexts%3B%20thus%2C%20we%20show">​</a></p>
<h2 id="heading-optimal-asymmetric-encryption-padding-oaep">Optimal Asymmetric Encryption Padding (OAEP)</h2>
<p>By the end of 1995, Bellare and Rogaway proposed Optimal Asymmetric Encryption Padding (OAEP) with the goal of achieving provable security. This padding aimed to make RSA encryption resistant not just to passive attacks but also to adaptive chosen-ciphertext attacks. In other words, even if an attacker can trick a system into decrypting chosen ciphertexts (as an “oracle”), they should learn nothing useful about the plaintext. OAEP was subsequently standardized in PKCS#1 v2.0 (published as RFC 2437 in 1998) and later versions.</p>
<p>Compared to PKCS#1 v1.5, OAEP has a more complex encoding that uses hash functions and a mask generation function (MGF) to thoroughly randomize the plaintext before RSA encryption, providing stronger guarantees.</p>
<p>OAEP’s design can be viewed as a two-layer Feistel-like network using a random seed. It takes the input message and randomizes it in a way that is reversible only with the correct seed. The scheme was proven plaintext-aware in the random oracle model which means that an adversary cannot concoct a valid ciphertext without knowing the corresponding plaintext. If an attacker tries to forge or tamper with ciphertexts, they almost surely produce an <em>invalid</em> padding that will be rejected. This property directly counters padding-oracle attacks.</p>
<p>OAEP (with a proper hash/MGF) is semantically secure against adaptive chosen ciphertext attacks, assuming RSA is hard to invert and treating the hash functions as random oracles. Unlike PKCS#1 v1.5, which lacked a formal proof, OAEP comes with a proof sketch that breaking RSA-OAEP is as hard as breaking RSA itself.</p>
<p>In practice, this means OAEP drastically reduces the risk of any padding oracle: an attacker can no longer easily find ciphertexts that slip through the padding check except by brute force which has a \(2^{-hLen*8}\) success probability. For example, the success probability with SHA-1 would be \(2^{-160}\).</p>
<p>The block diagram below is a visual representation of the OAEP encoding schema:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742663541136/1c418939-80f6-45ea-8667-cacdc5cdab2b.png" alt="Optimal Asymmetric Encryption Padding" class="image--center mx-auto" width="1434" height="1102" loading="lazy"></p>
<p>Let’s understand what these mathematical notions mean and the workings of RSA-OEAP, up next.</p>
<h3 id="heading-the-mathematics-behind-oaep">The Mathematics Behind OAEP</h3>
<p>Optimal Asymmetric Encryption Padding requires a hash function for two operations we will discuss in this section. We will choose SHA-1 as a hash function in OAEP and \(hLen\) denotes the length in octets of the hash function output. We will later demonstrate why even MD5 or SHA-1 is a secure choice for OAEP even if it is not collision resistant.</p>
<p>Before we dive into the mathematics, let’s recap a few notations and define the main pieces we’ll be using:</p>
<p>In RSA, \(N\)is the modulus, and \(k\) is the size of \(N\) in <em>bytes</em>. For a \(2048\)-bit modulus, \(k=256\) bytes.<br>\(M \) is the message or plaintext to be encrypted. This plaintext must be short enough to fit into the padded block (at most \(k−2⋅hLen−2\) bytes). In our notation, \(Hash\) refers to the cryptographic hash function (for example, SHA-1, SHA-256) of output length \(hLen\). For example: If using SHA-1, \(hLen=20\) bytes.</p>
<p>We will also use an optional string associated with the message (often empty). This is the Label \(L\). If this label is empty, its hash is a fixed value. (For example: the SHA-1 of an empty string).</p>
<p>The hash of this label \(L\) is represented by \(lHash\), where \(lHash=Hash(L)\). As mentioned earlier, if \(L\) is empty, \(lHash\) is simply \(Hash('')\). This means that in any case \(lHash\) will hold a value.</p>
<p>We will also use a Mask Generation Function, \(MGF\), which is often mentioned as \(MGF1\). This function takes an input (seed or masked data) and produces an output of a specified length by iterating the underlying hash function. We’ll write \(MGF(input,length)\) to indicate “generate a mask of \(length\) bytes from \(input\)”.</p>
<p>Now that you are familiar with all the necessary notations, we are ready to begin the encoding step.</p>
<h4 id="heading-step-1-constructing-the-data-block-db">Step 1: Constructing the Data Block (DB)</h4>
<p>We will compute \(lHash=Hash(L)\). If \(L\) is empty, \(lHash\) is a constant (For example, the SHA-1 of the empty string).</p>
<p>Form the padding string \(PS\), the length of \(PS\) is chosen so that the entire block \(DB\) has length \((k−hLen−1)\) bytes. Numerically, \(PS\) has \((k−mLen−2⋅hLen−2)\) bytes of \(0x00\), where \(mLen\) is the length of the message \(M\).</p>
<p>Now we simply concatenate the blocks to generate the octet string for the Data Block (\(DB\)):</p>
<p>$$DB=lHash~∣∣~PS~∣∣~0x01~∣∣~M$$</p><p>Here the single byte \(0x01\) acts as a delimiter which marks where the zero padding ends and the actual message \(M\) begins. \(DB\) ends up being \((k−hLen−1)\) bytes.</p>
<h4 id="heading-step-2-generating-a-mask-for-the-data-block">Step 2: Generating a Mask for the Data Block</h4>
<p>First, we pick a random string called \(seed\) of length \(hLen\) bytes. For example, when using SHA-1 where \(hLen=20\), then we say that the seed consists of \(20\) random bytes.</p>
<p>Now we use the mask generation function, \(MGF\), on the \(seed\) to create a mask the same length as \(DB\):</p>
<p>$$dbMask=MGF(seed,k−hLen−1)$$</p><p>The idea is to spread the randomness of the seed across the entire \(DB\).</p>
<h4 id="heading-step-3-mask-the-data-block">Step 3: Mask the Data Block</h4>
<p>Now, we will Combine \(DB\) and \(dbMask\) with the bitwise \(XOR\) operation:</p>
<p>$$maskedDB=DB \oplus dbMask$$</p><p>This step “scrambles” \(DB\) with the random seed.</p>
<h4 id="heading-step-4-generate-a-mask-for-the-seed">Step 4: Generate a Mask for the Seed</h4>
<p>Next, we will produce a mask for the seed itself, based on \(maskedDB\):</p>
<p>$$seedMask=MGF(maskedDB,hLen)$$</p><p>This step simply ensures that the seed is not left in the clear.</p>
<h4 id="heading-step-5-mask-the-seed">Step 5: Mask the Seed</h4>
<p>Now we will combine the original seed and the new mask with an \(XOR\) operation:</p>
<p>$$maskedSeed=seed \oplus seedMask$$</p><p>Now the seed is also “scrambled” by the data block.</p>
<h4 id="heading-step-6-form-the-final-encoded-message-em">Step 6: Form the Final Encoded Message (EM)</h4>
<p>We are now ready to build our final block. Simply concatenate everything into a \(k\)-byte string:</p>
<p>$$EM=0x00~∣∣~maskedSeed~∣∣~maskedDB$$</p><p>The leading \(0x00\) byte ensures that when \(EM\) is interpreted as an integer, it’s less than the RSA modulus \(N\). At this point, \(EM\) is your OAEP-padded message of length \(k\).</p>
<h4 id="heading-step-7-covert-concatenated-string-to-integer">Step 7: Covert concatenated String to Integer</h4>
<p>Remember from our discussion before on PKCS#1v1.5 that RSA cannot directly operate on this concatenated string of bytes. We need to convert the \(EM\) block to a non-negative integer using the OS2IP formula:</p>
<p>$$x = \sum_{i=1}^{k} 2^{8(k - i)} \,\mathrm{EB}_i$$</p><h4 id="heading-step-8-perform-rsa-encryption">Step 8: Perform RSA Encryption</h4>
<p>Now that we have the encoded message (\(EM\)) as an integer \(x\), we are ready to perform RSA guided by the formula:</p>
<p>$$C =x^e \bmod N$$</p><p>where \((e,N)\) is the public key. The thus computed \(C\) is our ciphertext generated using RSA-OAEP.</p>
<p>When decrypting, the process is reversed: the recipient uses their private key \(d\) to compute \(m = c^d \bmod N\), recovers the \(EM\), then splits it into the \(0x00\), \(maskedSeed\), and \(maskedDB\), and uses the same \(MGF\) and hash function to unravel the \(XORs\) in reverse order​. Finally, they check that the recovered \(lHash'\) matches the expected hash and that the block contains the proper structure​ (\(...||0x01||...\)).</p>
<p>If any check fails, the padding is invalid. Only if everything checks out is the message \(M\) returned. The result is that an invalid ciphertext will almost always be detected and rejected without giving an attacker any useful information.</p>
<p>By design, OAEP effectively foiled the padding oracle problem. The chance that a random guess produces a valid OAEP encoding is negligible: on the order of \(2^{-hLen*8}\)). In fact, Daniel Bleichenbacher (after breaking PKCS#1 v1.5) advocated for exactly such a “plaintext-aware” padding where forging a valid padding is infeasible.</p>
<h2 id="heading-why-sha-1-or-md5-are-safe-in-rsa-oaep"><strong>Why SHA-1 or MD5 Are Safe in RSA-OAEP</strong></h2>
<p>Earlier in the section above, we mentioned that we’d be using SHA-1 for our mathematical formulation and examples. When you see SHA-1 or MD5 used in the context of RSA-OAEP, don’t let the fact that these hash functions are considered broken for collision resistance alarm you. If you notice carefully in the previous section, the hash functions serve two very specific roles that do not rely on their collision resistance. Let’s break them down one by one:</p>
<h3 id="heading-label-hashing"><strong>Label Hashing</strong></h3>
<p>The hash function is used to compute a fixed-length hash of an optional label \(L\) (often empty).</p>
<p>Now let’s see why is this safe in the context. This hash, called \(lHash\), acts as a domain separator. Its job is simply to ensure that the label is correctly associated with the ciphertext during decryption. As long as the label is chosen wisely (that is, not built from adversary-controlled parts), collision resistance isn’t critical here.</p>
<h3 id="heading-mask-generation-function-mgf1"><strong>Mask Generation Function (MGF1)</strong></h3>
<p>The hash function is also used inside \(MGF1\) to create a pseudorandom mask. This mask is applied both to the data block \(DB\) and to the random seed used in the encoding process.</p>
<p>In this context, the hash function is treated as a random oracle. The job is to spread the randomness of the seed across a larger block of data. For this purpose, properties like length extension or collision resistance are not relevant. What matters is that the output appears random, and even SHA-1 or MD5 can deliver that when used in this controlled, fixed-input scenario.</p>
<h2 id="heading-adoption-in-cryptographic-libraries-pkcs1-v15-vs-oaep">Adoption in Cryptographic Libraries (PKCS#1 v1.5 vs OAEP)</h2>
<p>After the Bleichenbacher attack, standards and libraries migrated to OAEP or at least added support for it, while treating PKCS#1 v1.5 as a legacy option. Modern cryptographic libraries and protocols reflect these lessons.</p>
<p>In 1998, the RSA standard was updated. PKCS#1 v2.0 introduced RSAES-OAEP as the new recommended encryption scheme, and by PKCS#1 v2.1 and v2.2 (RFC 3447 and RFC 8017), OAEP is required for new applications, with PKCS#1 v1.5 included only for backward compatibility.</p>
<p>OpenSSL discourages users from using PKCS#1 v1.5 as it leaks information that can potentially be used to mount a Bleichenbacher padding oracle attack [10]. The documentation clearly mentions that it is highly recommended to use <code>RSA_PKCS1_OAEP_PADDING</code> in new applications.</p>
<p>The Python cryptography library (PyCA cryptography) also asks developers to use OAEP for encryption instead of PKCS#1 v1.5 [11].</p>
<p>After Bleichenbacher’s 1998 attack, it was impractical to instantly replace PKCS#1 v1.5 everywhere. Instead, protocol designers issued countermeasures.</p>
<p>TLS, for example, responded by changing the error handling: the server would not reveal a padding failure distinctly. It would generate a fake premaster secret and proceed to prevent timing clues, and always return a generic handshake failure at a later stage, making it harder for the attacker to distinguish why decryption failed.</p>
<p>These countermeasures reduced the oracle’s fidelity but were tricky to get right across different implementations. In fact, not everyone got it right – the Bleichenbacher attack continued to resurface in various forms when implementations made mistakes in error handling.</p>
<p>In 2018, researchers discovered the ROBOT attack (Return Of Bleichenbacher’s Oracle Threat): several TLS implementations had subtle bugs that recreated a padding oracle, allowing the attack to succeed 19 years later. The ROBOT paper showed that even with countermeasure guidelines, the complexity of uniformly handling errors led to slip-ups in popular products.</p>
<p>This underscores that patching an insecure scheme is often error-prone – a design that is secure by construction (like OAEP) is preferable.</p>
<p>PKCS#1 v1.5 continues to exist because of these patchwork security measures and the fact that it cannot be abruptly removed from all existing systems. It is generally regarded as "legacy" or maintained "for compatibility" purposes. The collective wisdom is clear: use OAEP for RSA encryption whenever possible.</p>
<h2 id="heading-enhancing-digital-signatures-the-transition-to-pss">Enhancing Digital Signatures: The Transition to PSS</h2>
<p>Now that you understand how OAEP transformed RSA encryption by mitigating vulnerabilities in deterministic padding, it’s time to turn our attention to RSA digital signatures – a critical function for ensuring message integrity and authenticity.</p>
<p>Early RSA signature schemes suffered from similar problems as raw encryption: their deterministic nature made them prone to forgery and replay attacks. This vulnerability paved the way for an improvement: the Probabilistic Signature Scheme (PSS).</p>
<p>Before we dive into PSS itself, let’s quickly understand the pain points with early RSA signatures.</p>
<h3 id="heading-problems-with-early-rsa-signature-schemes">Problems with Early RSA Signature Schemes</h3>
<p>Traditional RSA signatures were generated by simply applying the RSA decryption function on a message digest (often with minimal formatting):</p>
<p>$$s=m^d \bmod N$$</p><p>where \(m\) is the hash (or encoded hash) of the message. This approach was deterministic which meant that each time the same message was signed, the exact signature was produced. Such determinism had two major drawbacks:</p>
<ol>
<li><h4 id="heading-predictability-and-replay">Predictability and Replay</h4>
<p> Since the signature for a given message was always identical, an attacker could replay a captured signature with impunity or forge signatures if they could deduce patterns in the signature scheme.</p>
</li>
<li><h4 id="heading-forgery-risks">Forgery Risks</h4>
<p> In a deterministic setting, if an attacker finds any structure or mathematical relationship in the signature, they might be able to forge a valid signature for a new message. In certain scenarios, weak formatting could allow an adversary to create a “signature transformation” that produces a valid signature without having access to the private key.</p>
</li>
</ol>
<p>These issues highlighted that a signature scheme must be probabilistic to be secure against adaptive forgery attempts and to ensure non-repudiation. This means that the signer should not be able to repudiate a signature because it is bound to a random value known only at signing time.</p>
<h3 id="heading-birth-of-the-probabilistic-signature-scheme-pss">Birth of the Probabilistic Signature Scheme (PSS)</h3>
<p>Towards the end of 1998, Bellare and Rogaway also proposed a scheme to overcome the inherent limitations of deterministic RSA signatures [12]. The core idea was to introduce randomness into the signature generation process so that even when signing the same message twice, the resulting signatures would be different. This randomness comes from a salt value and a carefully designed encoding process. The result is a signature method with strong, provable security guarantees.</p>
<p>This randomness prevents attackers from exploiting patterns in the signature process. The probabilistic Signature Scheme was designed to be provably secure in the random oracle model, meaning that forging a signature would be as hard as breaking RSA itself under certain assumptions [13].</p>
<p>The block diagram below is a visual representation of the PSS encoding schema:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742669558156/8137f535-deb7-4437-887a-53cf7a412089.png" alt="Probabilistic Signature Scheme" class="image--center mx-auto" width="1580" height="1258" loading="lazy"></p>
<p>Let’s understand what these mathematical notions mean as well as the workings of RSA-PSS, up next.</p>
<h3 id="heading-the-mathematics-behind-pss">The Mathematics Behind PSS</h3>
<p>Before diving into the mechanics of RSA-PSS, it’s helpful to define the notations and terms you’ll see in the steps ahead.</p>
<p>In RSA, \(N\)is the modulus, a large integer that is the product of two primes. \(k\) is the length of \(N\) in <em>bytes</em>. For an \(2048\)-bit key, \(k=256\) bytes.</p>
<p>\(M\)represents the message data or document you want to sign. In RSA-PSS, you’ll typically first compute a hash of \(M\). \(Hash\) refers to a cryptographic hash function (for example, SHA-256) that maps data to a fixed-size output. The output length is denoted \(hLen\). For SHA-256, \(hLen=32\) bytes.</p>
<p>We will use a salt, \(S\), randomly generated string of fixed length (often the same as \(hLen\)). This randomness is essential in ensuring that each signature is unique, even for the same message.</p>
<p>\(H\) or \(mHash\) is the hash of the message \(M\)and \(H'\) is a secondary hash that includes both \(M\) and the salt \(S\). This appears in the PSS encoding step.</p>
<p>The Mask Generation Function, \(MGF\), is a function that uses the hash internally to produce a pseudorandom output of arbitrary length. In PSS, it is used to “mask” parts of the data block so that the signature is hard to forge.</p>
<p>A fixed byte, \(0xbc\) (in hex) is appended at the end of the encoded message to mark the boundary of the PSS structure. This serves as a simple integrity check during decoding. After a successful encoding we receive an encoded message \(EM\) which is an octet string of length \(emLen = \left\lceil{\frac{emBits}{8}}\right\rceil\).</p>
<p>Now that you are familiar with all the necessary notations, we are ready to begin the encoding step.</p>
<h4 id="heading-step-1-message-hashing-and-salt-generation">Step 1: Message Hashing and Salt Generation</h4>
<p>We compute the hash of the message as \(H~( mHash)=Hash(M)\) where \(M\) is our message. We will also create a random salt \(S\) (of fixed length, say 20 bytes if you use SHA-1).</p>
<h4 id="heading-step-2-encoding-the-hash-with-the-salt-pss-encode">Step 2: Encoding the Hash with the Salt (PSS-Encode)</h4>
<p>We will construct a Data Block, \(DB\), by combining a padding with the hash and the salt. The padding is a sequence of \(0\)’s that fills space and ensures a fixed length. Mathematically:</p>
<p>$$M' = (0x)~00 ~00 ~00 ~00 ~00 ~00 ~00 ~00 ~||~ mHash ~||~ salt$$</p><p>Now we compute the Hash of this block as \(H' = Hash(M')\). We will generate another octet string \(PS\) and concatenate it with the salt and \(0x01\) as a delimiter:</p>
<p>$$DB = PS ~||~ 0x01 ~||~ salt$$</p><p>Note that DB is an octet string of length \(emLen - hLen - 1\). The mask that you see in the visual representation above must be of this length. Mathematically:</p>
<p>$$dbMask = MGF(H, emLen - hLen - 1)$$</p><p>We will then apply this mask on the \(DB\) block using an \(XOR\) operation to produce our \(maskedDB\):</p>
<p>$$maskedDB = DB \oplus dbMask$$</p><p>Recollect that \(emLen\) is the intended length of the Encoded Message \(EM\) and \(hLen\) is the length of the hash output. Now we append a fixed trailer field \(0xbc\) and produce the encoded message in its octet string representation:</p>
<p>$$EM = maskedDB ~||~ H ~||~ 0xbc$$</p><p>This encoding process ensures that both the salt and the hash are mixed together in a non-reversible, pseudorandom manner. The randomness from the salt is “spread” over the data block by the \(MGF\), making it extremely difficult for any adversary to manipulate the signature.</p>
<h4 id="heading-step-3-rsa-signature-generation">Step 3: RSA Signature Generation</h4>
<p>Once you have the encoded message \(EM\), the RSA signature is produced by using the RSA private key. First, convert the Octet String to its integer representation using the OS2IP method we’ve discussed before. Then apply the RSA Private Key Operation:</p>
<p>$$s=m^d \bmod N$$</p><p>where \(d\) is the private exponent and \(N\) is the RSA modulus.</p>
<h4 id="heading-step-4-signature-verification">Step 4: Signature Verification</h4>
<p>At the receiver end, when any recipient wants to verify a signature, they reverse the process:</p>
<p>$$m′= s^e \bmod N$$</p><p>and convert \(m'\) back to an encoded message \(EM\). The verifier then extracts the components \((MaskedDB, H′, trailer)\) and recomputes \(H'\) from the message and salt. The verifier confirms that the hash and salt embedded in \(EM\) match what is expected. If everything checks out, the signature is valid.</p>
<h2 id="heading-the-road-ahead-assessing-rsas-long-term-viability"><strong>The Road Ahead: Assessing RSA’s Long-Term Viability</strong></h2>
<p>In 1994, Peter Shor’s algorithm [14], demonstrated that a quantum computer can factor large integers in polynomial time, thereby efficiently breaking RSA’s underlying hard problem – the difficulty of factoring \(N = p \times q\).</p>
<p>Although experimental quantum computers have made progress, they remain far from having the number of stable qubits required to break RSA keys of practical sizes (2048 or 4096 bits).</p>
<p>In anticipation of large-scale quantum computers, the cryptographic community is actively developing and standardizing algorithms believed to be resistant to quantum attacks. These include lattice-based schemes (such as CRYSTALS-Kyber and NTRU), code-based cryptography (such as the McEliece cryptosystem), hash-based signatures (such as XMSS), and multivariate polynomial cryptosystems.</p>
<p>It’s important to note that while OAEP and PSS improve the security of RSA against classical attacks, they do not protect RSA from quantum attacks. In a post-quantum world, even the most secure classical padding will not prevent a quantum computer from breaking RSA using Shor’s algorithm.</p>
<p>In the near term, RSA remains in widespread use and, when implemented with padding schemes such as OAEP and PSS, continues to provide strong security against classical adversaries. But looking ahead, it’s expected that organizations will gradually migrate to post-quantum algorithms as they mature and become standardized.</p>
<h2 id="heading-references">References</h2>
<p>[1] FIPS 186-5: <a target="_blank" href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf">Digital Signature Standard (DSS)</a></p>
<p>[2] RFC 8017 PKCS #1: <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8017.html">RSA Cryptography Specifications</a></p>
<p>[3] <a target="_blank" href="https://en.wikipedia.org/wiki/Lagrange%27s_theorem_\(number_theory\)">Lagrange's theorem</a></p>
<p>[4] Ronald L. Rivest, Robert D. Silverman: <a target="_blank" href="https://people.csail.mit.edu/rivest/pubs/pubs/RS01.version-1999-11-22.pdf">Are Strong Primes Needed for RSA</a>?</p>
<p>[5] <a target="_blank" href="https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/">pyca/cryptography</a></p>
<p>[6] <a target="_blank" href="https://github.com/openssl/openssl/blob/85cabd94958303859b1551364a609d4ff40b67a5/crypto/rsa/rsa_chk.c">OpenSSL Github</a>: <code>rsa_chk.c</code></p>
<p>[7] RFC 2313: <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc2313.html">PKCS #1: RSA Encryption</a></p>
<p>[8 ] Daniel Bleichenbacher: <a target="_blank" href="https://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf">Chosen Ciphertext Attacks Against Protocols Based on the RSA Encryption Standard PKCS #1</a></p>
<p>[9] RFC 8017: <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8017#section-7.1">PKCS #1 RSA Cryptography Specifications Version 2.2</a></p>
<p>[10] RSA_public_encrypt: <a target="_blank" href="https://docs.openssl.org/3.5/man3/RSA_public_encrypt/#warnings">Warnings</a></p>
<p>[11] <a target="_blank" href="https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15">pyca/PKCS1v1</a></p>
<p>[12] <a target="_blank" href="https://en.wikipedia.org/wiki/Probabilistic_signature_scheme">Probabilistic signature scheme</a></p>
<p>[13] RFC 8017: <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8017#section-8.1">RSASSA-PSS</a></p>
<p>[14] <a target="_blank" href="https://ieeexplore.ieee.org/abstract/document/365700/">Algorithms for quantum computation</a>: discrete logarithms and factoring</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Connect, Read, and Process Sensor Data on Microcontrollers – A Beginner's Guide ]]>
                </title>
                <description>
                    <![CDATA[ In today’s world, computers are ubiquitous and generally serve two primary purposes. The first is general-purpose computing, where they handle a wide range of tasks, including running diverse applications and programs. Examples include laptops, deskt... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/connect-read-process-sensor-data-on-microcontrollers-for-beginners/</link>
                <guid isPermaLink="false">67d45997c9e7f2d42bb1c540</guid>
                
                    <category>
                        <![CDATA[ embedded systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ microcontroller ]]>
                    </category>
                
                    <category>
                        <![CDATA[ embedded software ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ADC ]]>
                    </category>
                
                    <category>
                        <![CDATA[ I2C ]]>
                    </category>
                
                    <category>
                        <![CDATA[ real-time data processing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Signal Processing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sensors ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Electronics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hardware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ electrical engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Soham Banerjee ]]>
                </dc:creator>
                <pubDate>Fri, 14 Mar 2025 16:30:15 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741902732575/fd41a2d5-ed4f-445d-b186-936625837c8d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today’s world, computers are ubiquitous and generally serve two primary purposes.</p>
<p>The first is general-purpose computing, where they handle a wide range of tasks, including running diverse applications and programs. Examples include laptops, desktops, servers, and supercomputers.</p>
<p>The second is embedded systems, which are specialized computers designed for specific functions. Commonly found in devices such as thermostats, refrigerators, cars, and other smart appliances, they rely on sensors to collect environmental data and execute their tasks efficiently.</p>
<h3 id="heading-the-role-of-sensors"><strong>The Role of Sensors</strong></h3>
<p>Sensors play a critical role in both types of computing. In embedded systems, sensors gather environmental data to help devices like autonomous vehicles, home appliances, and industrial machines perform tasks. In general-purpose computers, sensors primarily monitor internal conditions such as temperature and voltage, ensuring safe operation and preventing issues like overheating or electrical faults.</p>
<p>As Artificial Intelligence (AI) and the Internet of Things (IoT) evolve, sensors have become indispensable for gathering real-world data to support intelligent decision-making. Embedded systems leverage sensors to perceive their environment, transforming raw data into actionable insights that power automation and improve efficiency across industries.</p>
<p>This means that understanding sensor interfacing and designing robust sensor-driven software has become a vital skill for engineers and hobbyists alike.</p>
<p>Whether you're a beginner or experienced engineer, this guide will help you build a solid understanding of sensor interfacing software.</p>
<h2 id="heading-what-youll-learn-and-article-scope"><strong>What You’ll Learn and Article Scope</strong></h2>
<p>In this article, you’ll learn how to connect sensors to microcontrollers (MCUs) and design sensor software pipelines that turn raw data into meaningful, usable information. You’ll also explore practical techniques for processing sensor data accurately and efficiently in embedded systems.</p>
<p>Here’s a breakdown of what we’ll cover:</p>
<ul>
<li><p>What sensors are and how they work – An introduction to sensors, common types, and how sensor pipelines help process sensor data.</p>
</li>
<li><p>Key sensor characteristics – Important parameters like sensitivity, accuracy, precision, range, drift, and response time to help you choose the right sensor for your project.</p>
</li>
<li><p>How to interface sensors with microcontrollers – Hardware connections and communication protocols like SPI, I²C, and GPIO that allow microcontrollers to read sensor data.</p>
</li>
<li><p>Software architecture for sensor data – A high-level overview of the software pipeline that processes sensor data, including drivers, ADC support, scaling, calibration, and post-processing.</p>
</li>
<li><p>Detailed design of pipeline components – A closer look at each step in the pipeline, focusing on scaling raw data, calibrating sensors, and applying filters to clean up noisy signals.</p>
</li>
<li><p>Practical tips for power management – Best practices for handling power efficiently using low-power modes, FIFO buffers, and DMA when working with sensor data in embedded systems.</p>
</li>
</ul>
<p>By the end of this article, you’ll know how to design and implement a complete sensor data pipeline for an embedded system, from reading raw sensor data to preparing it for real-world use in intelligent, connected devices.</p>
<p><strong>Note</strong>: Advanced data processing, high-resolution ADCs, and hardware circuit design for sensors are outside the scope of this article.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To get the most out of this article, you should have:</p>
<ol>
<li><p>Basic knowledge of microcontrollers: Understanding of common peripherals like ADCs (Analog-to-Digital Converters), SPI (Serial Peripheral Interface), I2C (Inter-Integrated Circuit) and GPIO (General Purpose Input/Output). If you’re new to these protocols, <a target="_blank" href="https://www.parlezvoustech.com/en/comparaison-protocoles-communication-i2c-spi-uart/">this article provides a great overview</a>.</p>
</li>
<li><p>Basic knowledge of electronics: Familiarity with circuits and signals, including analog and digital interfaces.</p>
</li>
<li><p>Programming in C: Familiarity in embedded software development, including driver development.</p>
</li>
<li><p>(Optional) Basic knowledge of sensors: Understanding different types of sensors (like temperature, pressure, motion) is helpful but not required.</p>
</li>
</ol>
<p>Also, this article assumes the following:</p>
<ul>
<li><p>You are working with a microcontroller equipped with the peripherals needed for sensor integration. The details of microcontroller peripherals can be found in a <a target="_blank" href="https://pdf.xab3.ro/manual/reference-manual-for-stm32f405415-stm32f407417-stm32f427437-and-stm32f429439-mcus-100">reference manual for example for an STM32F4</a> series microcontroller will have all the details :</p>
</li>
<li><p>You are familiar with compilers, debuggers, and IDEs used in embedded systems. Some common tools include:</p>
<ul>
<li><p>Compilers: <a target="_blank" href="https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads">GCC</a>, <a target="_blank" href="https://developer.arm.com/documentation/dui0773/l/Introducing-the-Toolchain/Toolchain-overview?lang=en">Clang</a>,</p>
</li>
<li><p>Debuggers: <a target="_blank" href="https://sourceware.org/gdb/">GDB</a>, <a target="_blank" href="https://lldb.llvm.org/use/tutorial.html">LLDB</a></p>
</li>
<li><p>IDEs: <a target="_blank" href="https://code.visualstudio.com">Visual Studio Code</a> (VSCode) is a popular choice, especially with extensions for embedded development and debugging.</p>
</li>
</ul>
</li>
<li><p>You aim to build reliable, sensor-driven embedded systems, capable of collecting and processing real-world data efficiently.</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-sensor-and-sensor-pipeline">What is a Sensor and Sensor Pipeline?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sensor-characteristics">Sensor Characteristics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-interface-with-a-microcontroller">How to Interface with a Microcontroller</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-software-architecture">Software Architecture</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-high-level-overview-of-components">High-Level Overview of Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-accessing-data-from-the-sensor">Accessing Data from the Sensor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sensor-power-management">Sensor Power Management</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-detailed-design-of-components">Detailed Design of Components</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-sensor-driver">1. Sensor Driver</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-adc-support">2. ADC Support</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-scaling">3. Scaling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-calibration">4. Calibration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-data-post-processing">5. Data Post-Processing</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-a-sensor-and-sensor-pipeline"><strong>What is a Sensor and Sensor Pipeline?</strong></h2>
<p>A sensor detects changes in physical properties such as temperature, pressure, or light and converts them into electrical signals that can be measured or interpreted. For example, a thermistor is a type of resistor whose resistance changes with temperature. As the temperature varies, the resistance of the thermistor changes, altering the voltage across it. The system then interprets this voltage change to determine the temperature.</p>
<p>To better understand sensors, consider the natural sensors in the human body: the eyes, ears, skin, nose, and tongue. These natural sensors constantly send signals about the environment to the brain for processing. Different regions of the brain interpret these signals and use the information to drive actions and responses. Just like the brain processes signals from natural sensors, a microcontroller processes signals from electronic sensors using a sensor pipeline.</p>
<p>Sensors come in many types, each designed to detect specific physical properties. Some sensors have a sensing element that changes its properties in response to conditions like heat, light, or pressure. Examples include thermistors, infrared receivers, and photodiodes.</p>
<p>For detecting movement, such as acceleration and rotation, MEMS (Microelectromechanical Systems) sensors—like accelerometers and gyroscopes—are widely used.</p>
<p>To measure distance, sensors like sonars, ultrasonic sensors, and radars are common. These are just a few examples of the many types of sensors available.</p>
<p>Beyond the types of physical properties they detect, sensors also differ in their levels of integration. Some sensors are raw sensors, consisting only of a sensing element and a transducer with simple leads for direct connection to an external circuit.</p>
<p>Others, known as smart sensors, include additional components such as an ADC (analog-to-digital converter) and onboard processing capabilities, enabling them to handle more of the data processing independently.</p>
<p>The choice between a raw sensor and a smart sensor depends on your application requirements, including factors like cost, size, and the processing load on the interfacing microcontroller.</p>
<p>Returning to our human analogy, consider how vision works as a sensor pipeline. When light enters our eyes, photoreceptor cells (rods and cones) in the retina act as sensing elements, converting the light into electrical signals. These signals travel via the optic nerve to the brain’s visual cortex, where they undergo processing to form a recognizable image. The brain then interprets this information and initiates a response, like smiling when you see a beautiful scenery.</p>
<p>Similarly, a sensor pipeline for an embedded system can be defined as shown in the picture below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738828676916/75137176-c9ba-432d-bf44-bb3da093e18d.png" alt="Figure 1: A Sensor Pipeline showing analogue to digital conversion, calibration, filtering, and then processing." class="image--center mx-auto" width="918" height="183" loading="lazy"></p>
<p>Each of these steps may have different requirements based on the application. Creating a requirements document for the sensor is helpful when selecting the appropriate sensor and configuring the pipeline.</p>
<h2 id="heading-sensor-characteristics"><strong>Sensor Characteristics</strong></h2>
<p>Before you dive into the blocks of the sensor pipeline, let’s review some important characteristics of a sensor.</p>
<h3 id="heading-sensitivity"><strong>Sensitivity</strong></h3>
<p>Sensitivity is the ability of a sensor to detect small changes in the physical property it’s designed to measure.</p>
<p>Sensitivity can vary based on factors like manufacturing processes, cost, and the design of the sensing element.</p>
<p>Sensors designed for a specific property often come in different sensitivity levels, allowing users to select an appropriate sensitivity based on the application requirements.</p>
<h3 id="heading-accuracy"><strong>Accuracy</strong></h3>
<p>Accuracy is the degree to which a sensor’s measurement matches the true value of the physical property it’s measuring. Testing a sensor’s accuracy typically requires comparing its readings to those of a reference instrument.</p>
<p>A sensor may have gain and offset errors—issues that calibration can help correct. Calibration adjusts for these systematic errors, which are often due to manufacturing tolerances or design factors.</p>
<p>Once calibrated, the sensor’s output can be verified against a reference to confirm its accuracy. The required level of accuracy should be determined based on the application’s needs.</p>
<h3 id="heading-precision"><strong>Precision</strong></h3>
<p>Precision refers to the consistency or repeatability of a sensor's measurements, regardless of how close those measurements are to the true value. It indicates the sensor's ability to produce the same output under identical conditions and how finely it can resolve and report values.</p>
<p>For example, if the true temperature of an object is 12.53°C:</p>
<ul>
<li><p>A precise sensor will consistently measure values like 12.52°C, 12.53°C, or 12.54°C, even if those values are slightly offset from the true temperature.</p>
</li>
<li><p>A highly accurate sensor, on the other hand, will measure values close to 12.53°C but may lack precision if those readings vary widely (e.g., 12.50°C, 12.53°C, and 12.56°C).</p>
</li>
</ul>
<p>For applications requiring exact measurements, a sensor with both high accuracy (closeness to the true value) and high precision (low variability) is essential. This is especially important in distinguishing small differences, such as between 12.5°C and 12.53°C.</p>
<p>In contrast, applications with less stringent requirements might use sensors with broader tolerances, such as ±1°C, which are sufficient for general monitoring purposes.</p>
<h3 id="heading-range"><strong>Range</strong></h3>
<p>The range of a sensor refers to the span between the maximum and minimum values of the physical property it can measure while maintaining its specified precision and accuracy. A sensor's operating range may extend beyond its measurement range, but the measurement range defines the limits within which the sensor reliably adheres to its specified sensitivity, accuracy, and response time.</p>
<h3 id="heading-drift"><strong>Drift</strong></h3>
<p>Drift is when a sensor's output changes over time due to conditions like temperature or humidity. Components within the sensor, including the sensing element, may be sensitive to these conditions, leading to gradual shifts in measurements.</p>
<p>For example, many components are affected by temperature and humidity changes, which can alter sensor readings. Also, sensors with internal oscillators may experience time-based drift, impacting accuracy.</p>
<p>Regular calibration with an accurate external reference (such as a precise clock) can help correct for drift and maintain reliable measurements. For certain applications, selecting a sensor with acceptable drift characteristics is crucial.</p>
<h3 id="heading-response-time"><strong>Response Time</strong></h3>
<p>Response time is the duration a sensor takes to detect and reflect a change in the measured physical property. For example, if the temperature rises by 5°C, the response time indicates how long the temperature sensor takes to reflect this change in its output.</p>
<p>Response time depends on the sensor’s design, manufacturing quality, and internal components, such as the ADC (Analog-to-Digital Converter), averaging circuits, and filters within the sensor pipeline.</p>
<p>All the parameters mentioned above are thoroughly documented in the sensor’s data-sheet. In practice, it’s a good idea to create a sensor requirements document for each specific application, detailing these key parameters as a baseline for sensor selection.</p>
<p>Now that you’ve examined the key characteristics of sensors, let’s explore how you can connect them to a microcontroller for real-world applications.</p>
<h2 id="heading-how-to-interface-with-a-microcontroller"><strong>How to Interface with a Microcontroller</strong></h2>
<h3 id="heading-choosing-a-communication-protocol">Choosing a Communication Protocol</h3>
<p>Another essential aspect of sensor requirements is specifying the communication interface between the sensor and the MCU or processor in the system. It’s important to understand how the sensor will be interfaced based on its output signal type and the available pins on the microcontroller.</p>
<p>For instance, certain sensors may connect directly to an analog or digital input pin on a microcontroller. A raw sensor, such as a temperature sensor, typically connects to an analog input pin, which is then read by the microcontroller’s internal ADC (Analog-to-Digital Converter).</p>
<p>In contrast, a digital-output sensor connects to a digital GPIO (General Purpose Input/Output) pin. For instance, speed sensors generate square waves with variable pulse widths to indicate speed. These signals are usually connected to a GPIO pin configured as an external interrupt or timer capture input, allowing the microcontroller to measure pulse width accurately.</p>
<p>A smart sensor, on the other hand, often supports communication protocols like SPI (Serial Peripheral Interface) or I2C (Inter-Integrated Circuit). These interfaces enable the microcontroller to configure the sensor, check its status, and retrieve data through register reads and writes.</p>
<p>Choosing the appropriate communication protocol for interfacing a sensor depends on the available pins in the system and the specific requirements of the application.</p>
<p><strong>Tip</strong>: When working with protocols like I²C or SPI, using tools such as <a target="_blank" href="https://www.saleae.com">Saleae</a> logic analyzers can greatly simplify debugging and validation. Logic analyzers capture and visualize communication signals, and tools like Saleae offer built-in protocol interpreters to help you decode sensor communication in real time. This can be especially helpful when troubleshooting configuration issues, timing problems, or communication errors during sensor interfacing.</p>
<p>Figure 2 below shows an example of a microcontroller connected to 4 sensors having different interfaces.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738828730915/25e62db6-a583-427a-bd77-c61c33990cdf.png" alt="Figure 2: A microcontroller interfacing with different sensors using different communication interfaces." class="image--center mx-auto" width="921" height="356" loading="lazy"></p>
<h3 id="heading-determining-power-requirements">Determining Power Requirements</h3>
<p>Power requirements are another key consideration when interfacing a sensor. Sensors may operate at different voltages (for example, 3.3V or 5V), so ensuring the microcontroller can accommodate these levels is essential. Level converters can bridge voltage mismatches, ensuring compatibility between the sensor and microcontroller voltage levels.</p>
<p>Timing and sampling requirements must also be evaluated, especially for sensors generating high-frequency data. Configuring external interrupts on GPIO pins can ensure timely data capture, while techniques like using DMA can streamline data transfer for sensors sampling at high frequencies without CPU involvement.</p>
<p>Now that you’ve learned about communication protocols and hardware connections, let’s focus on designing the software architecture that acquires, processes, and prepares sensor data for use. Designing effective software is crucial for obtaining clean, reliable data from the sensor.</p>
<h2 id="heading-software-architecture"><strong>Software Architecture</strong></h2>
<p>Now that we’ve chosen the sensor and communication protocol, let’s design the software architecture for the sensor pipeline. This software runs on the microcontroller connected to the sensor and processes raw data to make it clean and usable.</p>
<p>While application-level data processing is beyond the scope of this article, let’s focus on interfacing with the sensor and preparing the data for application use.</p>
<p>The sensor processing pipeline can be broken into the following components:</p>
<ol>
<li><p>Sensor Driver</p>
</li>
<li><p>Analog-to-Digital Conversion (ADC) Support</p>
</li>
<li><p>Scaling</p>
</li>
<li><p>Calibration</p>
</li>
<li><p>Data Post-Processing</p>
</li>
</ol>
<p>Let’s examine a high-level overview of these components for both smart and raw sensors.</p>
<h3 id="heading-high-level-overview-of-components"><strong>High-Level Overview of Components</strong></h3>
<ol>
<li><p><strong>Sensor Driver</strong></p>
<ol>
<li><p>Smart sensors: The driver configures the sensor, manages power, and handles read and write operations to the sensor registers over a communication protocol like SPI, I2C.</p>
</li>
<li><p>Raw sensors: The driver may only control GPIOs for power management, as raw sensors typically lack registers.</p>
</li>
</ol>
</li>
<li><p><strong>Analog-to-Digital Conversion (ADC) Support</strong></p>
<ol>
<li><p>Smart sensors: Include an onboard ADC, which is configured through the sensor driver.</p>
</li>
<li><p>Raw sensors: Requires an external ADC, an ADC driver implemented in software to configure the ADC, initiate conversions, and retrieve data.</p>
</li>
</ol>
</li>
<li><p><strong>Scaling</strong>: Scaling is necessary for both smart and raw sensors. It converts digital counts after the analog to digital conversion into meaningful physical quantities using formulas provided in the sensor data sheet. For example, a temperature sensor will use a formula to convert digital counts to degree Celsius.</p>
</li>
<li><p><strong>Calibration</strong>: Once the measured physical quantity is obtained, calibration adjusts the value by applying offsets, gains, or both to correct errors. This process ensures the sensor output aligns with reference values across its entire measurement range. A detailed discussion of the calibration process will follow in the next section.</p>
</li>
<li><p><strong>Data Post-Processing</strong>: Post-processing techniques, such as filtering are applied to improve data quality and reduce noise. Common filters such as low-pass or high-pass filters can remove unwanted frequency components.</p>
</li>
</ol>
<h3 id="heading-accessing-data-from-the-sensor"><strong>Accessing Data from the Sensor</strong></h3>
<p>The method of accessing data depends on the whether it’s a raw sensor or a smart sensor. Smart sensors will have onboard ADCs and FIFOs. Before delving into how data is accessed, it’s important to first understand sampling frequency.</p>
<h4 id="heading-sampling-frequency">Sampling Frequency:</h4>
<p>The frequency of taking a measurement from the sensor must follow the <a target="_blank" href="https://www.allaboutcircuits.com/technical-articles/nyquist-shannon-theorem-understanding-sampled-systems/">Nyquist-Shannon sampling theorem</a>. It states that the sampling rate must be twice the highest frequency component of the signal to be measured to accurately reconstruct the measured data.</p>
<p>The sampling frequency defines how often the sensor captures data, which affects how the data is accessed. Depending on whether the sensor is a raw sensor or a smart sensor, the approach to handling this sampled data varies.</p>
<p><strong>Smart Sensors:</strong></p>
<ol>
<li><p>Data register: The sensor writes sampled data directly into a register based on the set sample frequency updated during setup. The microcontroller reads this data register based on a data conversion completion interrupt.</p>
</li>
<li><p>FIFObBuffer: Some sensors include FIFO (First-In, First-Out) buffers to store multiple data points. When enabled, the FIFO updates at the configured sampling frequency and trigger interrupts when it becomes full or reaches a predefined level.<br> The benefits of FIFO include:</p>
<ol>
<li><p>Power efficiency: The MCU can process data in batches, reducing CPU overhead and allowing it to enter low-power mode during data collection.</p>
</li>
<li><p>Sampling and processing rate matching: FIFO buffers help reconcile differences between the sensor’s sampling rate and the MCU’s data processing rate.</p>
</li>
<li><p>For MCUs with Direct Memory Access (DMA), data transfer from the sensor to MCU memory can occur without CPU intervention, further reducing power consumption.</p>
</li>
</ol>
</li>
</ol>
<p><strong>Raw Sensors:</strong></p>
<p>For raw sensors, the MCU triggers ADC conversions at the sampling frequency, often using a timer interrupt. Data is read upon the ADC conversion complete interrupt, allowing the MCU to sleep during conversions and between samples to save power.</p>
<h3 id="heading-sensor-power-management"><strong>Sensor Power Management</strong></h3>
<p>Power management is critical for energy-sensitive applications. Strategies include:</p>
<ol>
<li><p>Low-power modes: Many sensors support low-power modes configurable through sensor registers.</p>
</li>
<li><p>GPIO-controlled power cycling (Duty-Cycling): For sensors without built-in low-power modes, the microcontroller can toggle the sensor’s power line using a GPIO pin, reducing power consumption further. Figure 3 below shows the diagram of a raw temperature sensor whose power is controlled using a GPIO from the MCU. For example, a temperature sensor in sleep mode can be activated only when temperature readings are required.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739042040654/1f2d4bbd-f15a-417a-9c79-3b93384e95bd.png" alt="Figure 3: Raw Temperature Sensor Interfacing a MCU" class="image--center mx-auto" width="549" height="557" loading="lazy"></p>
<p>The above techniques ensure efficient use of power while maintaining the required data sampling rate and sensor responsiveness.</p>
<p>With the high-level architecture in mind, we’ll now dive into the detailed design of each pipeline component.</p>
<h2 id="heading-detailed-design-of-components"><strong>Detailed Design of Components</strong></h2>
<p>In this section, you’ll delve into the key components of the sensor pipeline outlined in the Software Architecture section.</p>
<h3 id="heading-1-sensor-driver"><strong>1. Sensor Driver</strong></h3>
<p>The sensor driver is responsible for managing communication, configuration, power, and data acquisition for both smart and raw sensors.</p>
<h4 id="heading-smart-sensor-driver">Smart Sensor Driver:</h4>
<ol>
<li><p>Communication driver: Generic I2C or SPI drivers on the MCU can be adapted using wrapper functions to handle sensor-specific requirements, such as 1-byte, 2-byte, or 4-byte transfers.</p>
</li>
<li><p>Configuration: Typical tasks include setting the sampling rate, configuring interrupts, managing FIFO buffers, and, if needed, clock settings.</p>
</li>
<li><p>Power management: APIs should allow higher software layers to transition sensors between power modes by writing to specific registers or controlling GPIO lines for sensors without built-in power modes.</p>
</li>
</ol>
<h4 id="heading-raw-sensor-driver">Raw Sensor Driver:</h4>
<p>For raw sensors, the driver primarily manages power, often through GPIO-controlled toggling.</p>
<h3 id="heading-2-adc-support"><strong>2. ADC Support</strong></h3>
<p>ADC support is required only for raw sensors. In this article, we’re focusing on SAR ADCs, which are commonly embedded in microcontrollers.</p>
<h4 id="heading-how-sar-adcs-work">How SAR ADCs Work?</h4>
<p>A SAR ADC converts an analog signal to a digital value over multiple clock cycles, with the number of cycles equal to its bit resolution (for example, 10 cycles for a 10-bit ADC).</p>
<h4 id="heading-key-terms-related-to-adcs">Key terms related to ADCs:</h4>
<ol>
<li><p>Reference Voltage (VRef): Represents the maximum voltage the ADC can measure. Analog signals exceeding this limit must be scaled down.</p>
</li>
<li><p>Resolution: Determines the smallest detectable voltage change. For example, a 10-bit ADC with a 3.3V VRef has a resolution of 3.22 mV</p>
</li>
</ol>
<p>$$V_{\text{Res}} = V_{\text{Ref}} /2^{10}$$</p><p>The ADC result is stored in a data register, which can then be scaled to meaningful physical units.</p>
<h3 id="heading-3-scaling"><strong>3. Scaling</strong></h3>
<p>Scaling converts ADC counts into meaningful physical values, such as temperature (°C) or acceleration (g) depending on the sensor type. Sensor datasheets typically provide the necessary formulas or lookup tables.</p>
<p>For example, the method to convert a voltage measured by a raw temperature sensor to temperature value is shown below:</p>
<p>$$V_{\text{Measured}} = Counts_{\text{ADC}} / 2^{10} * V_{\text{Ref}} \quad \text{(Get V_Measured from ADC Counts)}$$</p><p>$$Temperature_{\text{Measured}} = V_{\text{Measured}} * T_{\text{C/mV}} \quad \text{(Get Temperature physical value)}$$</p><p>Similarly, a 3-axis accelerometer maps counts on the X, Y, and Z axes to acceleration values in g or milli-g.</p>
<h3 id="heading-4-calibration"><strong>4. Calibration</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738829686302/bfa643dc-5e01-4b24-b885-b682acdb11cb.png" alt="Figure 4a: Calibration with gain &amp; offset | Figure 4b: Calibration with fixed offset" class="image--center mx-auto" width="1149" height="421" loading="lazy"></p>
<p>The figure above on the left (4a) is showing Calibration with gain and offset, while the figure above on the right (4b) is showing calibration with fixed offset.</p>
<p>$$x_{\text{calibrated}} = Gain * x_{\text{raw}} + Offset \quad \text{(Figure 4a - Linear Calibration)}$$</p><p>$$x_{\text{calibrated}} = x_{\text{raw}} + Offset \quad \text{(Figure 4b - Fixed offset Calibration)}$$</p><p>Calibration ensures the sensor’s output aligns with reference measurements, correcting for errors introduced by design, materials, or manufacturing.</p>
<h4 id="heading-types-of-errors">Types of Errors:</h4>
<ol>
<li><p>Offset error: A constant deviation of the sensor’s output from the true reference value, regardless of input magnitude.</p>
</li>
<li><p>Gain error: A proportional error where the sensor’s output scale deviates from the expected value, causing the output to increase or decrease incorrectly relative to the input.</p>
</li>
</ol>
<h4 id="heading-calibration-methods">Calibration Methods:</h4>
<ol>
<li><p>2/3-Point calibration: This type of calibration may involve either applying a fixed offset to the raw value or applying both gain and offset. Figure 4a illustrates an example of a gain/offset calibration, while Figure 4b depicts offset calibration. In both figures, the y-axis represents the reference value measured by an accurate instrument, while the x-axis represents the raw value measured by the sensor after ADC.</p>
</li>
<li><p>N-Point calibration: Involves multiple points for more complex, non-linear error correction.</p>
</li>
</ol>
<h4 id="heading-implementation">Implementation:</h4>
<ol>
<li><p>Calibration points shall cover the sensor’s entire measurement range for accuracy.</p>
</li>
<li><p>Parameters like gain and offset once estimated shall be stored in a non-volatile memory in the system for persistence to be used across power cycles.</p>
</li>
</ol>
<h3 id="heading-5-data-post-processing"><strong>5. Data Post-Processing</strong></h3>
<p>Post-processing covered in this section talks about removing noise and unwanted signal components, which improves data reliability.</p>
<h4 id="heading-filtering">Filtering</h4>
<p>Filtering is the process of removing unwanted frequency components from a signal to improve data quality. There are several different types of filters:</p>
<ul>
<li><p>Low-Pass Filters: Allows low-frequency signals to pass while attenuating high-frequency noise.</p>
</li>
<li><p>High-Pass Filters: Allows high-frequency signals to pass while attenuating low-frequency noise. (for example, gravitational acceleration in accelerometer data).</p>
</li>
<li><p>Band-Pass Filters: Retains only signals within a specific frequency range, removing both lower and higher frequencies outside the desired band.</p>
</li>
</ul>
<p>These filters are often implemented as FIR (Finite Impulse Response) or IIR (Infinite Impulse Response) filters. IIR filters are easy to implement and computationally efficient while FIR filters are computationally intensive but have better control over the frequency response.</p>
<p>Here, we will explore a simple low-pass filter known as the Exponential Moving Average (EMA), a type of IIR filter. A moving average filter is a mathematical technique that smooths short-term fluctuations while highlighting longer-term trends.</p>
<p>Unlike other moving average filters, EMA does not require maintaining a buffer, making it more memory-efficient. It is also more responsive to data changes while still providing smoothing, making it well-suited for real-time filtering. EMA assigns greater weight to recent data samples than older ones, allowing it to adapt quickly to changes in sensor readings.</p>
<p>EMA can be calculated like this:</p>
<p>$$EMA_{\text{t}} = \alpha * x_{\text{t}} + (1 - \alpha) * EMA_{\text{t - 1}}$$</p><p>$$\alpha = 2 / (N + 1) \quad \text{(Smoothening Factor, N - filter window size)}$$</p><p>$$EMA_{\text{t}} \quad \text{(Exponential Moving Average in current iteration)}$$</p><p>$$x_{\text{t}} \quad \text{(New Data Sample in Current Iteration)}$$</p><p>$$EMA_{\text{t - 1}} \quad \text{(Exponential Moving Average in the last iteration)}$$</p><p>Now that we understand the Exponential Moving Average (EMA) filter, here are two key factors to consider when tuning it for an application:</p>
<ul>
<li><p>Smoothing vs. Responsiveness: A higher smoothing factor (closer to 1, smaller filter window size) gives more weight to recent data, making the filter more responsive to changes but less effective at noise reduction. A lower smoothing factor (closer to 0, larger filter window size) provides better noise reduction but reacts more slowly to data changes.</p>
</li>
<li><p>Application-Specific Tuning: The smoothing factor should be chosen based on the sampling rate, sensor sensitivity, and application requirements. Real-time systems often require a balance between quick responsiveness and stable output.</p>
</li>
</ul>
<p>Here’s a code sample for EMA:</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdint.h&gt;</span></span>

<span class="hljs-comment">// Exponential Moving Average (EMA) filter implementation</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> FILTER_WINDOW 5</span>

<span class="hljs-comment">// Function to calculate EMA</span>
<span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">calculateEMA</span><span class="hljs-params">(<span class="hljs-keyword">float</span> ema, <span class="hljs-keyword">float</span> new_value, <span class="hljs-keyword">float</span> alpha)</span> </span>{
    <span class="hljs-keyword">return</span> (alpha * new_value) + (<span class="hljs-number">1</span> - alpha) * ema;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">float</span> sensorReadings[] = {<span class="hljs-number">26.0</span>, <span class="hljs-number">27.5</span>, <span class="hljs-number">28.2</span>, <span class="hljs-number">27.0</span>, <span class="hljs-number">26.8</span>, <span class="hljs-number">26.5</span>, <span class="hljs-number">27.2</span>};
    <span class="hljs-keyword">int</span> numReadings = <span class="hljs-keyword">sizeof</span>(sensorReadings) / <span class="hljs-keyword">sizeof</span>(sensorReadings[<span class="hljs-number">0</span>]);

    <span class="hljs-keyword">float</span> alpha = <span class="hljs-number">2.0f</span> / (FILTER_WINDOW + <span class="hljs-number">1</span>); <span class="hljs-comment">// Standard EMA formula</span>
    <span class="hljs-keyword">float</span> ema = sensorReadings[<span class="hljs-number">0</span>];  <span class="hljs-comment">// Initialize EMA with the first reading</span>

    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"EMA Filtered Sensor Data:\n"</span>);

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; numReadings; i++) {
        ema = calculateEMA(ema, sensorReadings[i], alpha);
        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Reading %d: Raw = %.2f, EMA = %.2f\n"</span>, i + <span class="hljs-number">1</span>, sensorReadings[i], ema);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>In summary, sensors are the backbone of modern smart devices, bridging the gap between the physical world and digital systems. From consumer electronics to industrial automation and medical devices, they enable devices to perceive and interact with their environments.</p>
<p>Understanding how sensors work, the components of their data pipeline, and their integration with microcontrollers is essential for engineers and hobbyists alike. By designing effective pipelines, developers can ensure accurate, clean, and reliable data, enabling systems to meet performance and power efficiency goals.</p>
<p>If you have questions or want to talk more about this topic, feel free to reach out on <a target="_blank" href="https://x.com/sohamstars">Twitter</a> or <a target="_blank" href="https://x.com/sohamstars">Lin</a><a target="_blank" href="https://www.linkedin.com/in/sohambanerjee2/">kedIn</a>. Always happy to connect.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ SVM Kernels Explained: How to Tackle Nonlinear Data in Machine Learning ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever considered how your phone can recognize handwritten text and convert it into regular computer text? Or how your email can separate messages automatically into spam and non-spam categories? Both of these examples work based on classifica... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/svm-kernels-how-to-tackle-nonlinear-data-in-machine-learning/</link>
                <guid isPermaLink="false">677c5a037d6144e9c5ef49f8</guid>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Josiah Adesola ]]>
                </dc:creator>
                <pubDate>Mon, 06 Jan 2025 22:32:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735894336456/dae0caa1-7c01-4b88-a748-79d682bbed78.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever considered how your phone can recognize handwritten text and convert it into regular computer text? Or how your email can separate messages automatically into spam and non-spam categories?</p>
<p>Both of these examples work based on classification tasks, as does the facial recognition feature on your phone.</p>
<p>When building a classification algorithm, real-world data often has a non-linear relationship. And many machine learning classification algorithms struggle with non-linear algorithms. But in this article, we'll be looking at how Support Vector Machine (SVM) kernel functions can help to solve this problem. We’ll go in-depth into a Python implementation of non-linear classification and SVM kernel functions.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ol>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/learn-machine-learning-in-2024/">Basic Understanding of Machine Learning</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/linear-algebra-full-course/">Linear Algebra Basics</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/ultimate-beginners-python-course/">Basic Python Programming Skills</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/learn-data-visualization-in-this-free-17-hour-course/">Understanding of Data Visualization</a></p>
</li>
<li><p><a target="_blank" href="https://colab.research.google.com/">A Google Colab</a> or <a target="_blank" href="https://www.anaconda.com/">Jupyter Notebook</a> Account</p>
</li>
</ol>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-overview-of-the-support-vector-machine-svm-technique">Overview of the Support Vector Machine (SVM) Technique</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fundamentals-of-svm">Fundamentals of SVM</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-svm-objective-function">SVM Objective Function</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-kernel-functions">Understanding Kernel Functions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-popular-kernel-functions">Popular Kernel Functions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-choose-the-right-kernel">How to Choose the Right Kernel</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-svm-kernel-implementation">SVM Kernel Implementation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-overview-of-the-support-vector-machine-svm-technique">Overview of the Support Vector Machine (SVM) Technique</h2>
<p>Support Vector Machine (SVM) is a supervised learning algorithm. It uses a hyperplane that divides features inside a feature space into distinct categories. It’s effective for both classification and regression applications.</p>
<p>By identifying the optimal dividing line or plane that will serve as the decision boundary, SVM seeks to maximize the margin between the various target variables. It’s primarily utilized in classification tasks and is very helpful in ignoring outliers. It categorizes the data points of the features in the dataset into distinct outputs or classes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734965953633/54d38f2a-8062-4bcb-8cc6-9795064241de.png" alt="Comparison of decision boundaries using SVC with different kernels on a dataset." class="image--center mx-auto" width="640" height="480" loading="lazy"></p>
<p>SVM seeks to achieve the optimal maximum margin and an ideal or near-perfect separation. There are various applications for SVM, such as image classification, face detection, text classification, image classification, and bioinformatics. SVM is also efficient in linear and non-linear classification problems.</p>
<h3 id="heading-importance-of-kernel-methods-in-svm">Importance of Kernel methods in SVM</h3>
<p>Nonlinear classification is a sort of classification that involves categorizing features that have non-linear, curved, or complex decision boundaries. Decision boundaries are regions of space that separate two different classes.</p>
<p>In linear classification tasks, the region of space between the different classes such as if the email is spam or not can be easily separated with a straight line. But in non-linear relationships, it could have a circular, parabola, or a complex-shape decision boundary.</p>
<p>Non-linear classification tasks have patterns that cannot be discovered by linear models. This is because the features have a non-linear relationship with each other.</p>
<p><a target="_blank" href="https://www.researchgate.net/publication/349186066_Machine_Learning_Techniques_for_THz_Imaging_and_Time-Domain_Spectroscopy"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735885699184/222d7252-7ece-4e31-97f5-577bb8577797.png" alt="Two diagrams illustrating decision boundaries between two classes: (a) shows a complex, wavy decision boundary, while (b) shows a simpler, smoother boundary. Blue dots represent Class A, and red triangles represent Class B.Machine Learning Techniques for THz Imaging and Time-Domain Spectroscopy by Hochong Park and Joo-Huik Son" class="image--center mx-auto" width="1058" height="430" loading="lazy"></a></p>
<p>SVM as a linear classification algorithm isn’t efficient for a non-linear data. To handle this sort of data, it will require a kernel method, which is the core topic of this article.</p>
<p>A kernel method is a technique used in SVM to transform non-linear data into higher dimensions. For example, if the data has a complex decision boundary in a 2-Dimensional space (as I’ll explain further in the later part of this article), it can be transformed into a 3-Dimensional space. This allows efficient classification just with a linear plane.</p>
<p>The goal of the article is to teach you about SVM kernels and their application to non-linear classification tasks.</p>
<h2 id="heading-fundamentals-of-svm">Fundamentals of SVM</h2>
<h3 id="heading-linear-classifiers-and-margin-maximization">Linear Classifiers and Margin Maximization</h3>
<p>Linear classifiers are classification algorithms that make predictions by using a straight line of best fit as a decision boundary between two or more categories.</p>
<p>Marginal planes are used to determine the support vector in the classification task. Support vectors are the data points in the dataset that are used to separate the different target variable categories – they are data points very close to the decision boundary.</p>
<p>In the image below, the marginal planes are the yellow lines, while the hyperplane is the red line. The hyperplane serves as the line of best fit or decision boundary. The data points that are closest to the marginal plane are the support vectors – the data points encircled in green in the image below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735889303238/33e28db8-a0aa-4aa9-ac63-0ece6b2d8c15.png" alt="Hard Margin: Decision Boundary for classification of two labels Image by the Author" class="image--center mx-auto" width="580" height="436" loading="lazy"></p>
<p>The marginal plane aims to achieve a maximum margin between its plane and the hyperplane – both having equal distance from hyperplane to achieve the best classification. The hyperplane in the image above shows a perfect linear relationship between <code>feature x1</code> and <code>feature x2</code>. The support vectors also help to establish the location of the marginal plane.</p>
<p>We have the hard margin and the soft margin, serving as model optimization methodologies for the SVM. The hard margin shows that you cannot find a data point of <code>feature x1</code> in the same area where there are <code>feature x2</code> data points and vice versa. It used to describe a perfect classification by the algorithm. The image above gives a representation of a hard margin.</p>
<p>A soft margin shows that the classification is imperfect, because you can find some data points of <code>feature x1</code> in the same area where we have data points of feature two, which could be caused by outliers. The image below gives a representation of soft margin.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735888972196/3e9dfaa1-999e-4e55-b1eb-bc04ef8de24e.png" alt="Soft Margin: Decision Boundary for classification of two labels Image by the Author" class="image--center mx-auto" width="580" height="436" loading="lazy"></p>
<h2 id="heading-svm-objective-function">SVM Objective Function</h2>
<p>For a binary classification, such as a dog or a cat, the dog can be represented as class 1 and cat as -1. This shows that the decision boundary or hyperplane is the determining factor. Any value above the plane is given as 1, and the class below the plane is given as -1.</p>
<p>The mathematical function for the hyperplane is given as:</p>
<p>$$f(x) = \mathbf{w}^T\mathbf{x} + b$$</p><p>$$\begin{array}{l} \text{ The variables used are:} \\ \mathbf{w}: \text{Weight vector (defining the orientation of the hyperplane)} \\ b: \text{Bias term (defining the position of the hyperplane)} \\ \mathbf{x}: \text{Input feature vector} \\ \\ \text{The classification decision is based on the sign of } f(x)\text{:} \\ f(x) &gt; 0: \text{Class 1} \\ f(x) &lt; 0: \text{Class -1} \end{array}$$</p><h3 id="heading-hard-margin-svm">Hard Margin SVM</h3>
<p>The Hard Margin SVM ensures all the data points are all properly classified without error, ensuring that the data points don’t find themselves in the other part of the hyperplane, and also maximizing the margin. It’s an effective method for a “noise-free” dataset. This is achieved by minimizing an objective function given below:</p>
<p>$$\begin{array}{l} \text{Hard Margin SVM Objective Function:} \ \min_{\mathbf{w},b} \frac{1}{2}\|\mathbf{w}\|^2 \\ \\ \text{Subject to:} \\ y_i(\mathbf{w}^T\mathbf{x}_i + b) \geq 1, \,\, \forall i \\ \\ \text{Where:} \\ y_i: \text{ Class label of the }i\text{-th sample } (+1 \text{ or } -1) \\ \mathbf{x}_i: \text{ Feature vector of the }i\text{-th sample} \end{array}$$</p><p>This constraint given above in the objective function ensures that all the data points are not misclassified and the stay outside the margin.</p>
<h3 id="heading-soft-margin-svm">Soft Margin SVM</h3>
<p>The Soft Margin SVM is lenient, as it allows some misclassifications. It’s suitable for real-world datasets, which are noisy, and it handles non-linearly separable data. It introduces a slack variable that penalizes incorrect predictions.</p>
<p>$$\begin{array}{l} \text{Objective Function:} \ \min_{\mathbf{w},b,\xi} \frac{1}{2}\|\mathbf{w}\|^2 + C\sum_{i=1}^n \xi_i \\ \\ \text{Subject to:} \\ y_i(\mathbf{w}^T\mathbf{x}_i + b) \geq 1 - \xi_i, \,\, \forall i \\ \xi_i \geq 0, \,\, \forall i \\ \\ \text{Where:} \\ \xi_i: \text{ Slack variables representing the degree of misclassification or} \\ \text{margin violation.} \\ C: \text{ Regularization parameter controlling the trade-off between} \\ \text{margin maximization and error minimization.} \end{array}$$</p><p>The hyperparameter C helps to control the penalty for a balance between margin maximization and error minimization. A large C value minimizes the classification errors, but causes a smaller margin. A small C value allows some misclassifications but causes a larger margin.</p>
<h3 id="heading-nonlinear-classification-problems">Nonlinear Classification Problems</h3>
<p>Non-linear classification problems include datasets with non-linear patterns that are difficult for linear SVM models to capture. This is a drawback, but SVM kernels can help.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734969794598/58fcb341-735f-4a57-943b-c748b7a3f85c.png" alt="58fcb341-735f-4a57-943b-c748b7a3f85c" class="image--center mx-auto" width="714" height="493" loading="lazy"></p>
<p>Non-linear classification contains datasets with complicated relationships and linear models like linear regression will not be able to accurately generate predictions or identify trends.</p>
<h2 id="heading-understanding-kernel-functions">Understanding Kernel Functions</h2>
<p>In kernel functions, we transform the dataset used in the classification task into a higher dimensional feature space. This line of action enables the hyperplane (a linear decision boundary) to split the data as linearly separable data.</p>
<p>For example, if a dataset contains three features in a 2D plane, the kernel function converts the data to a 3D plane, making it much simpler to partition the dataset using a basic hyperplane. This technique can be used to capture non-linear relationships in data.</p>
<p>To provide a clearer mental image, consider three distinct feature sets in the 2D plane (x and y). This can be taken to a 3D plane by the kernel machine, where <code>features x1</code> and <code>feature x2</code> may be in the x-y plane, which is readily divided by a simple hyperplane, and <code>feature x3</code> may be in the y-z plane, which is already separated.</p>
<h3 id="heading-the-kernel-trick-explained">The Kernel Trick Explained</h3>
<p>Transformation into a higher dimensional space is computationally intensive and is not the best option. But we know the importance of kernel functions in classifying non-linear data. So, what’s the way forward to still achieve the same feat while bypassing the cost of computation? It’s called the kernel trick. The kernel trick explains the “magic power” of the kernel functions.</p>
<p>The kernel trick is the computation of the inner or dot product between the data points in the original dimensional space instead of transforming the data into a higher-dimensional space before doing the computation.</p>
<p>The right side of the equation below shows the dot product of ϕ(x), representing the transformed vector into a higher dimensional space (which is not efficient). It’s the same as a kernel function at the left hand side:</p>
<p>$$K(x_i, x_j) = \phi(x_i) \cdot \phi(x_j)$$</p><p>The purpose of the kernel trick is to perform computation based on the data point in its original dimensional space, instead of performing calculations on complex data that might require an infinite number of dimensions.</p>
<h3 id="heading-mathematical-implementation-of-the-kernel-trick">Mathematical Implementation of the Kernel Trick</h3>
<p>Suppose we have two classes of data that are non-linear in the 2D space representing the original feature space. No straight line can separate these points because they lie diagonally across the origin.</p>
<p>$$\begin{array}{l} \textbf{Mapping Without Kernel Trick: }\\ \\ \begin{align*} \textbf{The 2D data is given as: } \\ &amp; \mathbf{x}_1 = (1,1), &amp; y_1 = +1 \\ &amp; \mathbf{x}_2 = (-1,-1), &amp; y_2 = -1 \end{align*} \\ \\ \textbf{Let's use a mapping function: } \\ \\ \phi(x, y) = (x^2, \sqrt{2}xy, y^2)\ \\ \\ Mapping\ \mathbf{x}_1 \ and \ \ \mathbf{x}_2: \\ \\ \begin{array}{l} - \ \phi(\mathbf{x}_1) = (1^2, \sqrt{2}(1)(1), 1^2) = (1, \sqrt{2}, 1) \\ \\ -\ \phi(\mathbf{x}_2) = ((-1)^2, \sqrt{2}(-1)(-1), (-1)^2) = (1, \sqrt{2}, 1) \end{array} \\ \\ \\ \textbf{Dot Product in Higher-Dimensional Space:} \\ \\ \phi(\mathbf{x}_1) \cdot \phi(\mathbf{x}_2) = (1)(1) + (\sqrt{2})(\sqrt{2}) + (1)(1) = 1 + 2 + 1 = 4 \\ \\ \\ \begin{array}{l} \text{This is the dot product of }\mathbf{x}_1\text{ and }\mathbf{x}_2\text{ after explicitly} \\ \text{mapping them to the higher-dimensional space.} \end{array} \end{array}$$</p><p>$$\begin{array}{l} \textbf{Using the Kernel Trick: }\\ \\ \textbf{Polynomial Kernel Definition:} \\ \\ K(\mathbf{x}_i, \mathbf{x}_j) = (\mathbf{x}_i^\top \mathbf{x}_j + c)^d \\ \\ \textbf{For this example:} \\ \\ d = 2 \ (\text{degree of the polynomial}), \quad c = 0 \ (\text{no bias term}) \\ \\ \textbf{Given: } \\ \\ \mathbf{x}_1 = (1, -1), \quad \mathbf{x}_2 = (-1, -1) \\ \\ \textbf{Compute } K(\mathbf{x}_1, \mathbf{x}_2): \\ \\ \begin{align*} K(\mathbf{x}_1, \mathbf{x}_2) &amp;= ((1)(-1) + (1)(-1))^2 \\ &amp;= (-1 - 1)^2 \\ &amp;= (-2)^2 \\ &amp;= 4 \end{align*} \\ \\ \begin{array}{l} \text{Using the kernel trick, we directly compute the dot product in the higher} \\ \text{dimensional space without explicitly mapping the points.} \end{array} \end{array}$$</p><h2 id="heading-popular-kernel-functions">Popular Kernel Functions</h2>
<h3 id="heading-linear-kernel">Linear kernel</h3>
<p>For a dataset that is linearly separable, the linear kernel is ideal. When used for non-linear data sets, which are the main topic of this article, it may result in underfitting and create a linear decision boundary. It’s provided as the input feature vectors' dot product.</p>
<p>This kernel merely constructs the hyperplane or line of best fit to divide the data points. It does not perform any particular transformation to a higher dimension.</p>
<p>$$Linear Kernel Function: K(x_i, x_j) = x_i \cdot x_j$$</p><h3 id="heading-polynomial-kernel">Polynomial kernel</h3>
<p>The polynomial kernel transforms the data into a polynomial feature space of order d. It does a dot product on the feature vector with a constant c, all within the degree of d. The higher the degree of the polynomial, the better the kernel captures the relationships in the nonlinear dataset.</p>
<p>$$Polynomial Kernel Function: K(x_i, x_j) = (x_i \cdot x_j + c)^d$$</p><h3 id="heading-gaussian-or-radial-basis-function-rbf-kernel">Gaussian or Radial Basis Function (RBF) kernel</h3>
<p>The Gaussian kernel, also known as the RBF kernel, is often used in SVM to map the input feature vector to an infinite-dimensional feature space using a Gaussian function. This kernel can handle more complex relationships.</p>
<p>$$RBF Kernel Function: K(x_i, x_j) = \exp(-\gamma \|x_i - x_j\|^2)$$</p><h3 id="heading-sigmoid-kernel">Sigmoid kernel</h3>
<p>The sigmoid kernel acts similarly to the activation function in neural networks. It functions similarly to a two-layered perception network and can map data into a higher-dimensional feature space.</p>
<p>$$Sigmoid Kernel Function: K(x_i, x_j) = \tanh(\alpha(x_i \cdot x_j) + c)$$</p><p>There are other kernel functions such as Laplacian kernels, hyperbolic kernels, exponential kernels, and custom kernels that you can look into if you’re curious.</p>
<h2 id="heading-how-to-choose-the-right-kernel">How to Choose the Right Kernel</h2>
<p>The various kernel functions are applied based on the linear and nonlinear relationships in the feature space. The linear kernel is simple and fast, and it works well with linearly separable data but not with high-dimensional data.</p>
<p>The polynomial kernel is well-suited for data with non-linear or polynomial relationships, as well as low-dimensional data. The RBF kernel is ideal for dense data that you have no prior knowledge of. Finally, the sigmoid kernel works well for binary and categorical data points.</p>
<h2 id="heading-svm-kernel-implementation">SVM Kernel Implementation</h2>
<p>Let’s now go through an example showing how you can use this technique.</p>
<h3 id="heading-step-1-import-the-necessary-libraries"><strong>Step 1: Import the necessary libraries</strong></h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
<span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt
<span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> make_circles
<span class="hljs-keyword">from</span> mpl_toolkits.mplot3d <span class="hljs-keyword">import</span> Axes3D
<span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> StandardScaler
</code></pre>
<h3 id="heading-step-2-generate-the-non-linear-dataset"><strong>Step 2: Generate the non-linear dataset</strong></h3>
<p>The non-linear dataset used in this article is a circle dataset from <code>sklearn.datasets</code>. We used 1500 samples with a <code>random_state</code> of 46 to keep the dataset consistent for reproducibility. We added a Gaussian noise to the data of 10%. This <code>function generate_circle_data</code> is implemented to generate the dataset used in the article.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_circle_data</span>(<span class="hljs-params">n_samples=<span class="hljs-number">1500</span>, noise=<span class="hljs-number">0.10</span>, random_state=<span class="hljs-number">46</span></span>):</span>
    <span class="hljs-string">"""
    Generate two concentric circles dataset.

    Parameters:
    -----------
    n_samples : int
        The total number of points generated
    noise : float
        Standard deviation of Gaussian noise added to the data
    random_state : int
        Random seed for reproducibility

    Returns:
    --------
    X : array of shape [n_samples, 2]
        The generated samples
    y : array of shape [n_samples]
        The integer labels (0 or 1) for class membership of each sample
    """</span>
    <span class="hljs-keyword">return</span> make_circles(n_samples=n_samples, 
                       noise=noise, 
                       random_state=random_state)
</code></pre>
<h3 id="heading-step-3-plot-the-2d-data"><strong>Step 3: Plot the 2D Data</strong></h3>
<p>The data generated above comes in 2D form. Each color represents the two different data samples. The data points were plotted which allows us to see it as a circular dataset using the <code>Matplotlib</code> library.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">plot_2d_data</span>(<span class="hljs-params">X, y, title=<span class="hljs-string">"2D Circle Dataset"</span></span>):</span>
    <span class="hljs-string">"""
    Plot the 2D dataset with different colors for each class.

    Parameters:
    -----------
    X : array-like of shape (n_samples, 2)
        The input samples
    y : array-like of shape (n_samples,)
        The target values (class labels)
    title : str
        The title of the plot
    """</span>
    plt.figure(figsize=(<span class="hljs-number">8</span>, <span class="hljs-number">6</span>))
    plt.scatter(X[:, <span class="hljs-number">0</span>], X[:, <span class="hljs-number">1</span>], c=y, marker=<span class="hljs-string">'.'</span>, cmap=<span class="hljs-string">'viridis'</span>)
    plt.title(title)
    plt.xlabel(<span class="hljs-string">'X₁'</span>)
    plt.ylabel(<span class="hljs-string">'X₂'</span>)
    plt.colorbar(label=<span class="hljs-string">'Class'</span>)
    plt.grid(<span class="hljs-literal">True</span>, alpha=<span class="hljs-number">0.3</span>)
    plt.show()
</code></pre>
<p>The output image of the dataset is given below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734978596496/a529c7e6-13ce-427e-b462-1241ea6de1bf.png" alt="Output of circular dataset" class="image--center mx-auto" width="913" height="656" loading="lazy"></p>
<h3 id="heading-step-4-transform-into-a-higher-dimensional-space"><strong>Step 4: Transform into a Higher-Dimensional Space</strong></h3>
<p>The data in 2D is transformed into a 3D space using the polynomial kernel. We achieved this by creating a third feature X3 so it can be mapped into a higher dimensional space for easy separation.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform_to_3d</span>(<span class="hljs-params">X</span>):</span>
    <span class="hljs-string">"""Transform 2D data to 3D using radius-based transformation"""</span>
    X1 = X[:, <span class="hljs-number">0</span>].reshape(<span class="hljs-number">-1</span>, <span class="hljs-number">1</span>)
    X2 = X[:, <span class="hljs-number">1</span>].reshape(<span class="hljs-number">-1</span>, <span class="hljs-number">1</span>)
    <span class="hljs-comment"># Modified transformation to create better separation</span>
    X3 = X1**<span class="hljs-number">2</span> + X2**<span class="hljs-number">2</span>
    <span class="hljs-keyword">return</span> np.hstack((X1, X2, X3))
</code></pre>
<h3 id="heading-step-5-plot-the-3d-transformation">Step 5: Plot the 3D Transformation</h3>
<p>The next step is to plot the 3D transformed dataset. It now looks like a U-shaped bowl, and is separated with a hyperplane after fitting a <code>LinearSVC</code> model from the <code>sklearn</code> library as the kernel we’re using. This shows a practical example of the concepts you’ve learned so far:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">plot_3d_transformation_with_separator</span>(<span class="hljs-params">X_transformed, y, title=<span class="hljs-string">"3D Transformed Dataset with Linear Separator"</span></span>):</span>
    <span class="hljs-string">"""Plot the 3D transformed dataset with a clear linear separating plane"""</span>

    <span class="hljs-comment"># Scale the transformed features</span>
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X_transformed)

    <span class="hljs-comment"># Fit linear SVM with adjusted parameters for better separation</span>
    svm = LinearSVC(C=<span class="hljs-number">1.0</span>, dual=<span class="hljs-string">"auto"</span>, max_iter=<span class="hljs-number">5000</span>)
    svm.fit(X_scaled, y)

    <span class="hljs-comment"># Create the 3D plot</span>
    fig = plt.figure(figsize=(<span class="hljs-number">12</span>, <span class="hljs-number">8</span>))
    ax = fig.add_subplot(<span class="hljs-number">111</span>, projection=<span class="hljs-string">'3d'</span>)

    <span class="hljs-comment"># Plot the two classes with different colors and markers for clarity</span>
    class_0 = y == <span class="hljs-number">0</span>
    class_1 = y == <span class="hljs-number">1</span>

    ax.scatter(X_transformed[class_0, <span class="hljs-number">0</span>], 
              X_transformed[class_0, <span class="hljs-number">1</span>], 
              X_transformed[class_0, <span class="hljs-number">2</span>],
              c=<span class="hljs-string">'blue'</span>, 
              marker=<span class="hljs-string">'o'</span>,
              label=<span class="hljs-string">'Class 0'</span>,
              alpha=<span class="hljs-number">0.6</span>)

    ax.scatter(X_transformed[class_1, <span class="hljs-number">0</span>], 
              X_transformed[class_1, <span class="hljs-number">1</span>], 
              X_transformed[class_1, <span class="hljs-number">2</span>],
              c=<span class="hljs-string">'red'</span>, 
              marker=<span class="hljs-string">'^'</span>,
              label=<span class="hljs-string">'Class 1'</span>,
              alpha=<span class="hljs-number">0.6</span>)

    <span class="hljs-comment"># Create a grid for the separator plane</span>
    x_min, x_max = X_transformed[:, <span class="hljs-number">0</span>].min() - <span class="hljs-number">0.2</span>, X_transformed[:, <span class="hljs-number">0</span>].max() + <span class="hljs-number">0.2</span>
    y_min, y_max = X_transformed[:, <span class="hljs-number">1</span>].min() - <span class="hljs-number">0.2</span>, X_transformed[:, <span class="hljs-number">1</span>].max() + <span class="hljs-number">0.2</span>

    xx, yy = np.meshgrid(np.linspace(x_min, x_max, <span class="hljs-number">50</span>),
                        np.linspace(y_min, y_max, <span class="hljs-number">50</span>))

    <span class="hljs-comment"># Get the separating plane coefficients</span>
    w = svm.coef_[<span class="hljs-number">0</span>]
    b = svm.intercept_[<span class="hljs-number">0</span>]

    <span class="hljs-comment"># Calculate z coordinates of the plane</span>
    grid_points = np.c_[xx.ravel(), yy.ravel(), np.zeros(xx.ravel().shape[<span class="hljs-number">0</span>])]
    scaled_grid = scaler.transform(grid_points)

    <span class="hljs-comment"># Calculate the separator plane</span>
    z = (-w[<span class="hljs-number">0</span>] * scaled_grid[:, <span class="hljs-number">0</span>] - w[<span class="hljs-number">1</span>] * scaled_grid[:, <span class="hljs-number">1</span>] - b) / w[<span class="hljs-number">2</span>]
    z = z.reshape(xx.shape)
    z = scaler.inverse_transform(np.c_[xx.ravel(), yy.ravel(), z.ravel()])[:, <span class="hljs-number">2</span>].reshape(xx.shape)

    <span class="hljs-comment"># Plot the separating plane with adjusted transparency</span>
    surface = ax.plot_surface(xx, yy, z, alpha=<span class="hljs-number">0.3</span>, cmap=<span class="hljs-string">'coolwarm'</span>)

    <span class="hljs-comment"># Customize the plot</span>
    ax.set_xlabel(<span class="hljs-string">'X₁'</span>)
    ax.set_ylabel(<span class="hljs-string">'X₂'</span>)
    ax.set_zlabel(<span class="hljs-string">'X₁² + X₂²'</span>)
    ax.set_title(title)

    <span class="hljs-comment"># Add legend</span>
    ax.legend()

    <span class="hljs-comment"># Adjust the viewing angle for better visualization</span>
    ax.view_init(elev=<span class="hljs-number">20</span>, azim=<span class="hljs-number">45</span>)

    <span class="hljs-comment"># Add text description</span>
    ax.text2D(<span class="hljs-number">0.05</span>, <span class="hljs-number">0.95</span>, 
              <span class="hljs-string">"Polynomial Kernel Transformation:\nΦ(x₁,x₂) → (x₁,x₂,x₁²+x₂²)\n\nClasses are linearly separable\nin transformed space"</span>, 
              transform=ax.transAxes, 
              bbox=dict(facecolor=<span class="hljs-string">'white'</span>, alpha=<span class="hljs-number">0.8</span>))

    plt.show()

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-comment"># Generate and plot the dataset</span>
    X, y = generate_circle_data()

    <span class="hljs-comment"># Transform and plot 3D data with clear separator</span>
    X_transformed = transform_to_3d(X)
    plot_3d_transformation_with_separator(X_transformed, y)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<p>The <code>main</code> function is a function of functions that put together all the other functions such as <code>generate_circle_data</code>, <code>transform_to_3d</code> and <code>plot_3d_transformation_with_separator</code> together to establish the model. The image below shows a better separation with the aid of the polynomial kernel.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734979669969/2e0af04a-93cc-44e8-8ed5-15a484385fd1.png" alt="2e0af04a-93cc-44e8-8ed5-15a484385fd1" class="image--center mx-auto" width="694" height="754" loading="lazy"></p>
<h3 id="heading-heres-the-full-code">Here’s the full code:</h3>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="f980d950df07000b6779e53641f13a4d">
        <script src="https://gist.github.com/Josiah-Adesola/f980d950df07000b6779e53641f13a4d.js"></script></div><p> </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned about the efficiency of SVM kernels for non-linear classification applications. The various functions demonstrated computational efficiency by changing input data into higher dimensional data, as shown in the example, without requiring vast amounts of storage or processing.</p>
<p>SVM can be used in a variety of classification tasks, including image and text classification, and it has proven to be extremely efficient.</p>
<h3 id="heading-references">References</h3>
<ol>
<li><p>Park, H., &amp; Son, J.-H. (2021). Machine learning techniques for THz imaging and time-domain spectroscopy. <em>Sensors, 21</em>(4), 1186. <a target="_blank" href="https://doi.org/10.3390/s21041186">https://doi.org/10.3390/s21041186</a></p>
</li>
<li><p><a target="_blank" href="https://scikit-learn.org/1.5/modules/svm.html">Scikit-learn developers. (2024). Support vector machines. Scikit-learn.https://scikit-learn.org/1.5/modules/svm.html</a></p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
