# How Gin Handles Route Matching with Path Parameters: A Deep Dive into the Radix Tree

> Discover how Gin handles route matching with path parameters using its efficient radix tree. Learn how wildcards `/:id` are processed after static segments fail.

- Repository: [Gin-Gonic/gin](https://github.com/gin-gonic/gin)
- Tags: deep-dive
- Published: 2026-02-27

---

**Gin uses a radix-tree (compressed trie) data structure where path parameters like `/:id` are stored as special wildcard nodes that are evaluated only after static path segments fail to match.**

The `gin-gonic/gin` HTTP framework implements one of the fastest request routers in Go by leveraging a radix-tree for each HTTP method. When you register routes containing path parameters (segments starting with `:`) or catch-all wildcards (`*`), the engine creates specialized tree nodes that capture dynamic values while maintaining O(n) lookup performance, where n is the length of the path.

## Route Registration and Tree Structure

When you call `router.GET("/users/:id", handler)`, the framework does more than store a string mapping. It parses the path into a hierarchical structure in [`tree.go`](https://github.com/gin-gonic/gin/blob/main/tree.go), distinguishing between static segments and dynamic parameters.

### Static vs. Parameterized Nodes

The tree distinguishes node types through the `nType` field. Static segments use `nType = static`, while path parameters use `nType = param`. In [`tree.go`](https://github.com/gin-gonic/gin/blob/main/tree.go), the `node` struct tracks whether a node has wildcard children via the `wildChild` boolean flag. When `wildChild` is true, the node's last child is always a wildcard (parameter or catch-all), ensuring it acts as a fallback matcher.

### The insertChild Algorithm

The route registration flows through `Engine.addRoute` to `node.insertChild` (lines 306-338 in [`tree.go`](https://github.com/gin-gonic/gin/blob/main/tree.go)). This method scans the path for the first `:` or `*` character using `findWildcard`. When encountered, it splits the path:

1. The static prefix becomes a regular child node
2. The wildcard segment (e.g., `":id"`) becomes a param node with `nType = param`
3. The param node is marked `wildChild = true` and attached as the **last child** of its parent

This ordering is critical: because the wildcard is always the final child, static routes like `/users/me` are attempted before parameterized routes like `/users/:id`.

```go
// tree.go – insertChild handling of a param (lines ~306-338)
if wildcard[0] == ':' { // param
    child := &node{
        nType:    param,
        path:     wildcard,
        fullPath: fullPath,
    }
    // ... attached as wildChild (last child) ...
}

```

## Request Dispatch and Parameter Extraction

When a request arrives, Gin walks the tree using `node.getValue` (lines 486-525 in [`tree.go`](https://github.com/gin-gonic/gin/blob/main/tree.go)), collecting parameters into a `Params` slice stored in the `Context`.

### The getValue Method

The lookup algorithm processes the request path segment by segment. When it encounters a node with `wildChild = true`, it knows the final child is a wildcard and switches to parameter extraction mode:

1. For param nodes (`:`), it reads characters until the next `/` or end of string
2. It stores the extracted value in the `Params` slice using the node's path (minus the `:` prefix) as the key
3. It continues traversal with the remaining path segment

```go
// tree.go – extracting a param value (lines ~486-525)
case param:
    // Find param end (either '/' or path end)
    end := 0
    for end < len(path) && path[end] != '/' {
        end++
    }
    // Save param value
    (*value.params)[i] = Param{
        Key:   n.path[1:],   // "id" (removes the colon)
        Value: path[:end],   // actual segment from the URL
    }

```

### Priority Rules: Why Static Routes Win

Gin's matching follows a strict precedence order that prevents parameter shadowing:

1. **Static children** are checked first via the `indices` byte array (fast byte-to-node lookup)
2. **Wildcard child** is evaluated only if no static match exists
3. Because wildcards are always the last child, the route `/users/me` (static) takes priority over `/users/:id` (parameter), even if registered later.

## Accessing Path Parameters in Handlers

After successful route matching, parameters are accessible through the `Context` struct defined in [`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go). The framework provides both keyed lookup and slice iteration.

### Retrieving Individual Parameters

Use `c.Param("key")` to fetch a specific value. This method searches the `Params` slice for a matching key:

```go
r := gin.Default()

r.GET("/users/:id", func(c *gin.Context) {
    // Retrieves the value captured in the URL segment
    userID := c.Param("id")
    c.JSON(200, gin.H{"user_id": userID})
})

```

### Iterating Multiple Parameters

For routes with multiple parameters, you can access the underlying slice directly:

```go
r.GET("/files/:folder/:file", func(c *gin.Context) {
    // Params maintain registration order
    for _, p := range c.Params {
        fmt.Printf("%s = %s\n", p.Key, p.Value)
    }
    
    // Or access by key:
    folder := c.Param("folder")
    filename := c.Param("file")
})

```

### Static Route Precedence Example

The following demonstrates that static routes shadow parameterized ones:

```go
r.GET("/users/me", func(c *gin.Context) {
    c.String(200, "Current user profile")
})

// This handler will NOT execute for "/users/me"
// because the static route matches first
r.GET("/users/:id", func(c *gin.Context) {
    c.String(200, "User %s", c.Param("id"))
})

```

## Special Cases and Edge Handling

### Catch-All Parameters

Beyond single-segment parameters (`:`), Gin supports catch-all wildcards (`*`) that match remaining path segments. These are handled similarly to param nodes but consume the entire remaining path, including slashes.

### Trailing Slash Redirection

The tree logic in `getValue` tracks whether a route exists with an extra trailing slash (`tsr` flag). If a request misses a slash, Gin can issue a 301 redirect while preserving any extracted parameters:

```go
r.GET("/articles/:id", handler) // registered without trailing slash

// Request to "/articles/42/" triggers redirect to "/articles/42"
// with the "42" parameter intact

```

## Summary

- **Gin implements route matching via a radix-tree** where each HTTP method maintains a separate tree root in [`tree.go`](https://github.com/gin-gonic/gin/blob/main/tree.go).
- **Path parameters create special nodes** with `nType = param` and `wildChild = true`, stored as the last child to ensure static routes match first.
- **Parameter extraction** occurs in `getValue` (lines 486-525), which scans until the next `/` and populates the `Params` slice with key-value pairs.
- **Static segments take precedence** over dynamic ones because wildcard nodes are always evaluated last during tree traversal.
- **Access values** via `c.Param("key")` or iterate `c.Params` directly, as implemented in [`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go).

## Frequently Asked Questions

### What data structure does Gin use for route matching?

Gin uses a **radix-tree** (compressed trie), with a separate tree instance for each HTTP method (GET, POST, etc.). This structure provides O(n) path lookup time where n is the length of the request path, and efficiently handles both static and dynamic segments through specialized node types in [`tree.go`](https://github.com/gin-gonic/gin/blob/main/tree.go).

### How does Gin prioritize static routes over parameterized routes?

During route registration in `insertChild`, Gin attaches wildcard nodes (parameters) as the **last child** of their parent node and sets `wildChild = true`. During lookup in `getValue`, the algorithm checks all static children first using the `indices` array before falling back to the wildcard child, ensuring exact matches like `/users/me` win over `/users/:id`.

### Can I register multiple path parameters in a single route?

Yes. You can define multiple parameters such as `/users/:user_id/posts/:post_id`. Each colon-prefixed segment creates a separate param node in the radix-tree. During request handling, both values are extracted sequentially and stored in the `Params` slice, accessible via `c.Param("user_id")` and `c.Param("post_id")` respectively.

### What happens if a request URL matches a parameterized route but is missing a trailing slash?

Gin's tree logic detects this scenario using the **trailing slash redirect** (`tsr`) flag. If a route is registered as `/articles/:id` but the request is `/articles/42/`, Gin returns a 301 redirect to `/articles/42` while preserving the captured parameter value "42".