ProgramGuru

Concurrent File Downloader

Start with Concurrent File Downloader

Learn concurrent file downloader through an executable example, then connect the small demonstration to production code.

What you’ll learn

  • Explain the main purpose of concurrent file downloader.
  • Read and run the complete Go example in this lesson.
  • Apply the concept and recognize common mistakes.

Understand the idea

A concurrent downloader requests independent files in parallel while respecting a worker limit and request timeouts. Always stream or cap large bodies and safely choose output filenames.

This demonstration downloads two small files from a local HTTP test server into an isolated temporary directory, avoiding external-network dependencies.

Visualize how it works

Follow this diagram before executing the example. Check where a value or decision changes what runs next.

Visual explanation for file downloaderBound resources and report each download outcome.Prepare URLsStep 1Start bounded workersStep 2Stream to temp filesStep 3Verify + reportStep 4Bound resources and report each download outcome.
Bound resources and report each download outcome.

Tools, syntax and practical notes

Hardening: enforce URL allowlists where appropriate, connect/read timeouts, maximum size, temporary-file-and-rename writes, retries with backoff and per-job error handling. Do not trust Content-Length as the only size safeguard.

Run a complete Go example

This example is a complete program. Save it as main.go or paste it into the Go Playground.

Go code

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"sync"
	"time"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "data:"+r.URL.Path) }))
	defer server.Close()
	dir, err := os.MkdirTemp("", "go-download-")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)
	names := []string{"a.txt", "b.txt"}
	sizes := make([]int64, len(names))
	client := &http.Client{Timeout: 2 * time.Second}
	var wg sync.WaitGroup
	for i, name := range names {
		wg.Add(1)
		go func(index int, filename string) {
			defer wg.Done()
			response, err := client.Get(server.URL + "/" + filename)
			if err != nil {
				panic(err)
			}
			defer response.Body.Close()
			file, err := os.Create(filepath.Join(dir, filename))
			if err != nil {
				panic(err)
			}
			size, err := io.Copy(file, response.Body)
			closeErr := file.Close()
			if err != nil || closeErr != nil {
				panic("download failed")
			}
			sizes[index] = size
		}(i, name)
	}
	wg.Wait()
	for i, name := range names {
		fmt.Println(name, "bytes:", sizes[i])
	}
}

Output

a.txt bytes: 11
b.txt bytes: 11

Trace what the program does

Two goroutines fetch independent local paths. Each writes to a separate file and stores its byte count by index; Wait ensures that the printed results are complete.

Avoid common mistakes

  • In a production downloader, return worker errors through channels instead of panicking in goroutines.
  • Use an explicit concurrency limit, response-status checks, content-size bounds and safe filenames before accepting arbitrary URLs.

Try it yourself

Add a worker pool and error channel, reject non-200 responses and compute hashes for downloaded content.

Tip: Edit the example above, predict the output, and run the modified code to test your reasoning.

Check your understanding

What ensures both downloads have finished before printing?
Choose one answer. You can try again.

Answer and explanation

sync.WaitGroup.Wait

Correct. Two goroutines fetch independent local paths.

What you should remember

You have practiced Concurrent File Downloader in a small, self-contained program. The important habit is to predict what runs, inspect the actual output and distinguish the language rule from the particular example.

Before continuing, make sure you can explain the program to someone who has not seen it, including the line that changes its result.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Complete Go Web Application →