Parallel URL Checker

Checks multiple URLs concurrently using goroutines, WaitGroup for synchronization, channels for collecting results, and select with time.After for timeouts.

package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"
)

type Result struct {
	URL      string
	Status   int
	Duration time.Duration
	Err      error
}

func (r Result) String() string {
	if r.Err != nil {
		return fmt.Sprintf("  %-40s ERROR  %v", r.URL, r.Err)
	}
	return fmt.Sprintf("  %-40s %d    %v", r.URL, r.Status, r.Duration.Round(time.Millisecond))
}

func checkURL(url string, timeout time.Duration) Result {
	start := time.Now()

	client := &http.Client{Timeout: timeout}
	resp, err := client.Get(url)
	duration := time.Since(start)

	if err != nil {
		return Result{URL: url, Err: err, Duration: duration}
	}
	defer resp.Body.Close()

	return Result{URL: url, Status: resp.StatusCode, Duration: duration}
}

func checkAll(urls []string, perURLTimeout, totalTimeout time.Duration) []Result {
	results := make(chan Result, len(urls))
	var wg sync.WaitGroup

	for _, url := range urls {
		wg.Add(1)
		go func(u string) {
			defer wg.Done()
			results <- checkURL(u, perURLTimeout)
		}(url)
	}

	// Close results channel once all goroutines finish
	go func() {
		wg.Wait()
		close(results)
	}()

	var collected []Result
	timer := time.After(totalTimeout)

	for {
		select {
		case r, ok := <-results:
			if !ok {
				return collected
			}
			collected = append(collected, r)
		case <-timer:
			fmt.Println("  Global timeout reached, returning partial results.")
			return collected
		}
	}
}

func main() {
	urls := []string{
		"https://go.dev",
		"https://github.com",
		"https://httpbin.org/delay/1",
		"https://httpbin.org/status/404",
		"https://nonexistent.invalid",
		"https://httpbin.org/status/500",
		"https://example.com",
	}

	fmt.Printf("Checking %d URLs concurrently...\n\n", len(urls))
	fmt.Printf("  %-40s %-6s %s\n", "URL", "STATUS", "DURATION")
	fmt.Println("  " + "---------------------------------------- ------ --------")

	start := time.Now()
	results := checkAll(urls, 5*time.Second, 10*time.Second)

	for _, r := range results {
		fmt.Println(r)
	}

	fmt.Printf("\nChecked %d/%d URLs in %v\n", len(results), len(urls), time.Since(start).Round(time.Millisecond))

	// Count successes
	ok := 0
	for _, r := range results {
		if r.Err == nil && r.Status >= 200 && r.Status < 400 {
			ok++
		}
	}
	fmt.Printf("Healthy: %d/%d\n", ok, len(results))
}

💻 Run locally

Copy the code above and run it on your machine

© 2026 ByteLearn.dev. Free courses for developers. · Privacy