← thecodex.expert · The Codex Family of Knowledge
Tier 2 · Intermediate · Go Project

URL Shortener

Turn long URLs into short codes and back again, saved to a file. Learn two-way lookups and generating unique keys.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a URL shortener: give it a long link, it returns a short code; give back the code, it returns the original link. It teaches two-way lookups (code↔URL), generating unique keys, and persisting a small store — the core of any link service.

Where this shows up: bit.ly and every link shortener, QR-code targets, affiliate links, any system that maps a short key to a longer value — which includes caches, session stores, and lookup services generally.

2 How to Think About It

Think about the mapping, before any code:

The plan — in plain English
1. Keep a store mapping short code → long URL. → 2. To shorten: make a new random short code, save the mapping, return the code. → 3. To expand: look the code up and return the URL. → 4. Save the store to a file so links survive.

Long URL comes in

Generate a short code

Save code to URL mapping

Return the short code

Short code comes in

Look up the URL

Return the long URL

3 The Build — explained part by part

Here is the complete shortener. Each part is explained below.

Goshortener.go
package main

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"math/big"
	"os"
)

const (
	file       = "links.json"
	codeChars  = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	codeLength = 6
)

func load() map[string]string {
	data, err := os.ReadFile(file)
	if err != nil {
		return map[string]string{}
	}
	links := map[string]string{}
	if err := json.Unmarshal(data, &links); err != nil {
		return map[string]string{}
	}
	return links
}

func save(links map[string]string) {
	data, _ := json.Marshal(links)
	os.WriteFile(file, data, 0644)
}

// makeCode builds a random 6-character code from letters and digits, using
// crypto/rand — the secure choice, same lesson as the password generator.
func makeCode() string {
	b := make([]byte, codeLength)
	for i := range b {
		n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(codeChars))))
		b[i] = codeChars[n.Int64()]
	}
	return string(b)
}

// shorten makes a new code, retrying on a clash, and stores the mapping.
func shorten(links map[string]string, url string) string {
	code := makeCode()
	for {
		if _, exists := links[code]; !exists {
			break
		}
		code = makeCode()
	}
	links[code] = url
	return code
}

// expand looks up a code. The second return value is false for an unknown
// code — Go's comma-ok idiom, used instead of Python's links.get(code).
func expand(links map[string]string, code string) (string, bool) {
	url, ok := links[code]
	return url, ok
}

func main() {
	links := load()
	code := shorten(links, "https://example.com/a/very/long/link")
	save(links)
	fmt.Printf("Short code: %s\n", code)
	if url, ok := expand(links, code); ok {
		fmt.Printf("Expands to: %s\n", url)
	}
}
⚠ No in-browser playground here
Go compiles to a real binary, so unlike the Python version of this project there is no editor above you can run in the browser. Copy the code below and run it on your own machine — it takes seconds once Go is installed.
What each part does — in plain words
map[string]string — Go’s map is the equivalent of Python’s dict, and here it is the database: short code as key, long URL as value.

makeCode() — build a random 6-character code from letters and digits using crypto/rand — the same secure-randomness lesson as the password generator, applied to a different problem.

for { if _, exists := links[code]; !exists { break }; code = makeCode() } — the safety step: if the random code happens to already exist, generate another and check again. This guarantees every code is unique before it is ever stored.

url, ok := links[code] — Go’s comma-ok idiom for map lookups. Reading a missing key from a Go map never panics — it just returns the zero value ("" for a string) — but that is ambiguous if an empty string is ever a valid value, so ok tells you for certain whether the key was actually present. This is Go’s answer to Python’s links.get(code) returning None.
Common mistakes — and how to avoid them
✗ Reading with plain url := links[code] and treating an empty result as “not found” — a genuinely empty stored value would look identical to a missing key.
✓ Always use the two-value form url, ok := links[code] and check ok.
✗ Not checking for code clashes — a repeated random code would silently overwrite an existing link.
✓ Loop and regenerate until you find a code that is not already a key, as shorten does.
✗ Calling save(links) before adding the new mapping to the map — order matters, since maps in Go are reference types but the write still has to happen first.
✓ Mutate the map, then save it, in that order — as in main above.

4 Test & Prove Each Part

We test the round-trip (shorten then expand) and that codes are unique and the right length.

A shortened URL expands back to the original
Each short code is the expected length
An unknown code reports not-found
Goshortener_test.go
package main

import "testing"

func TestRoundTrip(t *testing.T) {
	links := map[string]string{}
	code := shorten(links, "https://example.com")
	got, ok := expand(links, code)
	if !ok {
		t.Fatalf("expand(%q) not found; want it present", code)
	}
	if got != "https://example.com" {
		t.Errorf("expand(%q) = %q; want %q", code, got, "https://example.com")
	}
}

func TestCodeLength(t *testing.T) {
	if got := len(makeCode()); got != 6 {
		t.Errorf("len(makeCode()) = %d; want 6", got)
	}
}

func TestUnknownCode(t *testing.T) {
	_, ok := expand(map[string]string{}, "missing")
	if ok {
		t.Errorf("expand on empty map returned ok=true; want false")
	}
}

Run with go test -v ./.... The round-trip test is the most important: it proves the two halves (shorten and expand) fit together. Testing a there-and-back journey is a powerful way to verify any mapping, in any language.

5 The Interface

INPUTSHORTENlong url
What it expects
shorten(links, "https://example.com/very/long")
OUTPUTEXPANDshort code
What it returns
Short code: T0Gl9y
Expands to: https://example.com/very/long

6 Run It & Automate It

Save the code as shortener.go and run it with go run shortener.go — Go compiles and executes in one step, no separate build needed while you are experimenting.

Run it locally
go run shortener.go
Shortens a link and expands it back. Links are saved in links.json, next to the program.

A CI tool like Jenkins runs go test automatically whenever the code changes — every line below has a plain explanation.

What you should see when it works
Terminala real run
Short code: T0Gl9y
Expands to: https://example.com/a/very/long/link
If it breaks — how to fix it
🚨 Expands to: (nothing prints)
You checked expand’s result without looking at the second (ok) return value. Always destructure both: url, ok := expand(...).
🚨 Links lost between runs.
You did not save. Call save(links) after shortening, before the program exits.
GroovyJenkinsfile
// Jenkinsfile — runs the tests automatically every time the code changes.
pipeline {
    agent any                                  // run on any available machine

    stages {
        stage('Get the code') {
            steps { checkout scm }             // download the latest code
        }
        stage('Set up Go') {
            steps {
                sh 'go version'                                 // confirm Go is installed
                sh 'test -f go.mod || go mod init url_shortener'  // create a module if none exists
            }
        }
        stage('Run the tests') {
            steps {
                sh 'go vet ./...'                    // catch obvious mistakes before running
                sh 'go test -v ./...'                // run every test, show each result
            }
        }
    }

    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Custom codes. Let users pick their own code if it is free, checking with the comma-ok idiom. (Teaches: checking availability.)
  2. Click counts. Store a small struct {URL string; Clicks int} as the map value instead of a bare string. (Teaches: richer values.)
  3. Make it a web service. Combine with the REST API project so GET /{code} redirects. (Teaches: connecting projects.)
What you learned
You learned two-way lookups with a map, generating unique keys safely with a clash check, and Go’s comma-ok idiom for graceful not-found handling — the direct replacement for Python’s .get(). This key-to-value mapping is the basis of caches, stores, and link services. Related: Maps, JSON & Encoding.