Go: Go Map and Structures

Last updated: 2026-08-26

Maps and structs are the two pillars of data modeling in Go—maps handle dynamic queries, while structs describe fixed structures—and together, they can describe 90% of business objects.

Maps provide O(1) key-value lookups, while structs aggregate data using fields. In this lesson, you'll master all the core concepts of data modeling in Go and be able to use them to build a complete e-commerce inventory system.

1. You will learn



2. The True Story of an E-commerce Engineer

(1) Pain point: Slice lookups are too slow

Bob is a واجهة خلفية engineer at an e-commerce platform. With the Double 11 pre-sale campaign underway, the inventory query API has been incredibly slow:

"We have 100,000 SKUs in stock, stored using a []Product slice, and every query involves a linear scan. As QPS increases, the average استجابة time is 800 ms with 100% CPU usage. My boss has given me three days to optimize this to under 100 ms."

He opened the code he had written:

GO
// Version 1: Store all products using a slice
var products []Product

func findProduct(sku string) *Product {
    for _, p := range products {  // O(n) linear scan
        if p.SKU == sku {
            return &p
        }
    }
    return nil
}

100,000 products × 1,000 QPS = 100 million traversals per second; 100% CPU utilization is inevitable.

(2) Solution in Go: Use a map for O(1) lookups

GO
// inventory.go
package main

import "fmt"

type Product struct {
    SKU      سلسلة  // Product unique identifier
    Name     سلسلة  // Product name
    Price    float64 // Price
    Stock    int     // Stock count
    Category سلسلة  // Classification
}

// Using map to create a hash table
type Inventory struct {
    products map[سلسلة]Product  // SKU -> Product
}

func NewInventory() *Inventory {
    return &Inventory{products: make(map[سلسلة]Product)}
}

// O(1) query
func (inv *Inventory) Find(sku سلسلة) (Product, bool) {
    p, ok := inv.products[sku]
    return p, ok
}

// O(1) update
func (inv *Inventory) UpdateStock(sku سلسلة, delta int) خطأ {
    p, ok := inv.products[sku]
    if !ok {
        return fmt.Errorf("SKU %s not found", sku)
    }
    p.Stock += delta
    inv.products[sku] = p
    return nil
}

func main() {
    inv := NewInventory()

    // Bulk import of 100,000 SKUs
    for i := 0; i < 100000; i++ {
        sku := fmt.Sprintf("SKU-%05d", i)
        inv.products[sku] = Product{
            SKU:      sku,
            Name:     fmt.Sprintf("Product-%d", i),
            Price:    99.99,
            Stock:    100,
            Category: "electronics",
        }
    }

    // Query performance comparison
    p, ok := inv.Find("SKU-50000")
    if ok {
        fmt.Printf("Found: %s, price=%.2f\n", p.Name, p.Price)
    }

    // Update inventory
    inv.UpdateStock("SKU-50000", -5)
}

Output:

TEXT 📖 Display only
Found: Product-50000, price=99.99

(3) Performance: Query Performance of slice vs. map

Data Size Linear Scan in slice Hash Lookup in map Performance Gap
100 SKUs 50 ns 50 ns equivalent
1,000 SKUs 500 ns 50 ns 10x
10,000 SKUs 5 µs 50 ns 100x
100,000 SKUs 50 µs 50 ns 1,000x
💡 Tip: The O(1) complexity of a map is the average complexity based on a hash table—in the worst case (hash collisions), it degrades to O(n). Go minimizes this probability through a well-designed hash دالة and resizing mechanism.



3. Map Basics

(1) Three Ways to Create a Map

GO
package main

import "fmt"

func main() {
    // Method 1: make (Recommended)
    m1 := make(map[string]int)  // empty map, writable

    // Method 2: make + pre-allocated capacity
    m2 := make(map[string]int, 100)  // pre-allocate 100 capacity, reduce resizing

    // Method 3: Literal Initialization
    m3 := map[string]int{
        "Alice": 28,
        "Bob":   32,
    }

    // Method 4: nil map (read-only, cannot be written to)
    var m4 map[string]int  // == nil, cannot do m4["a"] = 1
    _ = m4
}

(2) map CRUD

GO
package main

import "fmt"

func main() {
    ages := make(map[سلسلة]int)

    // Create
    ages["Alice"] = 28
    ages["Bob"] = 32

    // Read
    fmt.Println(ages["Alice"])  // 28

    // Update
    ages["Alice"] = 29

    // Delete
    delete(ages, "Bob")

    // Length
    fmt.Printf("len=%d\n", len(ages))

    fmt.Println(ages)  // map[Alice:29]
}

Output:

TEXT 📖 Display only
28
len=1
map[Alice:29]

▶ Example: map iterate (order is random)

GO
package main

import "fmt"

func main() {
    ages := map[string]int{"Alice": 28, "Bob": 32, "Charlie": 45}

    // for range: key + value
    for name, age := range ages {
        fmt.Printf("%s is %d years old\n", name, age)
    }

    // Only the key
    for name := range ages {
        fmt.Printf("name: %s\n", name)
    }

    // Only the value (use _ to ignore the key)
    for _, age := range ages {
        fmt.Printf("age: %d\n", age)
    }
}
▶ Try it Yourself

Output (in random order):

TEXT 📖 Display only
Alice is 28 years old
Charlie is 45 years old
Bob is 32 years old
name: Bob
...
🔥 Common Mistake: The iteration order of a Go map is intentionally randomized—to prevent programmers from relying on a specific order. If you need a consistent order, sort the keys first.



4. comma-ok Syntax (Key Point)

(1) Distinguish between "zero value" and "does not exist"

GO
package main

import "fmt"

func main() {
    ages := map[سلسلة]int{"Alice": 28}

    // Incorrect approach: Cannot distinguish between "key does not exist" and "value = 0"
    age := ages["Bob"]
    fmt.Printf("Bob's age: %d\n", age)  // 0 (but is Bob really 0 years old?)

    // Correct approach: comma-ok
    age, ok := ages["Bob"]
    if ok {
        fmt.Printf("Bob's age: %d\n", age)
    } else {
        fmt.Println("Bob not found")
    }

    // Just check if it exists: discard the value
    _, exists := ages["Bob"]
    fmt.Printf("Bob exists: %v\n", exists)
}

Output:

TEXT 📖 Display only
Bob's age: 0
Bob not found
Bob exists: false

(2) comma-ok in Practice: Caching Queries

GO
package main

import "fmt"

// Cache: key → value
var cache = make(map[string]string)

func getCached(key string) (string, bool) {
    val, ok := cache[key]
    return val, ok
}

func main() {
    // Set cache
    cache["user:1"] = "Alice"
    cache["user:2"] = "Bob"

    // Query cache
    if val, ok := getCached("user:1"); ok {
        fmt.Printf("Hit: %s\n", val)
    } else {
        fmt.Println("Miss")
    }

    if _, ok := getCached("user:999"); !ok {
        fmt.Println("user:999 not in cache, fetching from DB...")
    }
}

Output:

TEXT 📖 Display only
Hit: Alice
user:999 not in cache, fetching from DB...

(3) comma-ok General Pattern

Context Syntax Meaning
Map lookup v, ok := m[key] Does the key exist?
Type Assertion v, ok := x.(T) Type Match?
Receiving from channel v, ok := <-ch Channel closed?


5. Basics of Structs

(1) Defining and Initializing Structures

GO
package main

import "fmt"

// define struct
type User struct {
    Name سلسلة
    Age  int
    City سلسلة
}

func main() {
    // Method 1: By field order (not recommended; poor readability)
    u1 := User{"Alice", 28, "Shanghai"}

    // Method 2: Initializing Field Names (Recommended)
    u2 := User{
        Name: "Bob",
        Age:  32,
        City: "Beijing",
    }

    // Method 3: Partial Initialization (with the remainder set to zero)
    u3 := User{Name: "Charlie"}  // Age=0, City=""

    // Method 4: new() returns a pointer
    u4 := new(User)
    u4.Name = "Dave"

    fmt.Println(u1, u2, u3, u4)
}

Output:

TEXT 📖 Display only
{Alice 28 Shanghai} {Bob 32 Beijing} {Charlie  } &{Dave 0 }

(2) Field Access and Modification

GO
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func main() {
    u := User{Name: "Alice", Age: 28}

    // Read field
    fmt.Println(u.Name)  // Alice

    // Write to a field
    u.Age = 29

    // Pointer access (automatic dereferencing)
    p := &u
    fmt.Println(p.Name)  // Alice (equivalent to (*p).Name)
    p.Age = 30  // automatic dereferencing
}

▶ Example: Struct Copying vs. Pointers

GO
package main

import "fmt"

type Counter struct {
    Value int
}

// Pass-by-value: Copy the entire struct
func incrementByValue(c Counter) {
    c.Value++  // modifies the copy
    fmt.Printf("Inside دالة: %d\n", c.Value)
}

// Pointer passing: pass by reference
func incrementByPointer(c *Counter) {
    c.Value++  // modifies the original
    fmt.Printf("Inside دالة: %d\n", c.Value)
}

func main() {
    c := Counter{Value: 10}

    incrementByValue(c)
    fmt.Printf("After value passing: %d\n", c.Value)  // 10 (unchanged)

    incrementByPointer(&c)
    fmt.Printf("After pointer passing: %d\n", c.Value)  // 11 (modified)
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Inside function: 11
After value passing: 10
Inside function: 11
After pointer passing: 11
🔥 Common Mistake: A struct is a value type, so passing it as a parameter copies the entire struct. For large structs or when you need to modify the original كائن, you must use a pointer.



6. struct علامة (Key to JSON Serialization)

(1) struct علامة syntax

GO
type User struct {
    Name     string `json:"name" db:"user_name"`
    Age      int    `json:"age" validate:"min=0,max=150"`
    Email    string `json:"email,omitempty"`
    Password string `json:"-"`  // - means JSON ignores this field
}

(2) Hands-On JSON Serialization

GO
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name     سلسلة `json:"name"`
    Age      int    `json:"age"`
    Email    سلسلة `json:"email,omitempty"`
    Password سلسلة `json:"-"`  // password not serialized
}

func main() {
    u := User{
        Name:     "Alice",
        Age:      28,
        Email:    "alice@example.com",
        Password: "secret123",
    }

    // Serialization: struct → JSON
    data, _ := json.Marshal(u)
    fmt.Printf("JSON: %s\n", data)

    // Deserialization: JSON → struct
    jsonStr := `{"name":"Bob","age":32,"email":"bob@example.com"}`
    var u2 User
    json.Unmarshal([]byte(jsonStr), &u2)
    fmt.Printf("Deserialization: %+v\n", u2)
}

Output:

TEXT 📖 Display only
JSON: {"name":"Alice","age":28,"email":"alice@example.com"}
Deserialization: {Name:Bob Age:32 Email:bob@example.com Password:}

(3) Commonly used struct علامة libraries

Library Tag Name Purpose
encoding/json json:"name" JSON field name
gorm gorm:"primaryKey" ORM field constraints
validator validate:"required" Field validation
yaml yaml:"name" YAML serialization
💡 Tip: Lesson 10, "file-json," will cover all the details of JSON serialization in depth.



7. Nested Structures

(1) Nested structs (composition rather than inheritance)

Go does not have inheritance; it uses nested structs to achieve composition:

GO
package main

import "fmt"

type Address struct {
    City    string
    Country string
}

type User struct {
    Name    string
    Age     int
    Address Address  // nested Address
}

func main() {
    u := User{
        Name: "Alice",
        Age:  28,
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    // Accessing nested fields
    fmt.Println(u.Address.City)     // Shanghai
    fmt.Println(u.Address.Country)  // China
}

(2) Anonymous Nesting (Field Promotion)

GO
package main

import "fmt"

type Address struct {
    City    سلسلة
    Country سلسلة
}

// Anonymous nesting: fields are "promoted"
type User struct {
    Name سلسلة
    Age  int
    Address  // equivalent to Address Address, but without the field name
}

func main() {
    u := User{
        Name: "Alice",
        Age:  28,
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    // Field promotion: direct access, without u.Address.City
    fmt.Println(u.City)     // Shanghai
    fmt.Println(u.Country)  // China

    // You can also use the full path
    fmt.Println(u.Address.City)
}

▶ Example: Nested + JSON Tag in Practice

GO
package main

import (
    "encoding/json"
    "fmt"
)

type Address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

type User struct {
    Name    string  `json:"name"`
    Age     int     `json:"age"`
    Email   string  `json:"email,omitempty"`
    Address Address `json:"address"`
}

func main() {
    u := User{
        Name:  "Alice",
        Age:   28,
        Email: "alice@example.com",
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    data, _ := json.MarshalIndent(u, "", "  ")
    fmt.Println(string(data))
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
{
  "name": "Alice",
  "age": 28,
  "email": "alice@example.com",
  "address": {
    "city": "Shanghai",
    "country": "China"
  }
}


8. Choosing Between map and slice

(1) Model Selection Reference Table

Dimension slice []T map map[K]V
Lookup Method Linear Scan O(n) Hash O(1)
Is it ordered? ✅ Ordered ❌ Unordered
Memory usage Compact (24 + len*size) Less compact (hash table + bucket)
Delete an element Requires manual movement delete(m, k) O(1)
Typical Scenarios Lists/Queues/Stacks/Sorting Dictionaries/Caches/Indexes

(2) Model Selection Decision Tree

100%
graph TB
    A[Need to store multiple elements] --> B{Need to look up by key?}
    B -->|Yes| C[Use map<br/>O(1) lookup]
    B -->|No| D{Need to maintain order?}
    D -->|Yes| E[Use slice]
    D -->|No| F{Element count < 100?}
    F -->|Yes| G[Either slice or map works]
    F -->|No| C

(3) Real-World Example: Student Grade Management

GO
package main

import "fmt"

// Store student grades in a map (look up by student ID)
type GradeBook struct {
    scores map[string]int  // Student ID → Score
}

func (gb *GradeBook) Set(id string, score int) {
    gb.scores[id] = score
}

func (gb *GradeBook) Get(id string) (int, bool) {
    score, ok := gb.scores[id]
    return score, ok
}

// Use a slice to store a list of scores (for sorting or calculating the average)
func average(scores []int) float64 {
    if len(scores) == 0 {
        return 0
    }
    sum := 0
    for _, s := range scores {
        sum += s
    }
    return float64(sum) / float64(len(scores))
}

func main() {
    gb := &GradeBook{scores: make(map[string]int)}
    gb.Set("S001", 95)
    gb.Set("S002", 82)
    gb.Set("S003", 67)

    if s, ok := gb.Get("S001"); ok {
        fmt.Printf("S001: %d\n", s)
    }

    // Collect all scores and calculate the average
    allScores := []int{}
    for _, s := range gb.scores {
        allScores = append(allScores, s)
    }
    fmt.Printf("Average Score: %.2f\n", average(allScores))
}

Output:

TEXT 📖 Display only
S001: 95
Average Score: 81.33
💡 Tip: In real-world projects, it's common to use map and slice together—map for querying and slice for sorting or aggregation.



9. Complete Example: E-commerce SKU Inventory System

Combine all the features of maps and structs to build a complete e-commerce inventory query system:

GO
// inventory_system.go
package main

import (
    "encoding/json"
    "fmt"
    "sort"
)

// Product
type Product struct {
    SKU      string   `json:"sku"`
    Name     string   `json:"name"`
    Price    float64  `json:"price"`
    Stock    int      `json:"stock"`
    Category string   `json:"category"`
    Tags     []string `json:"tags,omitempty"`  // Tags: new/hot/discount
}

// Inventory System
type Inventory struct {
    products map[string]Product  // SKU → Product
}

func NewInventory() *Inventory {
    return &Inventory{products: make(map[string]Product)}
}

// Add product
func (inv *Inventory) Add(p Product) {
    inv.products[p.SKU] = p
}

// Query (comma-ok)
func (inv *Inventory) Find(sku string) (Product, bool) {
    p, ok := inv.products[sku]
    return p, ok
}

// Update stock
func (inv *Inventory) UpdateStock(sku string, delta int) error {
    p, ok := inv.products[sku]
    if !ok {
        return fmt.Errorf("SKU %s not found", sku)
    }
    newStock := p.Stock + delta
    if newStock < 0 {
        return fmt.Errorf("insufficient stock for %s: have %d, need %d",
            sku, p.Stock, -delta)
    }
    p.Stock = newStock
    inv.products[sku] = p
    return nil
}

// Sort by price (unordered map → convert to slice)
func (inv *Inventory) ListByPrice() []Product {
    list := make([]Product, 0, len(inv.products))
    for _, p := range inv.products {
        list = append(list, p)
    }
    sort.Slice(list, func(i, j int) bool {
        return list[i].Price < list[j].Price
    })
    return list
}

// Filter by category
func (inv *Inventory) FindByCategory(category string) []Product {
    var result []Product
    for _, p := range inv.products {
        if p.Category == category {
            result = append(result, p)
        }
    }
    return result
}

func main() {
    inv := NewInventory()

    // Initialize 100 SKUs
    categories := []string{"electronics", "clothing", "food"}
    for i := 0; i < 100; i++ {
        inv.Add(Product{
            SKU:      fmt.Sprintf("SKU-%05d", i),
            Name:     fmt.Sprintf("Product-%d", i),
            Price:    float64(i%50 + 10),
            Stock:    100 - i%30,
            Category: categories[i%3],
        })
    }

    // 1. Look up a single SKU
    if p, ok := inv.Find("SKU-0050"); ok {
        fmt.Printf("Found: %s, price=%.2f, stock=%d\n", p.Name, p.Price, p.Stock)
    }

    // 2. Update inventory
    if err := inv.UpdateStock("SKU-0050", -10); err == nil {
        fmt.Println("Stock updated successfully")
    }

    // 3. Filter by category
    electronics := inv.FindByCategory("electronics")
    fmt.Printf("\nElectronics products: %d\n", len(electronics))

    // 4. Sort by price (top 3)
    sorted := inv.ListByPrice()
    fmt.Println("\nTop 3 cheapest:")
    for i, p := range sorted[:3] {
        fmt.Printf("  %d. %s: %.2f\n", i+1, p.Name, p.Price)
    }

    // 5. JSON Serialization (API Response)
    if p, ok := inv.Find("SKU-0050"); ok {
        data, _ := json.MarshalIndent(p, "", "  ")
        fmt.Printf("\nJSON output:\n%s\n", data)
    }
}

Expected Output:

TEXT 📖 Display only
Found: Product-50, price=10.00, stock=70
Stock updated successfully

Electronics products: 34

Top 3 cheapest:
  1. Product-0: 10.00
  2. Product-30: 10.00
  3. Product-60: 10.00

JSON output:
{
  "sku": "SKU-0050",
  "name": "Product-50",
  "price": 10,
  "stock": 60,
  "category": "food"
}
🔥 Common Mistake: In the ListByPrice method, sort.Slice uses a closure as the comparison function. The closure captures the list variable and calls it during each comparison.



10. Additional Example Sets

▶ Example: Performance Comparison Between Initializing with a map Literal and Using make

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // Literal initialization
    start1 := time.Now()
    m1 := map[string]int{
        "a": 1, "b": 2, "c": 3, "d": 4, "e": 5,
        "f": 6, "g": 7, "h": 8, "i": 9, "j": 10,
    }
    fmt.Printf("Literals: %v, %v\n", len(m1), time.Since(start1))

    // make pre-allocation
    start2 := time.Now()
    m2 := make(map[string]int, 10)
    m2["a"] = 1
    m2["b"] = 2
    m2["c"] = 3
    m2["d"] = 4
    m2["e"] = 5
    m2["f"] = 6
    m2["g"] = 7
    m2["h"] = 8
    m2["i"] = 9
    m2["j"] = 10
    fmt.Printf("make: %v, %v\n", len(m2), time.Since(start2))
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Literals: 10, 1.2µs
make: 10, 850ns

▶ Example: Methods that accept struct values vs. methods that accept pointers

GO
package main

import "fmt"

type Counter struct {
    Value int
}

// Value receiver: operates on a copy; does not affect the original
func (c Counter) IncrementValue() Counter {
    c.Value++
    return c
}

// Pointer receiver: directly modifies the original
func (c *Counter) IncrementPointer() {
    c.Value++
}

func main() {
    c := Counter{Value: 10}

    // Value receiver: must use the return value
    c = c.IncrementValue()
    fmt.Printf("After value receiver: %d\n", c.Value)  // 11

    // Pointer receiver: direct modification
    c.IncrementPointer()
    fmt.Printf("After pointer receiver: %d\n", c.Value)  // 12
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
After value receiver: 11
After pointer receiver: 12

▶ Example: Demonstration of Concurrency Pitfalls in map

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    m := make(map[string]int)

    // Writing goroutine
    go func() {
        for i := 0; i < 1000; i++ {
            m["key"] = i  // write operation
        }
    }()

    // Reading goroutine
    go func() {
        for i := 0; i < 1000; i++ {
            _ = m["key"]  // read operation
        }
    }()

    time.Sleep(100 * time.Millisecond)
    fmt.Println("Concurrent reads and writes may cause a panic (fatal error: concurrent map read and map write)")
}
▶ Try it Yourself

Output (may cause a panic during runtime):

TEXT 📖 Display only
Concurrent reads and writes may cause a panic (fatal خطأ: concurrent map read and map write)
🔥 Common Mistake: This is a classic pitfall in Go concurrency. Concurrent maps must be protected with sync.Map or sync.Mutex; this is covered in detail in Lesson 16.


❓ FAQ

Q What happens if you try to access a non-existent key in a map?
A It returns a zero value. However, a zero value may be a valid value (e.g., age=0), so you should use "comma-ok" to distinguish between "does not exist" and "value is zero."
Q Is map a reference type?
A Yes. A map variable is a pointer to a hash table; both assignment and parameter passing share the same underlying hash table. It is similar to a slice but more radical—while a slice can trigger resizing in isolation via append, a map lacks this isolation mechanism.
Q Can structs be compared?
A Only if all fields are comparable. Structs containing slices, maps, or func cannot be compared (compilation error). Use reflect.DeepEqual() for a deep comparison.
Q When should you use nested structs, and when should you use pointers?
A (1) If you need to modify the original object → use nested pointers; (2) If the struct is large → use nested pointers (to avoid copying); (3) Polymorphism is required (to implement an interface) → use pointers; (4) Value semantics, immutable objects → use value nesting.
Q What are the restrictions on map keys?
A Keys must be comparable types—bool, int, float, or string—or structs containing these types. They cannot be slices, maps, or func.
Q What happens if the struct tag format is incorrect?
A The code will compile, but libraries such as JSON serialization will not be able to recognize it. Common errors include: misspelling the tag name (e.g., an extra space in json:"name") or using the wrong type of quotes (double quotes must be used).
Q Why is the iteration over a map random?
A This is by design in Go—to prevent programmers from relying on a specific order. If you need a consistent order, first use a slice to collect all the keys, then sort the keys, and finally look up the map using the sorted keys.
Q Is a map thread-safe?
A No! If multiple goroutines read from and write to the same map simultaneously, a panic will occur ("concurrent map read and map write"). In concurrent scenarios, use sync.Map (Lesson 16) or add a mutex (Lesson 16).

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Use map[string]int to count the number of times each word appears in a piece of text. Given the input "the quick brown fox jumps over the lazy dog the", the output should be map[the:3 quick:1 brown:1 ...].

  2. Advanced Problem (Difficulty ⭐⭐): Define a Student struct (Name string, Scores []int) and implement the following methods: (1) Average() float64 to calculate the average score; (2) Grade() string to return a grade (A/B/C/D/F); (3) Test with at least 3 students.

  3. Challenge Problem (Difficulty ⭐⭐⭐): Implement a contact list app: Store contacts using map[string]Contact (where Contact includes Name, Phone, Email, and Group); implement (1) add, delete, and search; (2) filtering by group (family/friends/work); (3) exporting to a JSON file (using os.WriteFile). Requirements: complete error handling + JSON tags + at least 10 test contacts.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏