<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ouma Asoyoh</title>
    <description>The latest articles on DEV Community by Ouma Asoyoh (@victorouma7).</description>
    <link>https://dev.to/victorouma7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3973991%2Fd3aa9fa2-4548-49db-9c23-b4f5662eb559.jpg</url>
      <title>DEV Community: Ouma Asoyoh</title>
      <link>https://dev.to/victorouma7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/victorouma7"/>
    <language>en</language>
    <item>
      <title>Building a web scraper in go: Understanding the go standard libraries.</title>
      <dc:creator>Ouma Asoyoh</dc:creator>
      <pubDate>Mon, 28 Sep 2026 20:49:25 +0000</pubDate>
      <link>https://dev.to/victorouma7/building-a-web-scraper-in-go-understanding-the-go-standard-libraries-4p21</link>
      <guid>https://dev.to/victorouma7/building-a-web-scraper-in-go-understanding-the-go-standard-libraries-4p21</guid>
      <description>&lt;p&gt;Web scraping is one of those projects that looks simple at first:&lt;/p&gt;

&lt;p&gt;«Send a request → get a webpage → extract some information.»&lt;/p&gt;

&lt;p&gt;But once you start implementing it, you quickly discover that there are several pieces involved.&lt;/p&gt;

&lt;p&gt;Recently, I started learning how to build a web scraper in Go. What interested me most was that I could build most of the foundation using Go's standard library.&lt;/p&gt;

&lt;p&gt;In this article, I'll break down the main packages involved, what each one does, and how they fit together to create a simple web scraper.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;The basic architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before looking at individual packages, it helps to understand the flow.&lt;/p&gt;

&lt;p&gt;A basic scraper can be thought of as:&lt;/p&gt;

&lt;p&gt;URL&lt;br&gt;
 │&lt;br&gt;
 ▼&lt;br&gt;
HTTP Request&lt;br&gt;
 │&lt;br&gt;
 ▼&lt;br&gt;
HTTP Response&lt;br&gt;
 │&lt;br&gt;
 ▼&lt;br&gt;
Response Body&lt;br&gt;
 │&lt;br&gt;
 ▼&lt;br&gt;
HTML/Text Processing&lt;br&gt;
 │&lt;br&gt;
 ▼&lt;br&gt;
Extract Information&lt;br&gt;
 │&lt;br&gt;
 ▼&lt;br&gt;
Validate / Store Data&lt;/p&gt;

&lt;p&gt;In Go, different standard library packages can handle different parts of this process.&lt;/p&gt;

&lt;p&gt;Some of the important ones are:&lt;/p&gt;

&lt;p&gt;net/http&lt;br&gt;
io&lt;br&gt;
net/url&lt;br&gt;
strings&lt;br&gt;
net/http/httptest&lt;/p&gt;

&lt;p&gt;Let's look at each one.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;"net/http" — communicating with websites&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first thing a scraper needs to do is communicate with a web server.&lt;/p&gt;

&lt;p&gt;This is where the "net/http" package comes in.&lt;/p&gt;

&lt;p&gt;A simple HTTP GET request looks like this:&lt;/p&gt;

&lt;p&gt;package main&lt;/p&gt;

&lt;p&gt;import (&lt;br&gt;
    "fmt"&lt;br&gt;
    "net/http"&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;func main() {&lt;br&gt;
    resp, err := http.Get("&lt;a href="https://example.com%22" rel="noopener noreferrer"&gt;https://example.com"&lt;/a&gt;)&lt;br&gt;
    if err != nil {&lt;br&gt;
        fmt.Println(err)&lt;br&gt;
        return&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defer resp.Body.Close()

fmt.Println("Status:", resp.Status)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The important part is:&lt;/p&gt;

&lt;p&gt;resp, err := http.Get("&lt;a href="https://example.com%22" rel="noopener noreferrer"&gt;https://example.com"&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This sends an HTTP GET request to the server.&lt;/p&gt;

&lt;p&gt;The server responds with an "http.Response".&lt;/p&gt;

&lt;p&gt;The response contains information such as:&lt;/p&gt;

&lt;p&gt;resp.Status&lt;br&gt;
resp.StatusCode&lt;br&gt;
resp.Header&lt;br&gt;
resp.Body&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;fmt.Println(resp.StatusCode)&lt;/p&gt;

&lt;p&gt;might produce:&lt;/p&gt;

&lt;p&gt;200&lt;/p&gt;

&lt;p&gt;A "200" response generally means the request was successful.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Why "defer resp.Body.Close()" matters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You'll often see this immediately after making an HTTP request:&lt;/p&gt;

&lt;p&gt;defer resp.Body.Close()&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because the response body is a resource that needs to be closed when we're finished with it.&lt;/p&gt;

&lt;p&gt;Think of it like opening a file.&lt;/p&gt;

&lt;p&gt;You open it:&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
  ↓&lt;br&gt;
Read&lt;br&gt;
  ↓&lt;br&gt;
Close&lt;/p&gt;

&lt;p&gt;HTTP response bodies follow a similar pattern.&lt;/p&gt;

&lt;p&gt;So this:&lt;/p&gt;

&lt;p&gt;resp, err := http.Get(url)&lt;/p&gt;

&lt;p&gt;if err != nil {&lt;br&gt;
    return&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;defer resp.Body.Close()&lt;/p&gt;

&lt;p&gt;means:&lt;/p&gt;

&lt;p&gt;«Once this function finishes, close the response body.»&lt;/p&gt;

&lt;p&gt;This is important for avoiding resource leaks.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;"io" — reading the response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Getting the response isn't enough.&lt;/p&gt;

&lt;p&gt;The actual webpage content is inside:&lt;/p&gt;

&lt;p&gt;resp.Body&lt;/p&gt;

&lt;p&gt;But "resp.Body" is an "io.ReadCloser".&lt;/p&gt;

&lt;p&gt;We need to read it.&lt;/p&gt;

&lt;p&gt;One simple approach is:&lt;/p&gt;

&lt;p&gt;body, err := io.ReadAll(resp.Body)&lt;/p&gt;

&lt;p&gt;if err != nil {&lt;br&gt;
    fmt.Println(err)&lt;br&gt;
    return&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;fmt.Println(string(body))&lt;/p&gt;

&lt;p&gt;The "io.ReadAll()" function reads everything from the reader.&lt;/p&gt;

&lt;p&gt;The result is a byte slice:&lt;/p&gt;

&lt;p&gt;[]byte&lt;/p&gt;

&lt;p&gt;That's why we convert it to a string:&lt;/p&gt;

&lt;p&gt;string(body)&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;fmt.Println(string(body))&lt;/p&gt;

&lt;p&gt;could produce HTML like:&lt;/p&gt;

&lt;p&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
    &lt;/p&gt;
Example Domain
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
    &lt;h1&gt;Example Domain&lt;/h1&gt;
&lt;br&gt;
&lt;br&gt;


&lt;p&gt;Now we have the webpage content in memory.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Understanding "io.Reader"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the concepts I found particularly useful while learning this was the idea of an "io.Reader".&lt;/p&gt;

&lt;p&gt;Go uses interfaces extensively.&lt;/p&gt;

&lt;p&gt;Many things can be represented as a reader:&lt;/p&gt;

&lt;p&gt;File&lt;br&gt;
 │&lt;br&gt;
 ├── Reader&lt;br&gt;
 │&lt;br&gt;
HTTP Response Body&lt;br&gt;
 │&lt;br&gt;
 ├── Reader&lt;br&gt;
 │&lt;br&gt;
Memory buffer&lt;br&gt;
 │&lt;br&gt;
 └── Reader&lt;/p&gt;

&lt;p&gt;This means functions such as:&lt;/p&gt;

&lt;p&gt;io.ReadAll()&lt;/p&gt;

&lt;p&gt;can work with many different sources.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;data, err := io.ReadAll(resp.Body)&lt;/p&gt;

&lt;p&gt;The function doesn't need to know that the data came from a website.&lt;/p&gt;

&lt;p&gt;It only needs something that implements the "io.Reader" interface.&lt;/p&gt;

&lt;p&gt;This is one of the ideas that makes Go's standard library very composable.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;"net/url" — working with URLs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When building a real scraper, URLs quickly become more complicated.&lt;/p&gt;

&lt;p&gt;You may need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parse URLs&lt;/li&gt;
&lt;li&gt;inspect their components&lt;/li&gt;
&lt;li&gt;modify query parameters&lt;/li&gt;
&lt;li&gt;construct URLs&lt;/li&gt;
&lt;li&gt;resolve relative links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go provides the "net/url" package for this.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;package main&lt;/p&gt;

&lt;p&gt;import (&lt;br&gt;
    "fmt"&lt;br&gt;
    "net/url"&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;func main() {&lt;br&gt;
    rawURL := "&lt;a href="https://example.com/products?page=2" rel="noopener noreferrer"&gt;https://example.com/products?page=2&lt;/a&gt;"&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;u, err := url.Parse(rawURL)
if err != nil {
    fmt.Println(err)
    return
}

fmt.Println("Scheme:", u.Scheme)
fmt.Println("Host:", u.Host)
fmt.Println("Path:", u.Path)
fmt.Println("Query:", u.RawQuery)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The output would be something like:&lt;/p&gt;

&lt;p&gt;Scheme: https&lt;br&gt;
Host: example.com&lt;br&gt;
Path: /products&lt;br&gt;
Query: page=2&lt;/p&gt;

&lt;p&gt;Instead of treating a URL as just a string, we can work with its individual components.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Query parameters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Suppose our scraper needs to request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://example.com/products?page=2" rel="noopener noreferrer"&gt;https://example.com/products?page=2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of manually constructing the string, we can use "url.Values".&lt;/p&gt;

&lt;p&gt;params := url.Values{}&lt;/p&gt;

&lt;p&gt;params.Set("page", "2")&lt;/p&gt;

&lt;p&gt;u := url.URL{&lt;br&gt;
    Scheme:   "https",&lt;br&gt;
    Host:     "example.com",&lt;br&gt;
    Path:     "/products",&lt;br&gt;
    RawQuery: params.Encode(),&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;fmt.Println(u.String())&lt;/p&gt;

&lt;p&gt;This produces:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://example.com/products?page=2" rel="noopener noreferrer"&gt;https://example.com/products?page=2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This becomes especially useful when building scrapers that need pagination.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;/products?page=1&lt;br&gt;
/products?page=2&lt;br&gt;
/products?page=3&lt;br&gt;
/products?page=4&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;"strings" — processing extracted data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once we've downloaded a webpage, we'll often need to work with text.&lt;/p&gt;

&lt;p&gt;The "strings" package provides many useful functions.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;title := "  Example Product  "&lt;/p&gt;

&lt;p&gt;title = strings.TrimSpace(title)&lt;/p&gt;

&lt;p&gt;fmt.Println(title)&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;Example Product&lt;/p&gt;

&lt;p&gt;We can also search for text:&lt;/p&gt;

&lt;p&gt;if strings.Contains(title, "Product") {&lt;br&gt;
    fmt.Println("Found product")&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Or split strings:&lt;/p&gt;

&lt;p&gt;links := "home,products,contact"&lt;/p&gt;

&lt;p&gt;parts := strings.Split(links, ",")&lt;/p&gt;

&lt;p&gt;for _, link := range parts {&lt;br&gt;
    fmt.Println(link)&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;home&lt;br&gt;
products&lt;br&gt;
contact&lt;/p&gt;

&lt;p&gt;These operations become useful after extracting information from HTML.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;A simple scraper using these packages&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's combine what we've learned.&lt;/p&gt;

&lt;p&gt;Suppose we want to download a webpage and print its HTML.&lt;/p&gt;

&lt;p&gt;package main&lt;/p&gt;

&lt;p&gt;import (&lt;br&gt;
    "fmt"&lt;br&gt;
    "io"&lt;br&gt;
    "net/http"&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;func main() {&lt;br&gt;
    url := "&lt;a href="https://example.com" rel="noopener noreferrer"&gt;https://example.com&lt;/a&gt;"&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resp, err := http.Get(url)
if err != nil {
    fmt.Println("Request failed:", err)
    return
}

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
    fmt.Println("Unexpected status:", resp.Status)
    return
}

body, err := io.ReadAll(resp.Body)
if err != nil {
    fmt.Println("Failed to read response:", err)
    return
}

fmt.Println(string(body))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The flow is now:&lt;/p&gt;

&lt;p&gt;http.Get()&lt;br&gt;
    ↓&lt;br&gt;
http.Response&lt;br&gt;
    ↓&lt;br&gt;
resp.Body&lt;br&gt;
    ↓&lt;br&gt;
io.ReadAll()&lt;br&gt;
    ↓&lt;br&gt;
[]byte&lt;br&gt;
    ↓&lt;br&gt;
string&lt;/p&gt;

&lt;p&gt;That's the foundation of our scraper.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Extracting information from the response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Downloading the HTML is only half the job.&lt;/p&gt;

&lt;p&gt;A scraper is useful because it extracts specific information.&lt;/p&gt;

&lt;p&gt;For example, imagine the response contains:&lt;/p&gt;

&lt;h1&gt;Go Web Scraping&lt;/h1&gt;

&lt;p&gt;Learning Go is interesting.&lt;/p&gt;

&lt;p&gt;We could perform very simple text processing with "strings".&lt;/p&gt;

&lt;p&gt;html := `&lt;/p&gt;

&lt;h1&gt;Go Web Scraping&lt;/h1&gt;

&lt;p&gt;Learning Go is interesting.&lt;/p&gt;

&lt;p&gt;`&lt;/p&gt;

&lt;p&gt;if strings.Contains(html, "Go Web Scraping") {&lt;br&gt;
    fmt.Println("Title found")&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;However, this approach becomes fragile very quickly.&lt;/p&gt;

&lt;p&gt;HTML is structured data, so for serious HTML parsing, a dedicated HTML parser is usually more appropriate.&lt;/p&gt;

&lt;p&gt;For example, Go developers commonly use:&lt;/p&gt;

&lt;p&gt;golang.org/x/net/html&lt;/p&gt;

&lt;p&gt;This is not part of the Go standard library, but it works naturally with the standard library's HTTP and I/O packages.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Parsing HTML with "golang.org/x/net/html"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After downloading a webpage, we can parse the HTML into a document tree.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;HTML Document&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
    &lt;br&gt;
      │&lt;br&gt;
 ┌────┴─────┐&lt;br&gt;
 ▼          ▼&lt;br&gt;
     &lt;br&gt;
             │&lt;br&gt;
        ┌────┴────┐&lt;br&gt;
        ▼         ▼&lt;br&gt;
      &lt;/p&gt;
&lt;h1&gt;       &lt;/h1&gt;


&lt;p&gt;The parser allows us to navigate this structure instead of treating HTML as plain text.&lt;/p&gt;

&lt;p&gt;A simplified example:&lt;/p&gt;

&lt;p&gt;doc, err := html.Parse(resp.Body)&lt;br&gt;
if err != nil {&lt;br&gt;
    fmt.Println(err)&lt;br&gt;
    return&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now "doc" represents the parsed HTML document.&lt;/p&gt;

&lt;p&gt;From there, we can walk through the nodes and look for elements such as:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;br&gt;
&lt;img&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;&lt;a&gt;&lt;br&gt;





&lt;ol&gt;
&lt;li&gt;Finding links&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Links are particularly important when building a crawler.&lt;/p&gt;

&lt;p&gt;Suppose we have:&lt;/p&gt;

&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;a href="/about"&gt;About&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="/products"&gt;Products&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A crawler could find those "href" attributes and then visit them.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;Page A&lt;br&gt;
 │&lt;br&gt;
 ├── /about&lt;br&gt;
 │&lt;br&gt;
 └── /products&lt;br&gt;
       │&lt;br&gt;
       ▼&lt;br&gt;
    Page B&lt;/p&gt;

&lt;p&gt;This is where web scraping starts becoming web crawling.&lt;/p&gt;

&lt;p&gt;A scraper might extract information from one page.&lt;/p&gt;

&lt;p&gt;A crawler can follow links and discover additional pages.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;"net/http/httptest" — testing the scraper&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the most useful packages I encountered was:&lt;/p&gt;

&lt;p&gt;net/http/httptest&lt;/p&gt;

&lt;p&gt;Testing a scraper against a real website is not always a good idea.&lt;/p&gt;

&lt;p&gt;The website could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change its HTML&lt;/li&gt;
&lt;li&gt;become unavailable&lt;/li&gt;
&lt;li&gt;block requests&lt;/li&gt;
&lt;li&gt;return different data&lt;/li&gt;
&lt;li&gt;become slow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, we can create a fake HTTP server.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {&lt;br&gt;
    fmt.Fprintln(w, "&lt;/p&gt;
&lt;h1&gt;Hello&lt;/h1&gt;")&lt;br&gt;
}))

&lt;p&gt;defer server.Close()&lt;/p&gt;

&lt;p&gt;Now we have a local test server.&lt;/p&gt;

&lt;p&gt;We can send our scraper to:&lt;/p&gt;

&lt;p&gt;server.URL&lt;/p&gt;

&lt;p&gt;instead of a real website.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Testing a scraper with a fake server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;func TestScraper(t *testing.T) {&lt;br&gt;
    server := httptest.NewServer(http.HandlerFunc(&lt;br&gt;
        func(w http.ResponseWriter, r *http.Request) {&lt;br&gt;
            fmt.Fprintln(w, "&lt;/p&gt;
&lt;h1&gt;Hello&lt;/h1&gt;")&lt;br&gt;
        },&lt;br&gt;
    ))

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defer server.Close()

resp, err := http.Get(server.URL)
if err != nil {
    t.Fatal(err)
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
    t.Fatal(err)
}

if !strings.Contains(string(body), "Hello") {
    t.Fatal("expected Hello in response")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;Now our test doesn't depend on the internet.&lt;/p&gt;

&lt;p&gt;We control exactly what the server returns.&lt;/p&gt;

&lt;p&gt;That's extremely useful.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Testing different HTTP responses&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can also test how our scraper handles errors.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;server := httptest.NewServer(http.HandlerFunc(&lt;br&gt;
    func(w http.ResponseWriter, r *http.Request) {&lt;br&gt;
        http.Error(w, "Not Found", http.StatusNotFound)&lt;br&gt;
    },&lt;br&gt;
))&lt;/p&gt;

&lt;p&gt;Now our scraper receives:&lt;/p&gt;

&lt;p&gt;404 Not Found&lt;/p&gt;

&lt;p&gt;We can verify that our program handles that situation correctly.&lt;/p&gt;

&lt;p&gt;We can also test:&lt;/p&gt;

&lt;p&gt;200 OK&lt;br&gt;
301 Redirect&lt;br&gt;
403 Forbidden&lt;br&gt;
404 Not Found&lt;br&gt;
500 Internal Server Error&lt;/p&gt;

&lt;p&gt;This is where testing becomes much more valuable than simply checking whether the scraper works once.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Putting the packages together&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, the roles of the packages become clearer.&lt;/p&gt;

&lt;p&gt;Package| Purpose&lt;br&gt;
"net/http"| Send HTTP requests and receive responses&lt;br&gt;
"io"| Read response data&lt;br&gt;
"net/url"| Parse and construct URLs&lt;br&gt;
"strings"| Process textual data&lt;br&gt;
"net/http/httptest"| Test HTTP-based applications&lt;br&gt;
"golang.org/x/net/html"| Parse HTML documents&lt;/p&gt;

&lt;p&gt;They aren't competing packages.&lt;/p&gt;

&lt;p&gt;They form a pipeline.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         URL
          │
          ▼
      net/url
          │
          ▼
      net/http
          │
          ▼
    HTTP Response
          │
          ▼
         io
          │
          ▼
      HTML data
          │
          ▼
    x/net/html
          │
          ▼
    Extract data
          │
          ▼
       strings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;ol&gt;
&lt;li&gt;What I learned beyond the packages&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The biggest lesson for me wasn't actually the individual packages.&lt;/p&gt;

&lt;p&gt;It was understanding how they work together.&lt;/p&gt;

&lt;p&gt;Before this, it was easy to think of web scraping as:&lt;/p&gt;

&lt;p&gt;Download webpage&lt;br&gt;
Extract information&lt;/p&gt;

&lt;p&gt;But a more realistic system looks like:&lt;/p&gt;

&lt;p&gt;URL&lt;br&gt;
 ↓&lt;br&gt;
HTTP request&lt;br&gt;
 ↓&lt;br&gt;
Timeout handling&lt;br&gt;
 ↓&lt;br&gt;
HTTP status validation&lt;br&gt;
 ↓&lt;br&gt;
Response body&lt;br&gt;
 ↓&lt;br&gt;
HTML parsing&lt;br&gt;
 ↓&lt;br&gt;
Data extraction&lt;br&gt;
 ↓&lt;br&gt;
Data validation&lt;br&gt;
 ↓&lt;br&gt;
Storage&lt;br&gt;
 ↓&lt;br&gt;
Tests&lt;/p&gt;

&lt;p&gt;And once you start building a real scraper, additional concerns appear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;redirects&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;robots.txt&lt;/li&gt;
&lt;li&gt;concurrent requests&lt;/li&gt;
&lt;li&gt;duplicate URLs&lt;/li&gt;
&lt;li&gt;malformed HTML&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;li&gt;data persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's where a simple scraper starts turning into a real software engineering project.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;A small mental model&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The way I'm currently thinking about Go web scraping is:&lt;/p&gt;

&lt;p&gt;net/http&lt;br&gt;
    ↓&lt;br&gt;
"Talk to the website"&lt;/p&gt;

&lt;p&gt;io&lt;br&gt;
    ↓&lt;br&gt;
"Read what the website sent"&lt;/p&gt;

&lt;p&gt;net/url&lt;br&gt;
    ↓&lt;br&gt;
"Understand the address"&lt;/p&gt;

&lt;p&gt;x/net/html&lt;br&gt;
    ↓&lt;br&gt;
"Understand the HTML structure"&lt;/p&gt;

&lt;p&gt;strings&lt;br&gt;
    ↓&lt;br&gt;
"Clean and process text"&lt;/p&gt;

&lt;p&gt;httptest&lt;br&gt;
    ↓&lt;br&gt;
"Test everything without depending on the real website"&lt;/p&gt;

&lt;p&gt;Once you understand those responsibilities, the code becomes much easier to reason about.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;What's next?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My next step is to move beyond a basic scraper and explore how to build a more robust crawler in Go.&lt;/p&gt;

&lt;p&gt;Some areas I want to explore are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;HTTP clients and custom timeouts&lt;/li&gt;
&lt;li&gt;HTML tree traversal&lt;/li&gt;
&lt;li&gt;Extracting links and images&lt;/li&gt;
&lt;li&gt;URL normalization&lt;/li&gt;
&lt;li&gt;Concurrent scraping with goroutines&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Retry strategies&lt;/li&gt;
&lt;li&gt;Structured data models&lt;/li&gt;
&lt;li&gt;Testing edge cases&lt;/li&gt;
&lt;li&gt;Persisting scraped data&lt;/li&gt;
&lt;li&gt;Handling failed requests&lt;/li&gt;
&lt;li&gt;Building a complete CLI scraper&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting part is that the deeper I go, the more I realize that web scraping is not really about scraping.&lt;/p&gt;

&lt;p&gt;It's about understanding how different parts of a software system communicate.&lt;/p&gt;

&lt;p&gt;And Go's standard library provides many of the building blocks needed to understand and build those systems.&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>webscraping</category>
    </item>
    <item>
      <title># What I Learned from Building with GIS Data and the Copernicus API at the KijaniSpace Hackathon</title>
      <dc:creator>Ouma Asoyoh</dc:creator>
      <pubDate>Wed, 29 Jul 2026 06:51:50 +0000</pubDate>
      <link>https://dev.to/victorouma7/-what-i-learned-from-building-with-gis-data-and-the-copernicus-api-at-the-kijanispace-hackathon-5c05</link>
      <guid>https://dev.to/victorouma7/-what-i-learned-from-building-with-gis-data-and-the-copernicus-api-at-the-kijanispace-hackathon-5c05</guid>
      <description>&lt;p&gt;As software developers, we often spend most of our time building APIs, databases, authentication systems, and web applications. That's certainly been my focus recently, especially working with Go, JWT authentication, and backend services.&lt;/p&gt;

&lt;p&gt;Last week, however, I had the opportunity to participate in the &lt;strong&gt;KijaniSpace Hackathon&lt;/strong&gt;, held at &lt;strong&gt;Zone01 Kisumu&lt;/strong&gt;, and it introduced me to an entirely different side of software development.&lt;/p&gt;

&lt;p&gt;Our challenge was to build solutions using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Geographic Information Systems (GIS)&lt;/li&gt;
&lt;li&gt;The Copernicus API&lt;/li&gt;
&lt;li&gt;IoT devices where applicable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was an opportunity to see how software can interact with our physical world.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is GIS?
&lt;/h2&gt;

&lt;p&gt;GIS (Geographic Information Systems) is a technology used to collect, analyze, visualize, and manage data that has a geographic location.&lt;/p&gt;

&lt;p&gt;Imagine not just storing information like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Temperature&lt;/li&gt;
&lt;li&gt;Population&lt;/li&gt;
&lt;li&gt;Vegetation&lt;/li&gt;
&lt;li&gt;Buildings&lt;/li&gt;
&lt;li&gt;Roads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...but also knowing exactly where that information exists on Earth.&lt;/p&gt;

&lt;p&gt;That location data allows developers to build intelligent systems capable of answering questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which farms are experiencing drought?&lt;/li&gt;
&lt;li&gt;Which roads are likely to flood?&lt;/li&gt;
&lt;li&gt;Which areas are losing forest cover?&lt;/li&gt;
&lt;li&gt;Where should new infrastructure be built?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GIS transforms ordinary data into meaningful geographic insights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovering the Copernicus Program
&lt;/h2&gt;

&lt;p&gt;Before this hackathon, I had heard very little about Copernicus.&lt;/p&gt;

&lt;p&gt;Copernicus is the European Union's Earth Observation Programme. It provides free satellite imagery and environmental data collected by the Sentinel satellite missions.&lt;/p&gt;

&lt;p&gt;Through its APIs, developers can access information about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Land cover&lt;/li&gt;
&lt;li&gt;Vegetation health&lt;/li&gt;
&lt;li&gt;Weather patterns&lt;/li&gt;
&lt;li&gt;Water bodies&lt;/li&gt;
&lt;li&gt;Air quality&lt;/li&gt;
&lt;li&gt;Climate changes&lt;/li&gt;
&lt;li&gt;Disaster monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What amazed me most is that much of this data is openly available for developers to build impactful applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where IoT Fits In
&lt;/h2&gt;

&lt;p&gt;Some teams also explored Internet of Things (IoT) solutions.&lt;/p&gt;

&lt;p&gt;IoT devices can collect real-world information through sensors measuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Soil moisture&lt;/li&gt;
&lt;li&gt;Temperature&lt;/li&gt;
&lt;li&gt;Humidity&lt;/li&gt;
&lt;li&gt;Water levels&lt;/li&gt;
&lt;li&gt;Air quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When combined with GIS and satellite imagery, these sensors create even richer datasets that enable better decision-making.&lt;/p&gt;

&lt;p&gt;Imagine combining satellite observations with live sensor readings from farms or rivers. That's incredibly powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Biggest Takeaways
&lt;/h2&gt;

&lt;p&gt;Although my background is primarily in backend development, this hackathon taught me several valuable lessons.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Software is Everywhere
&lt;/h3&gt;

&lt;p&gt;Software doesn't stop at websites and mobile apps.&lt;/p&gt;

&lt;p&gt;It powers agriculture, healthcare, climate science, environmental conservation, logistics, transportation, and countless other industries.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Learning New Technologies Quickly Is a Superpower
&lt;/h3&gt;

&lt;p&gt;Hackathons force you to become comfortable being uncomfortable.&lt;/p&gt;

&lt;p&gt;You rarely have the luxury of mastering every technology beforehand. Instead, you learn by building.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Collaboration Matters
&lt;/h3&gt;

&lt;p&gt;Great products aren't built by developers alone.&lt;/p&gt;

&lt;p&gt;Working with teammates who have different skills and perspectives often produces better solutions than working in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Open Data Creates Opportunities
&lt;/h3&gt;

&lt;p&gt;One of the most exciting discoveries was how much high-quality public data exists today.&lt;/p&gt;

&lt;p&gt;Developers can use satellite imagery and environmental datasets to create products that solve genuine problems affecting communities around the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I left the KijaniSpace Hackathon with more than just experience.&lt;/p&gt;

&lt;p&gt;I gained a new appreciation for geospatial technologies, Earth observation, and the growing role software developers can play in solving environmental and societal challenges.&lt;/p&gt;

&lt;p&gt;As I continue building backend systems in Go, I'll also be exploring how geospatial data, APIs, and IoT can be integrated into future projects.&lt;/p&gt;

&lt;p&gt;Sometimes the best way to grow as a developer isn't by learning another programming language.&lt;/p&gt;

&lt;p&gt;It's by discovering an entirely new domain where software can make a difference.&lt;/p&gt;

&lt;p&gt;Thank you to KijaniSpace, Zone01 Kisumu, the organizers, mentors, and every participant who made the event an incredible learning experience.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>iot</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title># Understanding Backtracking Through a Tetris Optimizer in Go</title>
      <dc:creator>Ouma Asoyoh</dc:creator>
      <pubDate>Tue, 14 Jul 2026 18:22:47 +0000</pubDate>
      <link>https://dev.to/victorouma7/-understanding-backtracking-through-a-tetris-optimizer-in-go-3em2</link>
      <guid>https://dev.to/victorouma7/-understanding-backtracking-through-a-tetris-optimizer-in-go-3em2</guid>
      <description>&lt;p&gt;When I first heard the term &lt;strong&gt;backtracking&lt;/strong&gt;, it sounded like a complicated algorithm reserved for computer scientists. After spending the last couple of weeks learning it and implementing it in a &lt;strong&gt;Tetris Optimizer&lt;/strong&gt; project, I realized something surprising:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Backtracking is simply the art of making a decision, checking whether it works, and if it doesn't, undoing it and trying something else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article explains backtracking using a practical project instead of abstract examples.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;Imagine you have several Tetris pieces (tetrominoes), and your goal is to fit all of them into the &lt;strong&gt;smallest possible square&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A A
A A

B B B
  B

C C C C

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

&lt;/div&gt;



&lt;p&gt;The challenge is to arrange every piece so that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No pieces overlap.&lt;/li&gt;
&lt;li&gt;No piece extends outside the board.&lt;/li&gt;
&lt;li&gt;Every piece is used exactly once.&lt;/li&gt;
&lt;li&gt;The board is as small as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is much harder than it looks.&lt;/p&gt;




&lt;h1&gt;
  
  
  My First Thought
&lt;/h1&gt;

&lt;p&gt;Initially, I thought I could simply place one piece after another.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Place A
Place B
Place C
Place D
Done!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, programming isn't always that kind.&lt;/p&gt;

&lt;p&gt;Sometimes the first position you choose for piece &lt;strong&gt;A&lt;/strong&gt; makes it impossible to place &lt;strong&gt;D&lt;/strong&gt; later.&lt;/p&gt;

&lt;p&gt;The mistake wasn't with &lt;strong&gt;D&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The mistake happened much earlier.&lt;/p&gt;




&lt;h1&gt;
  
  
  Enter Backtracking
&lt;/h1&gt;

&lt;p&gt;Backtracking works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Place a piece.&lt;/li&gt;
&lt;li&gt;Try placing the next one.&lt;/li&gt;
&lt;li&gt;If you get stuck...&lt;/li&gt;
&lt;li&gt;Remove the last piece.&lt;/li&gt;
&lt;li&gt;Try a different position.&lt;/li&gt;
&lt;li&gt;Repeat until every piece fits.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's essentially saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If this path doesn't work, let's go back and explore another one."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Visualizing the Search
&lt;/h1&gt;

&lt;p&gt;Suppose we have four tetrominoes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start

 ├── Put A at (0,0)
 │      ├── Put B
 │      │      ├── Put C
 │      │      │      ├── D fits ✅
 │      │      │      └── D fails ❌
 │      │      └── Try another position
 │      └── Move A elsewhere
 └── Try another position for A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every branch represents another possibility.&lt;/p&gt;

&lt;p&gt;Backtracking explores these branches until it finds one that works.&lt;/p&gt;




&lt;h1&gt;
  
  
  How It Looks in Go
&lt;/h1&gt;

&lt;p&gt;The heart of the algorithm is surprisingly small.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pieces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;every&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt; &lt;span class="n"&gt;fits&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="n"&gt;place&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything revolves around four actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if the piece fits.&lt;/li&gt;
&lt;li&gt;Place it.&lt;/li&gt;
&lt;li&gt;Continue recursively.&lt;/li&gt;
&lt;li&gt;Remove it if necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That final step—removing the piece—is what gives backtracking its name.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Remove the Piece?
&lt;/h1&gt;

&lt;p&gt;Imagine solving a maze.&lt;/p&gt;

&lt;p&gt;You choose the left path.&lt;/p&gt;

&lt;p&gt;After walking for a while, you reach a dead end.&lt;/p&gt;

&lt;p&gt;Do you stay there forever?&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;You walk back to the last intersection and try another direction.&lt;/p&gt;

&lt;p&gt;Your algorithm does exactly the same thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Place piece

↓

Dead end

↓

Remove piece

↓

Try another location
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Biggest Lesson I Learned
&lt;/h1&gt;

&lt;p&gt;The code wasn't the difficult part.&lt;/p&gt;

&lt;p&gt;The difficult part was learning &lt;strong&gt;how recursive calls think&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once I stopped imagining recursion as "functions calling functions" and started imagining it as &lt;strong&gt;exploring a decision tree&lt;/strong&gt;, everything became much clearer.&lt;/p&gt;

&lt;p&gt;Drawing recursion trees on paper helped me understand why the algorithm behaved the way it did.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Isn't Brute Force
&lt;/h1&gt;

&lt;p&gt;Many people assume backtracking is just brute force.&lt;/p&gt;

&lt;p&gt;Not quite.&lt;/p&gt;

&lt;p&gt;Brute force explores &lt;strong&gt;every&lt;/strong&gt; possibility.&lt;/p&gt;

&lt;p&gt;Backtracking stops exploring a path the moment it knows that path cannot succeed.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can piece C fit?

No.

Don't continue.

Go back immediately.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That early rejection saves an enormous amount of work.&lt;/p&gt;




&lt;h1&gt;
  
  
  Making the Solver Faster
&lt;/h1&gt;

&lt;p&gt;My Tetris Optimizer taught me that backtracking alone isn't enough.&lt;/p&gt;

&lt;p&gt;Several improvements can dramatically reduce runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with the smallest possible board.&lt;/li&gt;
&lt;li&gt;Place the most restrictive pieces first.&lt;/li&gt;
&lt;li&gt;Skip positions where a piece obviously cannot fit.&lt;/li&gt;
&lt;li&gt;Stop searching as soon as a valid arrangement is found.&lt;/li&gt;
&lt;li&gt;Avoid repeating board states you've already explored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small optimizations can turn a solver that runs for minutes into one that finishes in seconds.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I Took Away
&lt;/h1&gt;

&lt;p&gt;Learning backtracking changed how I think about programming.&lt;/p&gt;

&lt;p&gt;It taught me that solving complex problems doesn't always require knowing the answer immediately.&lt;/p&gt;

&lt;p&gt;Instead, it requires having the confidence to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make a decision.&lt;/li&gt;
&lt;li&gt;Test it.&lt;/li&gt;
&lt;li&gt;Admit when it doesn't work.&lt;/li&gt;
&lt;li&gt;Undo it.&lt;/li&gt;
&lt;li&gt;Try again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That lesson applies far beyond algorithms.&lt;/p&gt;

&lt;p&gt;Sometimes the fastest path to a solution begins with the willingness to step back.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If you're currently learning recursion or backtracking, don't get discouraged if it feels confusing at first.&lt;/p&gt;

&lt;p&gt;Almost everyone struggles with it initially because the challenge isn't writing the code—it's changing the way you think about problem-solving.&lt;/p&gt;

&lt;p&gt;Once that mental shift happens, backtracking becomes one of the most elegant techniques you'll ever use.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>beginners</category>
      <category>computerscience</category>
      <category>go</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Ouma Asoyoh</dc:creator>
      <pubDate>Sat, 04 Jul 2026 21:25:09 +0000</pubDate>
      <link>https://dev.to/victorouma7/-4neo</link>
      <guid>https://dev.to/victorouma7/-4neo</guid>
      <description></description>
    </item>
    <item>
      <title>Choosing the Right Backend Framework: Django vs. Gin vs. Ruby on Rails.</title>
      <dc:creator>Ouma Asoyoh</dc:creator>
      <pubDate>Sat, 04 Jul 2026 21:23:58 +0000</pubDate>
      <link>https://dev.to/victorouma7/choosing-the-right-backend-framework-django-vs-gin-vs-ruby-on-rails-gj3</link>
      <guid>https://dev.to/victorouma7/choosing-the-right-backend-framework-django-vs-gin-vs-ruby-on-rails-gj3</guid>
      <description>&lt;p&gt;Every application we use today—from banking apps to social media platforms—has something working behind the scenes. That hidden engine is called the backend.&lt;br&gt;
The backend is responsible for processing requests, storing data, handling authentication, enforcing business rules, and ensuring everything works as expected when users interact with an application.&lt;br&gt;
One of the first decisions backend developers make is choosing a framework. A framework provides the tools, structure, and best practices needed to build applications faster and more securely.&lt;br&gt;
Today, let's look at three popular backend frameworks: Django, Gin, and Ruby on Rails.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Django (Python)
Django is one of the most mature and feature-rich backend frameworks available. Built using Python, it follows the philosophy of "batteries included."
This means many features developers need are already built into the framework, including:
User authentication
Admin dashboard
Database ORM
Security protections
URL routing
Form validation
Because so much comes ready to use, developers can spend more time solving business problems instead of rebuilding common features.
Best for:
Content management systems
E-learning platforms
Business applications
APIs
Startups building products quickly
Advantages:
Fast development
Excellent security features
Large community
Extensive documentation
Scales well for many applications
Trade-offs:
The framework includes many components, so it can feel heavier than minimalist frameworks.&lt;/li&gt;
&lt;li&gt;Gin (Go)
Gin is a lightweight web framework built for the Go programming language.
Unlike Django, Gin keeps things minimal. It gives developers speed and flexibility while letting them choose many of the additional tools they want to use.
One reason many developers enjoy Gin is its impressive performance. Since Go is a compiled language designed for concurrency, Gin can efficiently handle many requests simultaneously while using relatively few system resources.
Best for:
REST APIs
Microservices
High-performance systems
Real-time services
Cloud-native applications
Advantages:
Extremely fast
Lightweight
Excellent concurrency support
Easy to deploy
Great for scalable services
Trade-offs:
Developers often need to choose and integrate additional libraries themselves, which can mean more setup than frameworks like Django.&lt;/li&gt;
&lt;li&gt;Ruby on Rails (Ruby)
Ruby on Rails has influenced modern web development more than many people realize.
It introduced ideas like Convention over Configuration, allowing developers to spend less time configuring projects and more time building features.
Rails emphasizes developer productivity. Many startups have used it to build products quickly because it helps developers move from idea to working application in a short time.
Best for:
Startup MVPs
SaaS applications
E-commerce platforms
Social platforms
Business applications
Advantages:
Rapid development
Clean project structure
Large ecosystem
Mature tooling
Excellent developer experience
Trade-offs:
Rails may not match Go-based frameworks in raw performance, though it is more than capable for many production applications.
Which One Should You Learn?
There isn't a single "best" framework. The right choice depends on the kind of problems you're solving.
Choose Django if:
You enjoy Python.
You want lots of built-in functionality.
You want to build complete applications quickly.
Choose Gin if:
You enjoy Go.
Performance matters.
You're interested in cloud-native development and microservices.
Choose Ruby on Rails if:
You value developer productivity.
You want to build products rapidly.
You appreciate clear conventions and an opinionated framework.
Final Thoughts
A framework is just a tool. What matters most is understanding backend fundamentals—HTTP, databases, authentication, APIs, security, caching, and application architecture. Once you understand these concepts, learning a new framework becomes much easier.
Rather than chasing whichever framework is trending today, invest in mastering the principles behind backend development. Frameworks evolve, but solid engineering fundamentals remain valuable throughout your career.
As I continue learning backend development, one thing has become increasingly clear: every framework has its strengths. The key isn't finding the "perfect" framework—it's choosing the one that best fits the problem you're trying to solve.Every application we use today—from banking apps to social media platforms—has something working behind the scenes. That hidden engine is called the backend.
The backend is responsible for processing requests, storing data, handling authentication, enforcing business rules, and ensuring everything works as expected when users interact with an application.
One of the first decisions backend developers make is choosing a framework. A framework provides the tools, structure, and best practices needed to build applications faster and more securely.
Today, let's look at three popular backend frameworks: Django, Gin, and Ruby on Rails.&lt;/li&gt;
&lt;li&gt;Django (Python)
Django is one of the most mature and feature-rich backend frameworks available. Built using Python, it follows the philosophy of "batteries included."
This means many features developers need are already built into the framework, including:
User authentication
Admin dashboard
Database ORM
Security protections
URL routing
Form validation
Because so much comes ready to use, developers can spend more time solving business problems instead of rebuilding common features.
Best for:
Content management systems
E-learning platforms
Business applications
APIs
Startups building products quickly
Advantages:
Fast development
Excellent security features
Large community
Extensive documentation
Scales well for many applications
Trade-offs:
The framework includes many components, so it can feel heavier than minimalist frameworks.&lt;/li&gt;
&lt;li&gt;Gin (Go)
Gin is a lightweight web framework built for the Go programming language.
Unlike Django, Gin keeps things minimal. It gives developers speed and flexibility while letting them choose many of the additional tools they want to use.
One reason many developers enjoy Gin is its impressive performance. Since Go is a compiled language designed for concurrency, Gin can efficiently handle many requests simultaneously while using relatively few system resources.
Best for:
REST APIs
Microservices
High-performance systems
Real-time services
Cloud-native applications
Advantages:
Extremely fast
Lightweight
Excellent concurrency support
Easy to deploy
Great for scalable services
Trade-offs:
Developers often need to choose and integrate additional libraries themselves, which can mean more setup than frameworks like Django.&lt;/li&gt;
&lt;li&gt;Ruby on Rails (Ruby)
Ruby on Rails has influenced modern web development more than many people realize.
It introduced ideas like Convention over Configuration, allowing developers to spend less time configuring projects and more time building features.
Rails emphasizes developer productivity. Many startups have used it to build products quickly because it helps developers move from idea to working application in a short time.
Best for:
Startup MVPs
SaaS applications
E-commerce platforms
Social platforms
Business applications
Advantages:
Rapid development
Clean project structure
Large ecosystem
Mature tooling
Excellent developer experience
Trade-offs:
Rails may not match Go-based frameworks in raw performance, though it is more than capable for many production applications.
Which One Should You Learn?
There isn't a single "best" framework. The right choice depends on the kind of problems you're solving.
Choose Django if:
You enjoy Python.
You want lots of built-in functionality.
You want to build complete applications quickly.
Choose Gin if:
You enjoy Go.
Performance matters.
You're interested in cloud-native development and microservices.
Choose Ruby on Rails if:
You value developer productivity.
You want to build products rapidly.
You appreciate clear conventions and an opinionated framework.
Final Thoughts
A framework is just a tool. What matters most is understanding backend fundamentals—HTTP, databases, authentication, APIs, security, caching, and application architecture. Once you understand these concepts, learning a new framework becomes much easier.
Rather than chasing whichever framework is trending today, invest in mastering the principles behind backend development. Frameworks evolve, but solid engineering fundamentals remain valuable throughout your career.
As I continue learning backend development, one thing has become increasingly clear: every framework has its strengths. The key isn't finding the "perfect" framework—it's choosing the one that best fits the problem you're trying to solve.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>backend</category>
      <category>django</category>
      <category>rails</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What a Mini Conference on Computer Graphics Taught Me About Building Better Technology</title>
      <dc:creator>Ouma Asoyoh</dc:creator>
      <pubDate>Mon, 08 Jun 2026 11:03:22 +0000</pubDate>
      <link>https://dev.to/victorouma7/what-a-mini-conference-on-computer-graphics-taught-me-about-building-better-technology-41k2</link>
      <guid>https://dev.to/victorouma7/what-a-mini-conference-on-computer-graphics-taught-me-about-building-better-technology-41k2</guid>
      <description>&lt;p&gt;Every time you unlock your phone, watch a film, or open an app — computer graphics are quietly doing the heavy lifting. We just rarely stop to think about it. 🖥️&lt;/p&gt;

&lt;p&gt;This past Saturday, I had the privilege of attending a mini conference on Computer Graphics at Zone01 Kisumu, and it genuinely shifted the way I think about technology.&lt;/p&gt;

&lt;p&gt;So, what are computer graphics? At its core, it's the field that deals with how computers generate, process, and display visual content — from the icons on your screen to the cinematic visuals in blockbuster films. It sits at the intersection of mathematics, physics, and creativity, and that combination alone makes it fascinating.&lt;/p&gt;

&lt;p&gt;Here's what stood out to me:&lt;/p&gt;

&lt;p&gt;🔢 Mathematics is the invisible engine. Vectors, matrices, and transformations are what make images render, move, and feel real. As someone who sometimes questioned why math mattered in tech — I get it now.&lt;/p&gt;

&lt;p&gt;🎮 The applications are everywhere. Gaming, animation, medical imaging, architectural visualisation, AI-generated visuals, simulations, and the interfaces we interact with daily — computer graphics is the backbone of all of it.&lt;/p&gt;

&lt;p&gt;🌍 The future is wide open. With the growth of AR/VR, AI-driven design tools, and real-time rendering, this field is only expanding. For young developers and entrepreneurs in Africa, that's not a distant opportunity — it's a present one.&lt;/p&gt;

&lt;p&gt;What hit me most was realising that building great digital products isn't just about writing clean code. It's about understanding how users see and experience what you build. Design, visuals, and interaction are not afterthoughts — they are the product.&lt;/p&gt;

&lt;p&gt;As an aspiring technology entrepreneur, this session reminded me that solving real problems requires a wide lens. The more disciplines I understand, the better products I can imagine and build.&lt;/p&gt;

&lt;p&gt;Technology is not one thing — it's a mosaic. And computer graphics is one of its most vivid tiles. 🎨&lt;/p&gt;

&lt;p&gt;To anyone building in tech: when did you first realise that your field connected to something much bigger than you expected? I'd love to hear your story.&lt;/p&gt;

&lt;h1&gt;
  
  
  ComputerGraphics #SoftwareDevelopment #TechInAfrica #Zone01Kisumu #Innovation #LearningEveryDay #ArtificialIntelligence #GameDevelopment #TechEntrepreneur #DigitalInnovation #StudentLife #FutureOfTech
&lt;/h1&gt;

</description>
      <category>beginners</category>
      <category>computerscience</category>
      <category>learning</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
