<?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" version="2.0"><channel><title><![CDATA[DevLogs by Sakib]]></title><description><![CDATA[Ideas, experiments and lessons from working in tech. From code quality and developer workflows to tools, architecture and the occasional opinion piece. 🌐 Learn]]></description><link>https://blog.sakibmiyahn.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1749269687433/ba306843-b092-4e8a-92ab-8948cc127c76.png</url><title>DevLogs by Sakib</title><link>https://blog.sakibmiyahn.com</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 19:04:41 GMT</lastBuildDate><atom:link href="https://blog.sakibmiyahn.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How AI Finds Its Way: The Logic of Search]]></title><description><![CDATA[When we discuss intelligence, we often think about making informed decisions. But decisions don’t always happen in a single step. Whether it’s navigating a maze, planning a delivery route, or solving a puzzle, many problems require an agent to figure...]]></description><link>https://blog.sakibmiyahn.com/how-ai-finds-its-way-the-logic-of-search</link><guid isPermaLink="true">https://blog.sakibmiyahn.com/how-ai-finds-its-way-the-logic-of-search</guid><category><![CDATA[AIPlanning]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Search Algorithms]]></category><category><![CDATA[Problem Solving]]></category><category><![CDATA[AIButWhatIsItReally]]></category><dc:creator><![CDATA[Sakib Miyahn]]></dc:creator><pubDate>Sun, 07 Sep 2025 09:58:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/183Yxo3vsGY/upload/c83581b4e9ed5bc66047f41751fad3b0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When we discuss intelligence, we often think about making informed decisions. But decisions don’t always happen in a single step. Whether it’s navigating a maze, planning a delivery route, or solving a puzzle, many problems require an agent to figure out a sequence of actions, not just what to do, but in what order.</p>
<p>This process of finding a path through a space of possibilities is what we refer to as <strong>search</strong> in artificial intelligence. It’s one of the most fundamental problem-solving tools in the field. And while it may sound simple, there’s a surprising amount of depth and elegance in how it works.</p>
<blockquote>
<p>“In which we see how an agent can find a sequence of actions that achieves its goals when no single action will do.” - Peter Norvig and Stuart J. Russel</p>
</blockquote>
<h2 id="heading-from-real-world-goals-to-search-problems">From Real-World Goals to Search Problems</h2>
<p>Before an AI system can search, it needs to understand what it’s searching for. This means turning a real-world problem into a well-defined structure that the machine can reason about. We start by defining the <strong>initial state</strong>, where the agent begins. We also define the <strong>goal</strong>, what the agent is trying to reach. Then we list all the <strong>actions</strong> available at each point, and describe how those actions move the agent from one state to another. This forms a <strong>search space</strong>, a kind of mental map the agent can explore.</p>
<p>Imagine a robot in a house. Its initial state might be <code>in the kitchen</code> and the goal is <code>in the living room</code>. The actions are things like <code>move forward</code>, <code>turn left</code> or <code>open door</code>. Once this is modeled, the AI can start figuring out how to get from point A to point B.</p>
<h2 id="heading-how-machines-search"><strong>How Machines Search</strong></h2>
<p>To find a path to the goal, the AI explores different possible sequences of actions, essentially walking a decision tree. Each branch represents a new state that results from taking an action. Some paths lead to dead ends. Others eventually reach the goal.</p>
<p>Unlike humans, machines don’t have intuition or shortcuts built in. So we need to give them <strong>algorithms</strong>, precise strategies for exploring the search space.</p>
<p>Some of these strategies are quite basic, treating all options equally. Others use clever tricks to prioritise certain paths over others. Broadly, we divide them into two categories: <strong>uninformed</strong> and <strong>informed</strong> search.</p>
<h2 id="heading-blind-strategies-uninformed-search">Blind Strategies: Uninformed Search</h2>
<p>Uninformed search algorithms don’t use any knowledge about the goal’s location. They treat all states equally and rely solely on the problem definition. These methods are useful when no heuristic guidance is available.</p>
<h3 id="heading-breadth-first-search-bfs">Breadth-First Search (BFS)</h3>
<p>It explores all paths that take one step, then all paths that take two steps, and so on. It guarantees the shortest solution (in terms of number of steps), but can be slow and memory-intensive.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*GChPGXvZQiVwjok9EvKPIA.gif" alt class="image--center mx-auto" /></p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">BFS</span>(<span class="hljs-params">graph, start, goal</span>):</span>
    <span class="hljs-comment"># Initialize the queue with the starting node and an empty path</span>
    queue = [(start, [])]
    <span class="hljs-comment"># Set to keep track of visited nodes</span>
    visited = set()
    <span class="hljs-comment"># Counter to keep track of node expansions</span>
    node_expansions = <span class="hljs-number">0</span>

    <span class="hljs-keyword">while</span> queue:
        <span class="hljs-comment"># Dequeue a node and its path</span>
        node, path = queue.pop(<span class="hljs-number">0</span>)

        <span class="hljs-comment"># If the node has been visited, skip it</span>
        <span class="hljs-keyword">if</span> node <span class="hljs-keyword">in</span> visited:
            <span class="hljs-keyword">continue</span>

        <span class="hljs-comment"># Mark the node as visited</span>
        visited.add(node)

        <span class="hljs-comment"># Append the current node to the path</span>
        path = path + [node]

        <span class="hljs-comment"># If the goal is reached, return the path, its length, and the number of node expansions</span>
        <span class="hljs-keyword">if</span> node == goal:
            <span class="hljs-keyword">return</span> path, len(path), node_expansions

        <span class="hljs-comment"># Increment the node expansions counter</span>
        node_expansions += <span class="hljs-number">1</span>

        <span class="hljs-comment"># Add all unvisited neighbors to the queue</span>
        <span class="hljs-keyword">for</span> neighbor <span class="hljs-keyword">in</span> graph.neighbors(node):
            <span class="hljs-keyword">if</span> neighbor <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> visited:
                queue.append((neighbor, path))

    <span class="hljs-comment"># If no path is found, return an empty list, infinity for path length, and the number of node expansions</span>
    <span class="hljs-keyword">return</span> [], float(<span class="hljs-string">"inf"</span>), node_expansions
</code></pre>
<h3 id="heading-depth-first-search-dfs">Depth-First Search (DFS)</h3>
<p>It goes as far down a single path as it can before backtracking. It’s more memory-efficient but can get lost if the search space is deep or looping.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*yBXw4Q8rSMRqGYC-iZI0yg.gif" alt class="image--center mx-auto" /></p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">DFS</span>(<span class="hljs-params">graph, start, goal</span>):</span>
    <span class="hljs-comment"># Initialize the stack with the starting node and an empty path</span>
    stack = [(start, [])]
    <span class="hljs-comment"># Set to keep track of visited nodes</span>
    visited = set()
    <span class="hljs-comment"># Counter to keep track of node expansions</span>
    node_expansions = <span class="hljs-number">0</span>

    <span class="hljs-keyword">while</span> stack:
        <span class="hljs-comment"># Pop a node and its path from the stack</span>
        node, path = stack.pop()

        <span class="hljs-comment"># If the node has been visited, skip it</span>
        <span class="hljs-keyword">if</span> node <span class="hljs-keyword">in</span> visited:
            <span class="hljs-keyword">continue</span>

        <span class="hljs-comment"># Mark the node as visited</span>
        visited.add(node)

        <span class="hljs-comment"># Append the current node to the path</span>
        path = path + [node]

        <span class="hljs-comment"># If the goal is reached, return the path, its length, and the number of node expansions</span>
        <span class="hljs-keyword">if</span> node == goal:
            <span class="hljs-keyword">return</span> path, len(path), node_expansions

        <span class="hljs-comment"># Increment the node expansions counter</span>
        node_expansions += <span class="hljs-number">1</span>

        <span class="hljs-comment"># Add all unvisited neighbors to the stack</span>
        <span class="hljs-keyword">for</span> neighbor <span class="hljs-keyword">in</span> reversed(list(graph.neighbors(node))):
            <span class="hljs-keyword">if</span> neighbor <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> visited:
                stack.append((neighbor, path))

    <span class="hljs-comment"># If no path is found, return an empty list, infinity for path length, and the number of node expansions</span>
    <span class="hljs-keyword">return</span> [], float(<span class="hljs-string">"inf"</span>), node_expansions
</code></pre>
<h3 id="heading-uniform-cost-search-ucs">Uniform Cost Search (UCS)</h3>
<p>Adds a layer of realism by considering the cost of each action. Instead of counting steps, it looks for the path with the lowest total cost, useful when some actions are harder, slower, or more expensive than others.</p>
<p><img src="https://raw.githubusercontent.com/AghdamAmir/UCS-and-A-star-search/main/ucs-gif.gif" alt class="image--center mx-auto" /></p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">UCS</span>(<span class="hljs-params">graph, start, goal</span>):</span>
    <span class="hljs-comment"># Initialize the priority queue with the starting node, cost 0, and an empty path</span>
    queue = [(<span class="hljs-number">0</span>, start, [])]
    <span class="hljs-comment"># Set to keep track of visited nodes</span>
    visited = set()
    <span class="hljs-comment"># Counter to keep track of node expansions</span>
    node_expansions = <span class="hljs-number">0</span>

    <span class="hljs-keyword">while</span> queue:
        <span class="hljs-comment"># Dequeue the node with the lowest cost</span>
        cost, node, path = heapq.heappop(queue)

        <span class="hljs-comment"># If the node has been visited, skip it</span>
        <span class="hljs-keyword">if</span> node <span class="hljs-keyword">in</span> visited:
            <span class="hljs-keyword">continue</span>

        <span class="hljs-comment"># Mark the node as visited</span>
        visited.add(node)

        <span class="hljs-comment"># Append the current node to the path</span>
        path = path + [node]

        <span class="hljs-comment"># If the goal is reached, return the path, its length, and the number of node expansions</span>
        <span class="hljs-keyword">if</span> node == goal:
            <span class="hljs-keyword">return</span> path, cost, node_expansions

        <span class="hljs-comment"># Increment the node expansions counter</span>
        node_expansions += <span class="hljs-number">1</span>

        <span class="hljs-comment"># Add all unvisited neighbors to the priority queue with their cumulative cost</span>
        <span class="hljs-keyword">for</span> neighbor <span class="hljs-keyword">in</span> graph.neighbors(node):
            <span class="hljs-keyword">if</span> neighbor <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> visited:
                edge_weight = graph.edges[node, neighbor].get(<span class="hljs-string">"weight"</span>, <span class="hljs-number">1</span>)
                heapq.heappush(queue, (cost + edge_weight, neighbor, path))

    <span class="hljs-comment"># If no path is found, return an empty list, infinity for path length, and the number of node expansions</span>
    <span class="hljs-keyword">return</span> [], float(<span class="hljs-string">"inf"</span>), node_expansions
</code></pre>
<p>All of these algorithms are systematic, but none of them are particularly smart. They don’t know which direction is better; they just explore everything. A few other uninformed search algorithms include:</p>
<ul>
<li><p><strong>Depth-Limited Search</strong> - DFS with a fixed depth limit. Prevents infinite loops, but might miss deeper solutions.</p>
</li>
<li><p><strong>Iterative Deepening Search (IDDFS)</strong> - Combines the space efficiency of DFS with the completeness of BFS. Performs DFS to increasing depths.</p>
</li>
<li><p><strong>Bidirectional Search</strong> - Searches forward from the start and backward from the goal simultaneously. Efficient if the goal is known.</p>
</li>
</ul>
<h2 id="heading-smarter-strategies-informed-heuristic-search">Smarter Strategies: Informed (Heuristic) Search</h2>
<p>Informed search, sometimes called heuristic search, brings some knowledge into the process. These algorithms use heuristics (estimates of how far a given state is from the goal) to make better choices about what to explore next.</p>
<h3 id="heading-greedy-best-first-search">Greedy Best-First Search</h3>
<p>Always picks the path that looks closest to the goal. It’s fast but not always accurate; sometimes it’s lured into traps because it doesn’t consider the cost of the path so far.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*mVI8w1-tOSsN78kIhYK1YA.gif" alt class="image--center mx-auto" /></p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">best_first_search</span>(<span class="hljs-params">graph, start, goal, heuristic</span>):</span>
    <span class="hljs-comment"># Initialize the priority queue with the starting node, its heuristic value, and an empty path</span>
    queue = [(heuristic[start], start, [])]
    <span class="hljs-comment"># Set to keep track of visited nodes</span>
    visited = set()
    <span class="hljs-comment"># Counter to keep track of node expansions</span>
    node_expansions = <span class="hljs-number">0</span>

    <span class="hljs-keyword">while</span> queue:
        <span class="hljs-comment"># Dequeue the node with the lowest heuristic value</span>
        _, node, path = heapq.heappop(queue)

        <span class="hljs-comment"># If the node has been visited, skip it</span>
        <span class="hljs-keyword">if</span> node <span class="hljs-keyword">in</span> visited:
            <span class="hljs-keyword">continue</span>

        <span class="hljs-comment"># Mark the node as visited</span>
        visited.add(node)

        <span class="hljs-comment"># Append the current node to the path</span>
        path = path + [node]

        <span class="hljs-comment"># If the goal is reached, return the path, its length, and the number of node expansions</span>
        <span class="hljs-keyword">if</span> node == goal:
            <span class="hljs-keyword">return</span> path, len(path), node_expansions

        <span class="hljs-comment"># Increment the node expansions counter</span>
        node_expansions += <span class="hljs-number">1</span>

        <span class="hljs-comment"># Add all unvisited neighbors to the priority queue with their heuristic value</span>
        <span class="hljs-keyword">for</span> neighbor <span class="hljs-keyword">in</span> graph.neighbors(node):
            <span class="hljs-keyword">if</span> neighbor <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> visited:
                heapq.heappush(queue, (heuristic[neighbor], neighbor, path))

    <span class="hljs-comment"># If no path is found, return an empty list, infinity for path length, and the number of node expansions</span>
    <span class="hljs-keyword">return</span> [], float(<span class="hljs-string">"inf"</span>), node_expansions
</code></pre>
<h3 id="heading-a-search">A* Search</h3>
<p>It tries to balance two things: how much progress has already been made, and how much further there is to go (based on the heuristic). If the heuristic is well-designed, A* can find the best path quickly and reliably.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*sbUuCeAb1EHgYBszGOXSWg.gif" alt class="image--center mx-auto" /></p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">a_star</span>(<span class="hljs-params">graph, start, goal, heuristic</span>):</span>
    <span class="hljs-comment"># Initialize the priority queue with the starting node, cost 0, its heuristic value, and an empty path</span>
    queue = [(heuristic[start], <span class="hljs-number">0</span>, start, [])]
    <span class="hljs-comment"># Set to keep track of visited nodes</span>
    visited = set()
    <span class="hljs-comment"># Counter to keep track of node expansions</span>
    node_expansions = <span class="hljs-number">0</span>

    <span class="hljs-keyword">while</span> queue:
        <span class="hljs-comment"># Dequeue the node with the lowest cost + heuristic value</span>
        _, cost, node, path = heapq.heappop(queue)

        <span class="hljs-comment"># If the node has been visited, skip it</span>
        <span class="hljs-keyword">if</span> node <span class="hljs-keyword">in</span> visited:
            <span class="hljs-keyword">continue</span>

        <span class="hljs-comment"># Mark the node as visited</span>
        visited.add(node)

        <span class="hljs-comment"># Append the current node to the path</span>
        path = path + [node]

        <span class="hljs-comment"># If the goal is reached, return the path, its length, and the number of node expansions</span>
        <span class="hljs-keyword">if</span> node == goal:
            <span class="hljs-keyword">return</span> path, cost, node_expansions

        <span class="hljs-comment"># Increment the node expansions counter</span>
        node_expansions += <span class="hljs-number">1</span>

        <span class="hljs-comment"># Add all unvisited neighbors to the priority queue with their cumulative cost + heuristic value</span>
        <span class="hljs-keyword">for</span> neighbor <span class="hljs-keyword">in</span> graph.neighbors(node):
            <span class="hljs-keyword">if</span> neighbor <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> visited:
                <span class="hljs-comment"># This line retrieves the weight (or cost) of the edge between the current node and the neighbor.</span>
                <span class="hljs-comment"># The code uses .get("weight", 1) to get the weight of the edge, with a default value of 1 if the weight is not specified.</span>
                edge_weight = graph.edges[node, neighbor].get(<span class="hljs-string">"weight"</span>, <span class="hljs-number">1</span>)
                total_cost = cost + edge_weight
                heapq.heappush(
                    queue,
                    (total_cost + heuristic[neighbor], total_cost, neighbor, path),
                )

    <span class="hljs-comment"># If no path is found, return an empty list, infinity for path length, and the number of node expansions</span>
    <span class="hljs-keyword">return</span> [], float(<span class="hljs-string">"inf"</span>), node_expansions
</code></pre>
<p>Designing good heuristics is an art of its own. A heuristic must be efficient to compute and ideally never overestimate the true cost. When done right, it can drastically improve performance, especially in large or complex search spaces.</p>
<h2 id="heading-why-search-matters">Why Search Matters</h2>
<p>Search algorithms are the decision-making backbone of many AI systems: robot navigation, game playing, logistics, and even basic planning in smart assistants.</p>
<p>Each algorithm offers trade-offs in speed, memory, and optimality. Some are fast but risky, others are thorough but slow. Choosing the right one depends on what your agent is trying to do, and how much it knows about the world.</p>
<p>Understanding search is the first step in designing agents that can act intelligently, not just react, but think ahead, reason with uncertainty, and solve problems.</p>
<h2 id="heading-looking-ahead">Looking Ahead</h2>
<p>So far, we’ve focused on planning, choosing actions in a known world. But what if the world is unknown, complex, or constantly changing?</p>
<p>That’s where learning comes in. In our next article, we’ll explore neural networks, how they learn patterns from data, recognise images, understand speech, and drive modern AI systems.</p>
]]></content:encoded></item><item><title><![CDATA[What Even Is AI?]]></title><description><![CDATA[Welcome to this new series, “AI, But what is it really?” - exploring the core concepts behind artificial intelligence, one idea at a time.
Artificial Intelligence is everywhere right now - in headlines, tools, startups, fears, and dreams. But the ter...]]></description><link>https://blog.sakibmiyahn.com/what-even-is-ai</link><guid isPermaLink="true">https://blog.sakibmiyahn.com/what-even-is-ai</guid><category><![CDATA[#UnderstandingAI  ]]></category><category><![CDATA[#KnowledgeRepresentation  ]]></category><category><![CDATA[IntelligentAgents]]></category><category><![CDATA[AIButWhatIsItReally]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[#Artificial Intelligence #Machine Learning #Deep Learning #AI Models #Neural Networks #Predictive Analytics #Data Science #Natural Language Processing #Computer Vision #Recommender Systems #Transfer Learning #Supervised Learning #Unsupervised Learning #Robotics #Big Data #Computer Science #Ethics in AI #AI Applications #AI in Business #Future of AI]]></category><dc:creator><![CDATA[Sakib Miyahn]]></dc:creator><pubDate>Mon, 09 Jun 2025 11:21:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749467436302/b7d644dc-b4bb-4a0d-ac12-4c101066886d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to this new series, “AI, But what is it really?” - exploring the core concepts behind artificial intelligence, one idea at a time.</p>
<p>Artificial Intelligence is everywhere right now - in headlines, tools, startups, fears, and dreams. But the term gets thrown around so loosely that it’s easy to lose sight of what it actually means. Is AI just chatbots and neural networks? Is it machine learning? Is it robots? Is it Python?</p>
<p>This series aims to slow things down and build up a real understanding, piece by piece. In this series, we’ll explore the fundamental concepts in AI. We begin with the most basic (and most misunderstood) question: what even is AI?</p>
<h2 id="heading-a-brief-history-of-the-idea-of-intelligence"><strong>A Brief History of the Idea of Intelligence</strong></h2>
<p>The quest to understand and replicate intelligence didn’t start with computers. It goes back over 2,000 years to philosophers, mathematicians, and logicians trying to formalise how we think, reason, and learn.</p>
<ul>
<li><p><strong>Aristotle</strong> laid the early foundations of formal logic c. 350 BC.</p>
</li>
<li><p><strong>Euclid’s algorithms,</strong> c. 300 BC, anticipated procedural problem-solving.</p>
</li>
<li><p><strong>Francis Bacon</strong> introduced inductive reasoning in the 16th century - an early precursor to learning from data.</p>
</li>
<li><p><strong>Thomas Bayes</strong> (17th century) and <strong>Blaise Pascal</strong> (18th century) laid the foundations of probabilistic reasoning.</p>
</li>
<li><p><strong>Alan Turing</strong>, in 1950, proposed the imitation game and imagined a child machine - a mind that could learn and grow through experience.</p>
</li>
</ul>
<p>By the time the term “Artificial Intelligence” was coined at the Dartmouth Conference in 1956, many of the core ingredients were already in place: logic, search, probability, learning, and formal systems.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749463129961/27b758f9-97d7-475e-bb3a-384cf8901b0d.png" alt="turing test diagram" class="image--center mx-auto" /></p>
<blockquote>
<p>“I believe that in about fifty years time [2000] it will be possible to programme computers to make them play the imitation game so well that an average interrogator will not have more than 70 per cent chance of making the right identification after five minutes of questioning.” - Alan Turing</p>
</blockquote>
<h2 id="heading-so-what-is-ai"><strong>So, What Is AI?</strong></h2>
<p>At its core, <strong>intelligence</strong> is the ability to perceive, reason, and act adaptively in response to a changing environment. <strong>Artificial Intelligence</strong>, then, is the field that aims to build systems that can do exactly that - not just compute, but behave intelligently. Russell &amp; Norvig’s classic textbook frames it around four perspectives:</p>
<blockquote>
<p>“Systems that think humanly, act humanly, think rationally, or act rationally.”</p>
</blockquote>
<p>That’s a broad space - and on purpose. AI isn’t one technique. It’s a long-term attempt to make machines that behave intelligently, however that’s defined.</p>
<h3 id="heading-ai-is-not-ml-is-not-dl">AI Is Not ML Is Not DL</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749463692399/f37ae679-a8eb-4d48-be27-febc3b9ae2e8.png" alt="ai, ml, dl figure" class="image--center mx-auto" /></p>
<p>Let’s clear up a common confusion:</p>
<ul>
<li><p><strong>Artificial Intelligence (AI)</strong> is the goal: intelligent behaviour.</p>
</li>
<li><p><strong>Machine Learning (ML)</strong> is one approach to achieving that goal, focused on learning from data.</p>
</li>
<li><p><strong>Deep Learning (DL)</strong> is a type of ML model.</p>
</li>
<li><p><strong>Python</strong> (or any language) is just a tool, not part of the definition.</p>
</li>
</ul>
<p>We can build AI without learning, and we can use learning without achieving intelligence.</p>
<h2 id="heading-thinking-in-terms-of-agents">Thinking in Terms of Agents</h2>
<p>One of the most useful ways to understand AI systems is to think in terms of agents - entities that perceive their environment and act upon it to achieve specific goals. This agent-based view helps us break down complex behaviours into manageable, modular components.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749465397686/b05845c6-a95d-41d2-b88e-dfff6ae24f01.png" alt="intelligent agents digaram" class="image--center mx-auto" /></p>
<p>Agents come in many forms, each with increasing levels of sophistication:</p>
<ul>
<li><p><strong>Reactive agents</strong> respond directly to their current sensory input. They don’t store memory or learn - they simply react based on predefined rules. Think of a basic obstacle-avoiding robot.</p>
</li>
<li><p><strong>Model-based agents</strong> go a step further by maintaining an internal representation (or model) of the world. This allows them to handle partial information and make decisions based on past perceptions, not just current input.</p>
</li>
<li><p><strong>Goal-based agents</strong> evaluate possible actions against desired outcomes. Instead of blindly following rules, they consider what they’re trying to achieve and plan accordingly.</p>
</li>
<li><p><strong>Utility-based agents</strong> assign value (or “utility”) to outcomes and choose actions that maximise expected benefit, often weighing trade-offs under uncertainty.</p>
</li>
<li><p><strong>Learning agents</strong> improve over time. They adapt their behaviour based on experience, feedback, or exploration, allowing them to function effectively even in changing or unknown environments.</p>
</li>
</ul>
<p>This layered framework allows us to build intelligent behaviour incrementally, starting with simple reflexes and evolving toward agents that can plan, evaluate, and adapt in complex environments.</p>
<h2 id="heading-why-knowledge-matters">Why Knowledge Matters</h2>
<p>But sensing and acting aren’t enough. To behave truly intelligently, an agent must have some understanding of how the world works. That’s where <strong>knowledge representation and reasoning (KRR)</strong> come in. KRR is a fundamental area of AI that focuses on how knowledge about the world can be formally represented and used by a machine to solve complex tasks. This discipline intertwines the cognitive aspects of how humans understand and process information with the computational methods necessary for enabling machines to exhibit intelligent behaviour.</p>
<p>AI systems need a way to structure information about the world - facts, rules, relationships, categories - so that they can reason, infer, and make informed decisions. This is more than storing data; it’s about organising information in a way that supports meaningful conclusions.</p>
<p>Common approaches include:</p>
<ul>
<li><p><strong>Symbolic logic</strong>, such as propositional and first-order logic, which allows systems to make precise, rule-based inferences.</p>
</li>
<li><p><strong>Ontologies and taxonomies</strong>, which define structured hierarchies of categories and concepts, like “a car is a kind of vehicle.”</p>
</li>
<li><p><strong>Rule-based systems</strong> that encode expert knowledge as a series of “if-then” statements.</p>
</li>
<li><p><strong>Semantic networks</strong>, where objects, attributes, and relationships are connected in graph-like structures for efficient reasoning.</p>
</li>
</ul>
<p>These methods allow AI agents to go beyond reactive behaviour. For example, if a system knows: all fruit is edible, and apples are fruit, it can logically infer that apples are edible. That’s reasoning - not hardcoded knowledge, but structured inference drawn from more general principles. In short, knowledge representation enables AI to move from responding to understanding - from executing actions to explaining why those actions make sense.</p>
<h2 id="heading-looking-ahead">Looking Ahead</h2>
<p>Artificial Intelligence is not just deep learning, chatbots, or hype. It’s the structured study of intelligent behaviour, grounded in perception, logic, learning, and reasoning. In this article, we unpacked its roots, core definitions, and the foundational idea of intelligent agents.</p>
<p>In the next article, we’ll explore one of the most fundamental tools in any intelligent system: search. What is search, and why is it needed? How does human search compare to how machines search? And how do we translate real-world problems into ones a machine can solve efficiently.</p>
<p>See you then!</p>
]]></content:encoded></item><item><title><![CDATA[Setting Up Prettier, ESLint (Airbnb), Stylelint, Husky & lint-staged for a Consistent Node.js Workflow]]></title><description><![CDATA[Whether you’re coding solo or working in a team, one thing becomes obvious fast: style inconsistencies and formatting nitpicks waste time.
Over the years, I’ve landed on a setup that saves mental overhead, keeps the codebase clean and scales well acr...]]></description><link>https://blog.sakibmiyahn.com/setting-up-prettier-eslint-airbnb-stylelint-husky-and-lint-staged-for-a-consistent-nodejs-workflow</link><guid isPermaLink="true">https://blog.sakibmiyahn.com/setting-up-prettier-eslint-airbnb-stylelint-husky-and-lint-staged-for-a-consistent-nodejs-workflow</guid><category><![CDATA[lintstaged]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[eslint]]></category><category><![CDATA[Prettier]]></category><category><![CDATA[Stylelint]]></category><category><![CDATA[husky]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Code Quality]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Sakib Miyahn]]></dc:creator><pubDate>Thu, 05 Jun 2025 13:40:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749129828611/03b59ea9-6e8f-4ae8-aa4f-fed65ec7f9a8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Whether you’re coding solo or working in a team, one thing becomes obvious fast: style inconsistencies and formatting nitpicks waste time.</p>
<p>Over the years, I’ve landed on a setup that saves mental overhead, keeps the codebase clean and scales well across teams and projects. In this guide, I’ll walk you through how I configure Prettier, ESLint with Airbnb’s style guide, Stylelint, Husky and lint-staged in a Node.js environment.</p>
<p>Let’s start with a high-level view of what each tool brings to the table.</p>
<h2 id="heading-overview-what-each-tool-does-and-why-you-need-it"><strong>📦 Overview: What Each Tool Does (and Why You Need It)</strong></h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Tool</strong></td><td><strong>What It Solves</strong></td></tr>
</thead>
<tbody>
<tr>
<td><a target="_blank" href="https://prettier.io/docs/"><strong>Prettier</strong></a></td><td>Automatically formats code to a consistent style across file types.</td></tr>
<tr>
<td><a target="_blank" href="https://eslint.org/docs/latest/"><strong>ESLint</strong></a></td><td>Detects syntax errors and enforces coding best practices in JS/TS code.</td></tr>
<tr>
<td><a target="_blank" href="https://github.com/airbnb/javascript"><strong>Airbnb Config</strong></a></td><td>A widely adopted ESLint config that enforces readable, consistent JS code.</td></tr>
<tr>
<td><a target="_blank" href="https://stylelint.io/"><strong>Stylelint</strong></a></td><td>Linter for CSS/SCSS that catches errors and enforces style conventions.</td></tr>
<tr>
<td><a target="_blank" href="https://github.com/lint-staged/lint-staged#readme"><strong>lint-staged</strong></a></td><td>Ensures linters only run on staged files — fast and targeted.</td></tr>
<tr>
<td><a target="_blank" href="https://typicode.github.io/husky/"><strong>Husky</strong></a></td><td>Hooks into Git to run checks before code is committed, keeping your main branch clean.</td></tr>
</tbody>
</table>
</div><p>When combined, these tools reduce the surface area for bugs, reduce friction in code reviews and let you focus on solving real problems — not arguing about trailing commas.</p>
<h2 id="heading-step-1-prettier-your-formatting-backbone"><strong>🧹 Step 1: Prettier — Your Formatting Backbone</strong></h2>
<p>Prettier is an opinionated formatter. It doesn’t care how you want your code to look — it enforces a consistent, readable style. It supports many file types: .html, .json, .js, .ts, .css, .scss, .md, etc.</p>
<h3 id="heading-install"><strong>🔧 Install</strong></h3>
<pre><code class="lang-bash">npm install --save-dev prettier
</code></pre>
<p>Create a <code>.prettierrc</code> file at the root of your project. You can modify these settings as per your preferences. Refer to the <a target="_blank" href="https://prettier.io/docs/">Prettier</a> documentation for all available options. Here's a sample configuration I use:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"bracketSpacing"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"printWidth"</span>: <span class="hljs-number">120</span>,
  <span class="hljs-attr">"singleQuote"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"tabWidth"</span>: <span class="hljs-number">2</span>,
  <span class="hljs-attr">"trailingComma"</span>: <span class="hljs-string">"all"</span>
}
</code></pre>
<p><strong>Why these settings?</strong></p>
<ul>
<li><p><code>bracketSpacing</code> - Improves readability with space inside object literals: <code>{ key: value }</code>.</p>
</li>
<li><p><code>printWidth</code> - I find 80 too cramped; 120 gives more breathing room, especially for modern monitors.</p>
</li>
<li><p><code>singleQuote</code> - JavaScript traditionally uses single quotes; this reduces diff noise and aligns with many style guides.</p>
</li>
<li><p><code>tabWidth</code> - Standard size, especially in JS/TS communities.</p>
</li>
<li><p><code>trailingComma</code> - Makes diffs cleaner and reduces bugs when adding new lines.</p>
</li>
</ul>
<p>Create a <code>.prettierignore</code> file at the root of your project to exclude files/directories you don’t want Prettier to touch — generated files, IDE configs, etc.</p>
<pre><code class="lang-plaintext">node_modules
build
dist
coverage
.vscode
</code></pre>
<h2 id="heading-step-2-eslint-airbnb-code-quality-amp-best-practices"><strong>🧠 Step 2: ESLint + Airbnb — Code Quality &amp; Best Practices</strong></h2>
<p>Where Prettier handles style, ESLint enforces logic and structure. It catches things like unused variables, accidental globals and subtle syntax issues.</p>
<p>The Airbnb style guide is battle-tested and opinionated. It promotes clarity, consistency and best practices out of the box.</p>
<h3 id="heading-install-dependencies"><strong>🔧 Install dependencies</strong></h3>
<pre><code class="lang-bash">npm install --save-dev eslint eslint-plugin-node eslint-config-node
npm install --save -dev eslint-plugin-prettier eslint-config-prettier
npx install-peerdeps --dev eslint-config-airbnb-base
</code></pre>
<blockquote>
<p>Note: Use <code>eslint-config-airbnb</code> instead of <code>airbnb-base</code> if you’re working with React.</p>
</blockquote>
<p>Create an <code>.eslintrc.json</code> file at the root of your project. Here's a sample configuration that uses the Airbnb base rules (which exclude React-specific rules). It also adds Prettier and node config as a rule. Check out <a target="_blank" href="https://eslint.org/docs/latest/">ESLint</a> docs for full sets of available rules:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"env"</span>: {
    <span class="hljs-attr">"browser"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"commonjs"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"es2021"</span>: <span class="hljs-literal">true</span>
  },
  <span class="hljs-attr">"extends"</span>: [<span class="hljs-string">"airbnb-base"</span>, <span class="hljs-string">"prettier"</span>, <span class="hljs-string">"plugin:node/recommended"</span>],
  <span class="hljs-attr">"plugins"</span>: [<span class="hljs-string">"prettier"</span>],
  <span class="hljs-attr">"rules"</span>: {
    <span class="hljs-attr">"prettier/prettier"</span>: <span class="hljs-string">"warn"</span>,
    <span class="hljs-attr">"no-console"</span>: <span class="hljs-string">"warn"</span>,
    <span class="hljs-attr">"no-unused-vars"</span>: <span class="hljs-string">"warn"</span>,
    <span class="hljs-attr">"no-shadow"</span>: <span class="hljs-string">"off"</span>,
    <span class="hljs-attr">"no-underscore-dangle"</span>: <span class="hljs-string">"off"</span>
  }
}
</code></pre>
<p><strong>Key rules explained:</strong></p>
<ul>
<li><p><code>prettier/prettier</code> - ESLint surfaces Prettier issues, but only as warnings.</p>
</li>
<li><p><code>no-console</code> - Logs are useful during development, but shouldn’t ship to prod.</p>
</li>
<li><p><code>no-unused-vars</code> - Helps avoid bloated, misleading code.</p>
</li>
<li><p><code>no-underscore-dangle</code> - I often use _id from MongoDB, so this rule gets turned off.</p>
</li>
</ul>
<h2 id="heading-step-3-stylelint-keep-your-stylesheets-clean"><strong>🎨 Step 3: Stylelint — Keep Your Stylesheets Clean</strong></h2>
<p>Stylelint works just like ESLint, but for styles. It ensures your SCSS/CSS is consistent, avoids deprecated syntax and promotes clean layout logic.</p>
<h3 id="heading-install-1"><strong>🔧 Install</strong></h3>
<pre><code class="lang-bash">npm install --save-dev sass stylelint stylelint-config-standard-scss stylelint-config-prettier
</code></pre>
<p>Create <code>.stylelintrc.json</code> in the project root. Here's a sample configuration:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"extends"</span>: [<span class="hljs-string">"stylelint-config-standard-scss"</span>, <span class="hljs-string">"stylelint-config-prettier"</span>],
  <span class="hljs-attr">"rules"</span>: {
    <span class="hljs-attr">"color-function-notation"</span>: <span class="hljs-string">"legacy"</span>,
    <span class="hljs-attr">"selector-class-pattern"</span>: <span class="hljs-literal">null</span>
  }
}
</code></pre>
<p><strong>What this does:</strong></p>
<ul>
<li><p><code>standard-scss</code> gives us a solid base config for SCSS.</p>
</li>
<li><p><code>tylelint-config-prettier</code> disables rules that conflict with Prettier.</p>
</li>
<li><p>We relax strict class naming rules <code>selector-class-pattern</code> to suit different naming conventions (BEM, utility-first, etc.).</p>
</li>
</ul>
<h2 id="heading-step-4-lint-staged-lint-only-what-changed"><strong>⚡ Step 4: lint-staged — Lint Only What Changed</strong></h2>
<p>Without lint-staged, Husky would run ESLint/Prettier on the whole codebase for every commit. That’s slow and unnecessary. lint-staged is a package that can be used to run formatting and linting commands on staged files in a Git repo.</p>
<h3 id="heading-install-2"><strong>🔧 Install</strong></h3>
<pre><code class="lang-bash">npm install --save-dev lint-staged
</code></pre>
<p>lint-staged will run specific commands on staged files before committing them. Add the following configuration to your <code>package.json</code>. The below configures lint-staged to run Prettier, Stylelint and ESLint. Only add the Stylelint script for front-end projects:</p>
<pre><code class="lang-json"><span class="hljs-string">"lint-staged"</span>: {
  <span class="hljs-attr">"*.{js,ts,jsx,tsx}"</span>: [<span class="hljs-string">"prettier --write"</span>, <span class="hljs-string">"eslint"</span>],
  <span class="hljs-attr">"*.{css,scss,json,md}"</span>: [<span class="hljs-string">"prettier --write"</span>, <span class="hljs-string">"stylelint --allow-empty-input"</span>]
}
</code></pre>
<blockquote>
<p>This setup makes sure only staged files are linted/formatted, keeping pre-commit hooks fast and focused.</p>
</blockquote>
<h2 id="heading-step-5-husky-git-gatekeeper"><strong>🔐 Step 5: Husky — Git Gatekeeper</strong></h2>
<p><a target="_blank" href="https://typicode.github.io/husky/">Husky</a> allows us to run scripts at key Git lifecycle moments. Here, we’ll use it to run lint-staged before any commit. Thus code only ever gets into the repo after it has been consistently formatted and verified to be free of linting errors. This is a particularly big advantage in a team setting.</p>
<h3 id="heading-install-and-set-up"><strong>🔧 Install and set up</strong></h3>
<pre><code class="lang-bash">npm install --save-dev husky
npx husky init
</code></pre>
<p>The <code>init</code> command simplifies setting up Husky in a project. It creates a <code>pre-commit</code> script in <code>.husky/</code> and updates the <code>prepare</code> script in <code>package.json</code>. Modifications can be made later to suit your workflow.</p>
<p>Update the <code>.husky/pre-commit</code> file:</p>
<pre><code class="lang-bash">npx lint-staged
</code></pre>
<blockquote>
<p>This ensures your staged files pass all checks before being committed.</p>
</blockquote>
<p>Create <code>.husky/install.mjs</code> file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Skip Husky install in production and CI</span>
<span class="hljs-keyword">if</span> (process.env.NODE_ENV === <span class="hljs-string">'production'</span> || process.env.CI === <span class="hljs-string">'true'</span>) {
    process.exit(<span class="hljs-number">0</span>);
}

<span class="hljs-keyword">const</span> husky = (<span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'husky'</span>)).default;
<span class="hljs-built_in">console</span>.log(husky());
</code></pre>
<blockquote>
<p>This ensures husky is only installed in dev environment.</p>
</blockquote>
<h2 id="heading-step-6-helpful-npm-scripts">🛠️ Step 6: Helpful NPM Scripts</h2>
<p>Add these to your <code>package.json</code> for manual use or CI pipelines, adjust according to your project setup:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
  <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"eslint './**/*.@(js|jsx|ts|tsx)'"</span>,
  <span class="hljs-attr">"lint:fix"</span>: <span class="hljs-string">"eslint './**/*.@(js|jsx|ts|tsx)' --fix"</span>,
  <span class="hljs-attr">"prettier:check"</span>: <span class="hljs-string">"prettier --check './**/*.{js,ts,json,md,css,scss}'"</span>,
  <span class="hljs-attr">"prettier:write"</span>: <span class="hljs-string">"prettier --write './**/*.{js,ts,json,md,css,scss}'"</span>,
  <span class="hljs-attr">"style:check"</span>: <span class="hljs-string">"stylelint '**/*.{css,scss}' --allow-empty-input"</span>,
  <span class="hljs-attr">"prepare"</span>: <span class="hljs-string">"node .husky/install.mjs"</span>
}
</code></pre>
<ul>
<li><p><code>lint</code> - This script will run ESLint on all JavaScript, TypeScript, JSX and TSX files in your project.</p>
</li>
<li><p><code>lint:fix</code> - This will do the same as the lint script but will also attempt to automatically fix any issues that ESLint can handle.</p>
</li>
<li><p><code>prettier:write</code> - This script will format all JS, JSX, TS, TSX, JSON, CSS, SCSS and Markdown files using Prettier. This ensures consistency in both code and documentation.</p>
</li>
<li><p><code>prettier:check</code> - This will check if all the specified files adhere to the formatting rules specified by Prettier. This is especially useful as a pre-commit or CI/CD step to ensure that no non-formatted files get committed or deployed.</p>
</li>
<li><p><code>style:check</code> - This will check if all the specified CSS, SCSS files adhere to the formatting rules specified by Stylelint. Only add for front-end projects.</p>
</li>
<li><p><code>prepare</code> - This will only run Husky in a non-production environment.</p>
</li>
</ul>
<p>You can adjust the patterns like <code>./**/*.@(js|jsx|ts|tsx|json|css|scss|md)</code> to fit the specific needs of your project, and which directories or file types you want to include/exclude.</p>
<h3 id="heading-using-in-your-workflow">⚙️ Using in your Workflow</h3>
<p>Whenever you're about to commit, you can run:</p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"Set up linting and formatting"</span>
<span class="hljs-comment"># lint-staged script will run every time you commit</span>
</code></pre>
<blockquote>
<p>This will ensure that your code is both linted and formatted. If anything had not been set up correctly, you would likely get errors during commit.</p>
</blockquote>
<h2 id="heading-bonus-vs-code-setup-for-auto-formatting">🧩 Bonus: VS Code Setup for Auto-Formatting</h2>
<p>To make all of this seamless, configure VS Code to format on save and use the following extensions:</p>
<ul>
<li><p><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint">ESLint</a></p>
</li>
<li><p><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint">Stylelint</a></p>
</li>
<li><p><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier - Code formatter</a></p>
</li>
</ul>
<p>Put the following in <code>.vscode/settings.json</code> in the project (you can also put these settings in your User Preferences file):</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"[javascript]"</span>: {
        <span class="hljs-attr">"editor.defaultFormatter"</span>: <span class="hljs-string">"esbenp.prettier-vscode"</span>
    },
    <span class="hljs-attr">"typescript.tsdk"</span>: <span class="hljs-string">"node_modules/typescript/lib"</span>,
    <span class="hljs-attr">"[typescript]"</span>: {
        <span class="hljs-attr">"editor.defaultFormatter"</span>: <span class="hljs-string">"esbenp.prettier-vscode"</span>
    },
    <span class="hljs-attr">"[javascriptreact]"</span>: {
        <span class="hljs-attr">"editor.defaultFormatter"</span>: <span class="hljs-string">"esbenp.prettier-vscode"</span>
    },
    <span class="hljs-attr">"[typescriptreact]"</span>: {
        <span class="hljs-attr">"editor.defaultFormatter"</span>: <span class="hljs-string">"esbenp.prettier-vscode"</span>
    },
    <span class="hljs-attr">"[scss]"</span>: {
        <span class="hljs-attr">"editor.defaultFormatter"</span>: <span class="hljs-string">"esbenp.prettier-vscode"</span>
    },
    <span class="hljs-attr">"scss.lint.emptyRules"</span>: <span class="hljs-string">"ignore"</span>,
    <span class="hljs-attr">"stylelint.validate"</span>: [<span class="hljs-string">"css"</span>, <span class="hljs-string">"scss"</span>],
    <span class="hljs-attr">"editor.formatOnSave"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<blockquote>
<p>The above set up, will allow the extensions to lint and format on Save.</p>
</blockquote>
<h2 id="heading-final-thoughts">✅ Final Thoughts</h2>
<p>This setup may feel like a lot up front, but once it’s in place, it quietly handles all the repetitive formatting, catches subtle issues and keeps your Git history clean.</p>
<p>It’s not about obsessing over code style. It’s about freeing up mental bandwidth so you can focus on solving real problems, not arguing over trailing commas or indentation.</p>
<h2 id="heading-what-do-you-use"><strong>🤔 What Do You Use?</strong></h2>
<p>I’d love to hear how others approach this:</p>
<ul>
<li><p>What’s your go-to setup?</p>
</li>
<li><p>Any must-disable rules in your projects?</p>
</li>
<li><p>Do you skip this entirely for small experiments?</p>
</li>
</ul>
<p>Let me know your take. Always curious to hear how other devs keep their code clean.</p>
]]></content:encoded></item></channel></rss>