HackIndia Logo
HackathonsNewsSkillsGrowth
HackIndia LogoContact: +91 9910125804

General

  • Companies
  • Colleges
  • Board of Advisors
  • Brand Ambassadors
  • FAQ
  • Contact
  • Report a Bug

Hackathons

  • Upcoming Hackathons
  • Judges
  • Mentors
  • Partners
  • Participating Colleges
  • Testimonials

Resources

  • About Us
  • Branding
  • Latest News
  • Growth
  • Skills
  • Builders
  • Ecosystem
  • Forum
  • Ideas
  • Prompt Optimizer
  • Games

© 2026 HackIndia. All rights reserved.

Privacy PolicyTerms of ServiceCode of ConductSitemap

Build 2.139 · 05 Nov 2026, 10:27 UTC

    HackIndia
    HackathonsNewsInternshipsSkillsGrowthPro Perks
    ← Build a fast Go service

    All the reading

    Go

    7 lessons, about 29 minutes. The check's questions come from these.

    Contents
    Starting Go services
    1. 1. Start a Go module
    2. 2. Model data with types
    Interfaces and errors
    1. 3. Use interfaces by behavior
    2. 4. Return useful errors
    Concurrency API and shipping
    1. 5. Add goroutines and channels
    2. 6. Build the HTTP API
    3. 7. Ship one binary repo

    Starting Go services

    1. Start a Go module

    4 min

    In 30 seconds
    • Create a Go 1.27 project with a go.mod file.
    • Run a tiny Go program during development.
    • Build a local executable you can later ship in your repo.

    Why modules first

    Your service starts as a module. A module is the unit Go uses to track your packages, dependencies, and Go version.

    For this track, your final repo must be easy for a recruiter to clone and run. A clean go.mod file is the first proof that your project is a real Go project, not loose files.

    Key point

    Remember this

    Start every Go service inside a module, before writing handlers or adding packages. The go.mod file is the project’s dependency record.

    Your to-do

    Create the project

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    mkdir go-fast-service
    cd go-fast-service
    go mod init github.com/YOUR_USERNAME/go-fast-service
    Example

    First runnable file

    Keep the first file boring. The goal is to prove the module runs. HTTP routes, types, errors, and concurrency come later in this track.

    This program prints one line and exits. That is enough to check your Go install, folder, and module setup.

    2. Model data with types

    4 min

    In 30 seconds
    • Define structs that match your service data, not random JSON blobs.
    • Use slices and maps for lists and lookups in memory.
    • Keep model code typed so handlers stay small and clear.

    Why types matter

    Your HTTP service will receive, store, and return data. If that data is loose, every handler becomes a mess of string keys and checks.

    In Go, you model data with structs, slices, and maps. This makes your repo easier for a recruiter to read and easier for you to test.

    Key point

    One idea

    Create types for the real things in your app. Then pass those types through your handlers instead of passing raw maps everywhere.

    Example

    Model a project

    For a HackIndia-style backend, you may track student projects. Start with a struct that names the data clearly.

    JSON tags control the field names your API sends and receives. Keep Go field names exported with capital letters if another package or JSON encoder must access them.

    package main
    
    type Project struct {
    	ID       string   `json:"id"`
    	Name     string   `json:"name"`
    	Team     string   `json:"team"`
    	College  string   `json:"college"`
    	Tags     []string `json:"tags"`
    	RepoURL  string   `json:"repoUrl"`
    	LiveURL  string   `json:"liveUrl,omitempty"`
    }
    
    type ProjectStore struct {
    	items map[string]Project
    	order []string
    }

    Interfaces and errors

    3. Use interfaces by behavior

    4 min

    In 30 seconds
    • Define small interfaces from what your handler needs, not from a database brand.
    • Swap a real store with a fake store without changing handler code.
    • Avoid empty interface{} and large interfaces in your service design.
    Key point

    One idea

    In Go, a type implements an interface by having the methods the interface asks for. You do not write “implements”.

    This means your service can depend on behavior, such as “can find a student”, instead of a concrete detail, such as “uses Postgres”.

    Why it matters

    Your HackIndia repo needs typed handlers and clean design. Interfaces help you keep HTTP code separate from storage code.

    A recruiter should be able to read your handler and see the business action. They should not need to understand your map, file, or database code first.

    Your to-do

    Design the interface

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    Concurrency API and shipping

    5. Add goroutines and channels

    4 min

    In 30 seconds
    • Start background work with a goroutine without blocking your main flow.
    • Pass results back safely with a channel made using make.
    • Add one small goroutine/channel feature your repo can show.
    Key point

    One idea

    A goroutine runs a function at the same time as the rest of your program. A channel lets that goroutine send a typed value back safely.

    Use this for work that can happen in the background, like logging, sending a notification, checking a slow dependency, or preparing a report.

    Where it fits

    Your service does not need complex concurrency to look serious. One clear use is enough for this track.

    For a college project, you can record a visit count, run a fake email receipt, or calculate a summary in the background after an HTTP request.

    Your to-do

    Use the pattern

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    10 questions, 7 to pass.

    Sign in to take the test
    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("go-fast-service is running")
    }
    Your to-do

    Run and build

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 4 done

    go run .
    
    go build
    
    ./go-fast-service
    Tip

    Pick a stable module path

    If your repo will be public, choose the final repo path now, like github.com/yourname/go-fast-service. It makes imports clear later when your project grows into multiple packages.

    If you are just testing locally, a temporary path works. But change it before submitting your HackIndia project.

    Common mistake

    Do not skip go.mod

    Beginners sometimes write main.go first and only fix modules after adding dependencies. That causes messy imports and confusing setup steps.

    Also remember that go run is not the same as shipping. It compiles and runs during development, but it does not leave you with the final executable. Use go build for that.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Your to-do

    Build useful shapes

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Use make correctly

    Slices and maps often need initialization before use. The built-in make creates initialized slices, maps, and channels and returns the value, not a pointer.

    A nil map will panic when you assign into it. So initialize your store before your handlers use it.

    func NewProjectStore() *ProjectStore {
    	return &ProjectStore{
    		items: make(map[string]Project),
    		order: make([]string, 0),
    	}
    }
    
    func (s *ProjectStore) Add(p Project) {
    	s.items[p.ID] = p
    	s.order = append(s.order, p.ID)
    }
    
    func (s *ProjectStore) All() []Project {
    	projects := make([]Project, 0, len(s.order))
    	for _, id := range s.order {
    		projects = append(projects, s.items[id])
    	}
    	return projects
    }
    Common mistake

    Avoid map soup

    Do not model a project as map[string]string just because JSON looks like key-value data. That hides mistakes until runtime.

    With a struct, the compiler catches p.RepoURL versus p.RepoUrl. With a raw map, a typo becomes bad data in your API response.

    Tip

    Use AI carefully

    You can ask AI to draft structs from a sample JSON response. Then check three things yourself: field names are clear, optional fields use omitempty only when empty is valid, and lists are slices, not comma-separated strings.

    If AI gives you interface{} for normal app data, push back. Ask for concrete structs unless the field can truly hold many unrelated shapes.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    0 of 5 done

    Example

    Small store example

    Here the handler depends on StudentStore. The mapStore type matches it because it has FindByID with the same signature.

    No extra keyword is needed. If the method is missing or has the wrong return type, the code will not compile.

    package main
    
    import "fmt"
    
    type Student struct {
    	ID   string
    	Name string
    }
    
    type StudentStore interface {
    	FindByID(id string) (Student, bool)
    }
    
    type mapStore struct {
    	students map[string]Student
    }
    
    func (m mapStore) FindByID(id string) (Student, bool) {
    	student, ok := m.students[id]
    	return student, ok
    }
    
    func PrintStudent(store StudentStore, id string) {
    	student, ok := store.FindByID(id)
    	if !ok {
    		fmt.Println("student not found")
    		return
    	}
    	fmt.Println(student.Name)
    }
    
    func main() {
    	store := mapStore{
    		students: map[string]Student{
    			"101": {ID: "101", Name: "Asha"},
    		},
    	}
    
    	PrintStudent(store, "101")
    }
    Tip

    Use fakes

    Interfaces make testing easier. You can pass a fake store to a handler instead of connecting to a real database or editing live data.

    For a college project, this is enough to show good backend thinking. Your README can say the HTTP layer is tested with a fake store.

    type fakeStore struct{}
    
    func (fakeStore) FindByID(id string) (Student, bool) {
    	return Student{ID: id, Name: "Test Student"}, true
    }
    Common mistake

    Avoid empty interface

    Do not use interface{} because you are unsure of the type. It means any non-interface type can fit, so you lose useful compiler checks.

    Prefer concrete types or a small interface with named methods. Your service will be easier to read and safer to change.

    Go 1.27 note

    Go 1.27 supports generic methods, so older tutorials saying Go has no generic methods are outdated.

    But interface methods may not declare type parameters, and generic methods cannot implement interface methods. Keep service interfaces simple and non-generic.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    4. Return useful errors

    4 min

    In 30 seconds
    • Return errors as values instead of hiding failures or panicking.
    • Add context so a caller knows what failed and why.
    • Use named error values or custom types when code must react.
    Key point

    One idea

    In Go, useful functions return an error as the last value. nil means success. Any other error means the caller must decide what to do next.

    Do not make the caller read a string to understand a known case. Use error values or custom error types for cases your service can handle.

    Why this matters

    Your HTTP service will fail in normal ways. A product ID may be missing. A map lookup may fail. A request body may be invalid.

    Good errors make these failures easy to turn into proper HTTP responses later. Bad errors become random 500s and confusing logs during demos.

    Your to-do

    Shape errors well

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Repository error

    This example uses a map from your data lesson. The repository returns a typed value and a useful error.

    The caller can check ErrProductNotFound without comparing text like "not found".

    package store
    
    import (
    	"errors"
    	"fmt"
    )
    
    type Product struct {
    	ID    string
    	Name  string
    	Price int
    }
    
    var ErrProductNotFound = errors.New("product not found")
    
    type ProductStore struct {
    	items map[string]Product
    }
    
    func NewProductStore() ProductStore {
    	return ProductStore{
    		items: map[string]Product{
    			"chai": {ID: "chai", Name: "Masala Chai", Price: 20},
    		},
    	}
    }
    
    func (s ProductStore) Get(id string) (Product, error) {
    	p, ok := s.items[id]
    	if !ok {
    		return Product{}, fmt.Errorf("get product %q: %w", id, ErrProductNotFound)
    	}
    	return p, nil
    }
    Tip

    Wrap, then check

    Use fmt.Errorf with %w when you want to keep the original error inside a clearer message. This gives logs context and still lets code detect the real error.

    Later, your HTTP handler can use errors.Is(err, ErrProductNotFound) and return 404 instead of 500.

    if errors.Is(err, store.ErrProductNotFound) {
    	// return HTTP 404 later
    }
    Common mistake

    Avoid string checks

    Do not write code that checks err.Error() == "product not found". That breaks when you improve the message.

    Also avoid panic for expected user mistakes. Missing input, unknown IDs, and invalid values should return errors, not crash your service.

    Example

    Validate input

    Validation is also a good place for useful errors. Keep the message short, but mention the field that failed.

    For prices in rupees, reject zero or negative values before saving.

    package store
    
    import "fmt"
    
    func ValidateProduct(p Product) error {
    	if p.ID == "" {
    		return fmt.Errorf("id is required")
    	}
    	if p.Price <= 0 {
    		return fmt.Errorf("price must be positive: got %d", p.Price)
    	}
    	return nil
    }
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    0 of 5 done

    Example

    Small worker example

    This example calculates a total in a goroutine and sends it back on a channel. It uses only the standard library.

    A channel made with make(chan int) is unbuffered. The send waits until another goroutine receives the value.

    package main
    
    import "fmt"
    
    type Order struct {
    	ID     string
    	Amount int
    }
    
    func totalAsync(orders []Order) <-chan int {
    	result := make(chan int)
    
    	go func() {
    		total := 0
    		for _, order := range orders {
    			total += order.Amount
    		}
    		result <- total
    	}()
    
    	return result
    }
    
    func main() {
    	orders := []Order{
    		{ID: "upi-101", Amount: 199},
    		{ID: "upi-102", Amount: 249},
    	}
    
    	totalCh := totalAsync(orders)
    	fmt.Println("doing other work")
    
    	total := <-totalCh
    	fmt.Println("total rupees:", total)
    }
    Tip

    Use direction

    Return a receive-only channel when callers should only read from it. That is why the example returns <-chan int.

    This makes your intent clear and prevents accidental sends from the wrong place.

    Common mistake

    Avoid goroutine leaks

    Do not start a goroutine that sends to a channel if nobody will receive from it. It can wait forever.

    Also do not add goroutines just to look advanced. If normal code is clearer, use normal code.

    Example

    Buffered channel option

    A channel made with make(chan string, 1) has space for one value. The sender can place one value without waiting for the receiver immediately.

    This is useful for small one-shot signals, not for hiding slow code.

    func sendReceiptAsync(orderID string) <-chan string {
    	done := make(chan string, 1)
    
    	go func() {
    		// Replace this later with real email, SMS, or webhook code.
    		done <- "receipt queued for " + orderID
    	}()
    
    	return done
    }
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    6. Build the HTTP API

    5 min

    In 30 seconds
    • Create a small Go HTTP API using net/http handlers.
    • Decode requests, call typed logic, and return JSON responses.
    • Run locally and test your endpoints with curl.
    Key point

    One job per handler

    An HTTP handler should translate web input into your Go types, call your service logic, then translate the result back to HTTP.

    Keep business rules out of the handler. This makes your API easier to test and easier for a recruiter to read.

    Use net/http

    Go ships with an HTTP server in the standard library. You can register a route with http.HandleFunc and start the server with http.ListenAndServe.

    :8080 means your service listens on port 8080 on any interface. ListenAndServe blocks, so your program keeps running until you stop it.

    Your to-do

    Shape the endpoint

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Minimal API

    This example stores tasks in memory. It shows typed handlers, maps, JSON, and method checks. Use this pattern with the types you created earlier in the track.

    For Go 1.27, encoding/json keeps the familiar API while using the newer implementation behind it, so this code stays simple.

    package main
    
    import (
    	"encoding/json"
    	"log"
    	"net/http"
    	"strconv"
    	"strings"
    )
    
    type Task struct {
    	ID   int    `json:"id"`
    	Text string `json:"text"`
    	Done bool   `json:"done"`
    }
    
    type CreateTaskRequest struct {
    	Text string `json:"text"`
    }
    
    type ErrorResponse struct {
    	Error string `json:"error"`
    }
    
    var tasks = map[int]Task{}
    var nextID = 1
    
    func writeJSON(w http.ResponseWriter, status int, v any) {
    	w.Header().Set("Content-Type", "application/json")
    	w.WriteHeader(status)
    	json.NewEncoder(w).Encode(v)
    }
    
    func writeError(w http.ResponseWriter, status int, msg string) {
    	writeJSON(w, status, ErrorResponse{Error: msg})
    }
    
    func tasksHandler(w http.ResponseWriter, r *http.Request) {
    	switch r.Method {
    	case http.MethodGet:
    		list := make([]Task, 0, len(tasks))
    		for _, task := range tasks {
    			list = append(list, task)
    		}
    		writeJSON(w, http.StatusOK, list)
    
    	case http.MethodPost:
    		var req CreateTaskRequest
    		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
    			writeError(w, http.StatusBadRequest, "invalid JSON body")
    			return
    		}
    		req.Text = strings.TrimSpace(req.Text)
    		if req.Text == "" {
    			writeError(w, http.StatusBadRequest, "text is required")
    			return
    		}
    		task := Task{ID: nextID, Text: req.Text}
    		tasks[nextID] = task
    		nextID++
    		writeJSON(w, http.StatusCreated, task)
    
    	default:
    		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
    	}
    }
    
    func taskByIDHandler(w http.ResponseWriter, r *http.Request) {
    	idText := strings.TrimPrefix(r.URL.Path, "/tasks/")
    	id, err := strconv.Atoi(idText)
    	if err != nil {
    		writeError(w, http.StatusBadRequest, "invalid task id")
    		return
    	}
    
    	task, ok := tasks[id]
    	if !ok {
    		writeError(w, http.StatusNotFound, "task not found")
    		return
    	}
    	writeJSON(w, http.StatusOK, task)
    }
    
    func main() {
    	http.HandleFunc("/tasks", tasksHandler)
    	http.HandleFunc("/tasks/", taskByIDHandler)
    	log.Println("listening on :8080")
    	log.Fatal(http.ListenAndServe(":8080", nil))
    }
    Tip

    Test with curl

    Keep sample curl commands in your README. They prove your service runs without needing Postman or a frontend.

    Run the server in one terminal. Use curl from another terminal.

    go run .
    
    curl http://localhost:8080/tasks
    
    curl -X POST http://localhost:8080/tasks \
      -H "Content-Type: application/json" \
      -d '{"text":"pay mess bill by UPI"}'
    
    curl http://localhost:8080/tasks/1
    Common mistake

    Do not hide errors

    A common beginner mistake is returning plain text errors sometimes and JSON errors other times. Pick one style for your API.

    Also, do not ignore json.NewDecoder errors. Bad input should get a 400 response, not a fake empty task.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    7. Ship one binary repo

    4 min

    In 30 seconds
    • Turn your Go HTTP service into a repo someone can run in minutes.
    • Use go build to create one executable instead of shipping loose scripts.
    • Add clear README steps, sample curl, and a simple release workflow.
    Key point

    One thing matters

    Your repo should prove this: a recruiter can clone it, run one command to build it, then start one binary.

    go run is fine while coding, but it does not create the executable you submit. Use go build for the shippable path.

    What to ship

    A strong final repo is boring in a good way. It has a Go 1.27 module, a clear main package, typed handlers, useful errors, slices or maps, one goroutine or channel use, and proof that the API works.

    Keep the shape simple. Do not hide your service behind too many folders just to look senior.

    Example

    Good repo shape

    This is enough for a small internship-ready backend project. Adjust names for your idea, like mess-menu-api, upi-splitter, or placement-tracker.

    mess-menu-api/
      go.mod
      main.go
      internal/
        menu/
          handler.go
          store.go
          errors.go
      README.md
      Makefile
      .gitignore
    Your to-do

    Build the binary

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    go mod tidy
    go build
    ./mess-menu-api
    Example

    README that works

    Your README should answer four questions fast: what it is, how to run it, how to build it, and how to test one endpoint.

    Use commands that you have actually run on your laptop. If AI writes your README, check every command before committing.

    # Mess Menu API
    
    Small Go 1.27 HTTP service for storing hostel mess menu items.
    
    ## Requirements
    
    Go 1.27 or later
    
    ## Run during development
    
    go run .
    
    The service listens on :8080.
    
    ## Build one binary
    
    go mod tidy
    go build
    ./mess-menu-api
    
    ## Try it
    
    curl http://localhost:8080/health
    curl http://localhost:8080/menu
    Tip

    Use a Makefile

    A Makefile gives reviewers short commands without making them learn your folder structure. Keep it tiny.

    If you are on Windows without make, the README commands still matter. The Makefile is a helper, not the only way to run the project.

    run:
    	go run .
    
    build:
    	go mod tidy
    	go build
    
    test-api:
    	curl http://localhost:8080/health
    	curl http://localhost:8080/menu
    Common mistake

    Common shipping mistakes

    Do not submit only screenshots or only a deployed URL. The task is a public repository someone can inspect and run.

    Do not leave your README saying go run if your project needs environment variables, a port, or seed data. Write the real steps. Also avoid copying old Go advice blindly; Go 1.27 modules may tidy require blocks differently than old tutorials show.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done