diff --git a/.travis.yml b/.travis.yml
index ffacfb5..559c92e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,6 +9,10 @@ go:
   - 1.12.x
   - 1.13.x
   - master
+matrix:
+  allow_failures:
+    - go: master
+  fast_finish: true
 before_install:
   - go get github.com/mattn/goveralls
 script:
diff --git a/README.md b/README.md
index d1e68b5..29d35db 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,7 @@ router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Requ
     if r.Header.Get("Access-Control-Request-Method") != "" {
         // Set CORS headers
         header := w.Header()
-        header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow"))
+        header.Set("Access-Control-Allow-Methods", header.Get("Allow"))
         header.Set("Access-Control-Allow-Origin", "*")
     }
 
diff --git a/debian/changelog b/debian/changelog
index 5d6cf09..764f622 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-julienschmidt-httprouter (1.3.0+git20200921.1.fe77dd0-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 09 Apr 2021 22:02:03 -0000
+
 golang-github-julienschmidt-httprouter (1.3.0-1) unstable; urgency=medium
 
   * Team upload.
diff --git a/path.go b/path.go
index 0331c7e..fb6c520 100644
--- a/path.go
+++ b/path.go
@@ -19,13 +19,18 @@ package httprouter
 //
 // If the result of this process is an empty string, "/" is returned
 func CleanPath(p string) string {
+	const stackBufSize = 128
+
 	// Turn empty string into "/"
 	if p == "" {
 		return "/"
 	}
 
+	// Reasonably sized buffer on stack to avoid allocations in the common case.
+	// If a larger buffer is required, it gets allocated dynamically.
+	buf := make([]byte, 0, stackBufSize)
+
 	n := len(p)
-	var buf []byte
 
 	// Invariants:
 	//      reading from path; r is index of next byte to process.
@@ -37,15 +42,21 @@ func CleanPath(p string) string {
 
 	if p[0] != '/' {
 		r = 0
-		buf = make([]byte, n+1)
+
+		if n+1 > stackBufSize {
+			buf = make([]byte, n+1)
+		} else {
+			buf = buf[:n+1]
+		}
 		buf[0] = '/'
 	}
 
 	trailing := n > 1 && p[n-1] == '/'
 
 	// A bit more clunky without a 'lazybuf' like the path package, but the loop
-	// gets completely inlined (bufApp). So in contrast to the path package this
-	// loop has no expensive function calls (except 1x make)
+	// gets completely inlined (bufApp calls).
+	// So in contrast to the path package this loop has no expensive function
+	// calls (except make, if needed).
 
 	for r < n {
 		switch {
@@ -69,7 +80,7 @@ func CleanPath(p string) string {
 				// can backtrack
 				w--
 
-				if buf == nil {
+				if len(buf) == 0 {
 					for w > 1 && p[w] != '/' {
 						w--
 					}
@@ -81,14 +92,14 @@ func CleanPath(p string) string {
 			}
 
 		default:
-			// real path element.
-			// add slash if needed
+			// Real path element.
+			// Add slash if needed
 			if w > 1 {
 				bufApp(&buf, p, w, '/')
 				w++
 			}
 
-			// copy element
+			// Copy element
 			for r < n && p[r] != '/' {
 				bufApp(&buf, p, w, p[r])
 				w++
@@ -97,27 +108,43 @@ func CleanPath(p string) string {
 		}
 	}
 
-	// re-append trailing slash
+	// Re-append trailing slash
 	if trailing && w > 1 {
 		bufApp(&buf, p, w, '/')
 		w++
 	}
 
-	if buf == nil {
+	// If the original string was not modified (or only shortened at the end),
+	// return the respective substring of the original string.
+	// Otherwise return a new string from the buffer.
+	if len(buf) == 0 {
 		return p[:w]
 	}
 	return string(buf[:w])
 }
 
-// internal helper to lazily create a buffer if necessary
+// Internal helper to lazily create a buffer if necessary.
+// Calls to this function get inlined.
 func bufApp(buf *[]byte, s string, w int, c byte) {
-	if *buf == nil {
+	b := *buf
+	if len(b) == 0 {
+		// No modification of the original string so far.
+		// If the next character is the same as in the original string, we do
+		// not yet have to allocate a buffer.
 		if s[w] == c {
 			return
 		}
 
-		*buf = make([]byte, len(s))
-		copy(*buf, s[:w])
+		// Otherwise use either the stack buffer, if it is large enough, or
+		// allocate a new buffer on the heap, and copy all previous characters.
+		if l := len(s); l > cap(b) {
+			*buf = make([]byte, len(s))
+		} else {
+			*buf = (*buf)[:l]
+		}
+		b = *buf
+
+		copy(b, s[:w])
 	}
-	(*buf)[w] = c
+	b[w] = c
 }
diff --git a/path_test.go b/path_test.go
index 75fde32..cfadd32 100644
--- a/path_test.go
+++ b/path_test.go
@@ -6,13 +6,15 @@
 package httprouter
 
 import (
-	"runtime"
+	"strings"
 	"testing"
 )
 
-var cleanTests = []struct {
+type cleanPathTest struct {
 	path, result string
-}{
+}
+
+var cleanTests = []cleanPathTest{
 	// Already clean
 	{"/", "/"},
 	{"/abc", "/abc"},
@@ -79,15 +81,69 @@ func TestPathCleanMallocs(t *testing.T) {
 	if testing.Short() {
 		t.Skip("skipping malloc count in short mode")
 	}
-	if runtime.GOMAXPROCS(0) > 1 {
-		t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
-		return
-	}
 
 	for _, test := range cleanTests {
+		test := test
 		allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) })
 		if allocs > 0 {
 			t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs)
 		}
 	}
 }
+
+func BenchmarkPathClean(b *testing.B) {
+	b.ReportAllocs()
+
+	for i := 0; i < b.N; i++ {
+		for _, test := range cleanTests {
+			CleanPath(test.path)
+		}
+	}
+}
+
+func genLongPaths() (testPaths []cleanPathTest) {
+	for i := 1; i <= 1234; i++ {
+		ss := strings.Repeat("a", i)
+
+		correctPath := "/" + ss
+		testPaths = append(testPaths, cleanPathTest{
+			path:   correctPath,
+			result: correctPath,
+		}, cleanPathTest{
+			path:   ss,
+			result: correctPath,
+		}, cleanPathTest{
+			path:   "//" + ss,
+			result: correctPath,
+		}, cleanPathTest{
+			path:   "/" + ss + "/b/..",
+			result: correctPath,
+		})
+	}
+	return testPaths
+}
+
+func TestPathCleanLong(t *testing.T) {
+	cleanTests := genLongPaths()
+
+	for _, test := range cleanTests {
+		if s := CleanPath(test.path); s != test.result {
+			t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result)
+		}
+		if s := CleanPath(test.result); s != test.result {
+			t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result)
+		}
+	}
+}
+
+func BenchmarkPathCleanLong(b *testing.B) {
+	cleanTests := genLongPaths()
+	b.ResetTimer()
+	b.ReportAllocs()
+
+	for i := 0; i < b.N; i++ {
+		for _, test := range cleanTests {
+			CleanPath(test.path)
+		}
+	}
+}
diff --git a/router.go b/router.go
index 599529d..1eab403 100644
--- a/router.go
+++ b/router.go
@@ -34,7 +34,7 @@
 // The router matches incoming requests by the request method and the path.
 // If a handle is registered for this path and method, the router delegates the
 // request to that function.
-// For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to
+// For the methods GET, POST, PUT, PATCH, DELETE and OPTIONS shortcut functions exist to
 // register handles, for all other methods router.Handle can be used.
 //
 // The registered path, against which the router matches incoming requests, can
@@ -80,11 +80,12 @@ import (
 	"context"
 	"net/http"
 	"strings"
+	"sync"
 )
 
 // Handle is a function that can be registered to a route to handle HTTP
 // requests. Like http.HandlerFunc, but has a third parameter for the values of
-// wildcards (variables).
+// wildcards (path variables).
 type Handle func(http.ResponseWriter, *http.Request, Params)
 
 // Param is a single URL parameter, consisting of a key and a value.
@@ -101,9 +102,9 @@ type Params []Param
 // ByName returns the value of the first Param which key matches the given name.
 // If no matching Param is found, an empty string is returned.
 func (ps Params) ByName(name string) string {
-	for i := range ps {
-		if ps[i].Key == name {
-			return ps[i].Value
+	for _, p := range ps {
+		if p.Key == name {
+			return p.Value
 		}
 	}
 	return ""
@@ -121,16 +122,36 @@ func ParamsFromContext(ctx context.Context) Params {
 	return p
 }
 
+// MatchedRoutePathParam is the Param name under which the path of the matched
+// route is stored, if Router.SaveMatchedRoutePath is set.
+var MatchedRoutePathParam = "$matchedRoutePath"
+
+// MatchedRoutePath retrieves the path of the matched route.
+// Router.SaveMatchedRoutePath must have been enabled when the respective
+// handler was added, otherwise this function always returns an empty string.
+func (ps Params) MatchedRoutePath() string {
+	return ps.ByName(MatchedRoutePathParam)
+}
+
 // Router is a http.Handler which can be used to dispatch requests to different
 // handler functions via configurable routes
 type Router struct {
 	trees map[string]*node
 
+	paramsPool sync.Pool
+	maxParams  uint16
+
+	// If enabled, adds the matched route path onto the http.Request context
+	// before invoking the handler.
+	// The matched route path is only added to handlers of routes that were
+	// registered when this option was enabled.
+	SaveMatchedRoutePath bool
+
 	// Enables automatic redirection if the current route can't be matched but a
 	// handler for the path with (without) the trailing slash exists.
 	// For example if /foo/ is requested but a route only exists for /foo, the
 	// client is redirected to /foo with http status code 301 for GET requests
-	// and 307 for all other request methods.
+	// and 308 for all other request methods.
 	RedirectTrailingSlash bool
 
 	// If enabled, the router tries to fix the current request path, if no
@@ -138,7 +159,7 @@ type Router struct {
 	// First superfluous path elements like ../ or // are removed.
 	// Afterwards the router does a case-insensitive lookup of the cleaned path.
 	// If a handle can be found for this route, the router makes a redirection
-	// to the corrected path with status code 301 for GET requests and 307 for
+	// to the corrected path with status code 301 for GET requests and 308 for
 	// all other request methods.
 	// For example /FOO and /..//Foo could be redirected to /foo.
 	// RedirectTrailingSlash is independent of this option.
@@ -198,6 +219,33 @@ func New() *Router {
 	}
 }
 
+func (r *Router) getParams() *Params {
+	ps, _ := r.paramsPool.Get().(*Params)
+	*ps = (*ps)[0:0] // reset slice
+	return ps
+}
+
+func (r *Router) putParams(ps *Params) {
+	if ps != nil {
+		r.paramsPool.Put(ps)
+	}
+}
+
+func (r *Router) saveMatchedRoutePath(path string, handle Handle) Handle {
+	return func(w http.ResponseWriter, req *http.Request, ps Params) {
+		if ps == nil {
+			psp := r.getParams()
+			ps = (*psp)[0:1]
+			ps[0] = Param{Key: MatchedRoutePathParam, Value: path}
+			handle(w, req, ps)
+			r.putParams(psp)
+		} else {
+			ps = append(ps, Param{Key: MatchedRoutePathParam, Value: path})
+			handle(w, req, ps)
+		}
+	}
+}
+
 // GET is a shortcut for router.Handle(http.MethodGet, path, handle)
 func (r *Router) GET(path string, handle Handle) {
 	r.Handle(http.MethodGet, path, handle)
@@ -242,9 +290,22 @@ func (r *Router) DELETE(path string, handle Handle) {
 // frequently used, non-standardized or custom methods (e.g. for internal
 // communication with a proxy).
 func (r *Router) Handle(method, path string, handle Handle) {
+	varsCount := uint16(0)
+
+	if method == "" {
+		panic("method must not be empty")
+	}
 	if len(path) < 1 || path[0] != '/' {
 		panic("path must begin with '/' in path '" + path + "'")
 	}
+	if handle == nil {
+		panic("handle must not be nil")
+	}
+
+	if r.SaveMatchedRoutePath {
+		varsCount++
+		handle = r.saveMatchedRoutePath(path, handle)
+	}
 
 	if r.trees == nil {
 		r.trees = make(map[string]*node)
@@ -259,6 +320,19 @@ func (r *Router) Handle(method, path string, handle Handle) {
 	}
 
 	root.addRoute(path, handle)
+
+	// Update maxParams
+	if paramsCount := countParams(path); paramsCount+varsCount > r.maxParams {
+		r.maxParams = paramsCount + varsCount
+	}
+
+	// Lazy-init paramsPool alloc func
+	if r.paramsPool.New == nil && r.maxParams > 0 {
+		r.paramsPool.New = func() interface{} {
+			ps := make(Params, 0, r.maxParams)
+			return &ps
+		}
+	}
 }
 
 // Handler is an adapter which allows the usage of an http.Handler as a
@@ -319,7 +393,15 @@ func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
 // the same path with an extra / without the trailing slash should be performed.
 func (r *Router) Lookup(method, path string) (Handle, Params, bool) {
 	if root := r.trees[method]; root != nil {
-		return root.getValue(path)
+		handle, ps, tsr := root.getValue(path, r.getParams)
+		if handle == nil {
+			r.putParams(ps)
+			return nil, nil, tsr
+		}
+		if ps == nil {
+			return handle, nil, tsr
+		}
+		return handle, *ps, tsr
 	}
 	return nil, nil, false
 }
@@ -347,7 +429,7 @@ func (r *Router) allowed(path, reqMethod string) (allow string) {
 				continue
 			}
 
-			handle, _, _ := r.trees[method].getValue(path)
+			handle, _, _ := r.trees[method].getValue(path, nil)
 			if handle != nil {
 				// Add request method to list of allowed methods
 				allowed = append(allowed, method)
@@ -371,7 +453,8 @@ func (r *Router) allowed(path, reqMethod string) (allow string) {
 		// return as comma separated list
 		return strings.Join(allowed, ", ")
 	}
-	return
+
+	return allow
 }
 
 // ServeHTTP makes the router implement the http.Handler interface.
@@ -383,15 +466,20 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	path := req.URL.Path
 
 	if root := r.trees[req.Method]; root != nil {
-		if handle, ps, tsr := root.getValue(path); handle != nil {
-			handle(w, req, ps)
+		if handle, ps, tsr := root.getValue(path, r.getParams); handle != nil {
+			if ps != nil {
+				handle(w, req, *ps)
+				r.putParams(ps)
+			} else {
+				handle(w, req, nil)
+			}
 			return
 		} else if req.Method != http.MethodConnect && path != "/" {
-			code := 301 // Permanent redirect, request with GET method
+			// Moved Permanently, request with GET method
+			code := http.StatusMovedPermanently
 			if req.Method != http.MethodGet {
-				// Temporary redirect, request with same method
-				// As of Go 1.3, Go does not support status code 308.
-				code = 307
+				// Permanent Redirect, request with same method
+				code = http.StatusPermanentRedirect
 			}
 
 			if tsr && r.RedirectTrailingSlash {
@@ -411,7 +499,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 					r.RedirectTrailingSlash,
 				)
 				if found {
-					req.URL.Path = string(fixedPath)
+					req.URL.Path = fixedPath
 					http.Redirect(w, req, req.URL.String(), code)
 					return
 				}
diff --git a/router_test.go b/router_test.go
index 37b140b..ae7d243 100644
--- a/router_test.go
+++ b/router_test.go
@@ -164,14 +164,38 @@ func TestRouterAPI(t *testing.T) {
 	}
 }
 
-func TestRouterRoot(t *testing.T) {
+func TestRouterInvalidInput(t *testing.T) {
 	router := New()
+
+	handle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
+
 	recv := catchPanic(func() {
-		router.GET("noSlashRoot", nil)
+		router.Handle("", "/", handle)
+	})
+	if recv == nil {
+		t.Fatal("registering empty method did not panic")
+	}
+
+	recv = catchPanic(func() {
+		router.GET("", handle)
+	})
+	if recv == nil {
+		t.Fatal("registering empty path did not panic")
+	}
+
+	recv = catchPanic(func() {
+		router.GET("noSlashRoot", handle)
 	})
 	if recv == nil {
 		t.Fatal("registering path not beginning with '/' did not panic")
 	}
+
+	recv = catchPanic(func() {
+		router.GET("/", nil)
+	})
+	if recv == nil {
+		t.Fatal("registering nil handler did not panic")
+	}
 }
 
 func TestRouterChaining(t *testing.T) {
@@ -395,21 +419,21 @@ func TestRouterNotFound(t *testing.T) {
 		code     int
 		location string
 	}{
-		{"/path/", 301, "/path"},   // TSR -/
-		{"/dir", 301, "/dir/"},     // TSR +/
-		{"", 301, "/"},             // TSR +/
-		{"/PATH", 301, "/path"},    // Fixed Case
-		{"/DIR/", 301, "/dir/"},    // Fixed Case
-		{"/PATH/", 301, "/path"},   // Fixed Case -/
-		{"/DIR", 301, "/dir/"},     // Fixed Case +/
-		{"/../path", 301, "/path"}, // CleanPath
-		{"/nope", 404, ""},         // NotFound
+		{"/path/", http.StatusMovedPermanently, "/path"},   // TSR -/
+		{"/dir", http.StatusMovedPermanently, "/dir/"},     // TSR +/
+		{"", http.StatusMovedPermanently, "/"},             // TSR +/
+		{"/PATH", http.StatusMovedPermanently, "/path"},    // Fixed Case
+		{"/DIR/", http.StatusMovedPermanently, "/dir/"},    // Fixed Case
+		{"/PATH/", http.StatusMovedPermanently, "/path"},   // Fixed Case -/
+		{"/DIR", http.StatusMovedPermanently, "/dir/"},     // Fixed Case +/
+		{"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath
+		{"/nope", http.StatusNotFound, ""},                 // NotFound
 	}
 	for _, tr := range testRoutes {
 		r, _ := http.NewRequest(http.MethodGet, tr.route, nil)
 		w := httptest.NewRecorder()
 		router.ServeHTTP(w, r)
-		if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header().Get("Location")) == tr.location)) {
+		if !(w.Code == tr.code && (w.Code == http.StatusNotFound || fmt.Sprint(w.Header().Get("Location")) == tr.location)) {
 			t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header().Get("Location"))
 		}
 	}
@@ -417,22 +441,22 @@ func TestRouterNotFound(t *testing.T) {
 	// Test custom not found handler
 	var notFound bool
 	router.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-		rw.WriteHeader(404)
+		rw.WriteHeader(http.StatusNotFound)
 		notFound = true
 	})
 	r, _ := http.NewRequest(http.MethodGet, "/nope", nil)
 	w := httptest.NewRecorder()
 	router.ServeHTTP(w, r)
-	if !(w.Code == 404 && notFound == true) {
+	if !(w.Code == http.StatusNotFound && notFound == true) {
 		t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
 	}
 
-	// Test other method than GET (want 307 instead of 301)
+	// Test other method than GET (want 308 instead of 301)
 	router.PATCH("/path", handlerFunc)
 	r, _ = http.NewRequest(http.MethodPatch, "/path/", nil)
 	w = httptest.NewRecorder()
 	router.ServeHTTP(w, r)
-	if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
+	if !(w.Code == http.StatusPermanentRedirect && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
 		t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
 	}
 
@@ -442,7 +466,7 @@ func TestRouterNotFound(t *testing.T) {
 	r, _ = http.NewRequest(http.MethodGet, "/", nil)
 	w = httptest.NewRecorder()
 	router.ServeHTTP(w, r)
-	if !(w.Code == 404) {
+	if !(w.Code == http.StatusNotFound) {
 		t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
 	}
 }
@@ -495,7 +519,6 @@ func TestRouterLookup(t *testing.T) {
 
 	// insert route and try again
 	router.GET("/user/:name", wantHandle)
-
 	handle, params, _ := router.Lookup(http.MethodGet, "/user/gopher")
 	if handle == nil {
 		t.Fatal("Got no handle!")
@@ -505,10 +528,25 @@ func TestRouterLookup(t *testing.T) {
 			t.Fatal("Routing failed!")
 		}
 	}
-
 	if !reflect.DeepEqual(params, wantParams) {
 		t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
 	}
+	routed = false
+
+	// route without param
+	router.GET("/user", wantHandle)
+	handle, params, _ = router.Lookup(http.MethodGet, "/user")
+	if handle == nil {
+		t.Fatal("Got no handle!")
+	} else {
+		handle(nil, nil, nil)
+		if !routed {
+			t.Fatal("Routing failed!")
+		}
+	}
+	if params != nil {
+		t.Fatalf("Wrong parameter values: want %v, got %v", nil, params)
+	}
 
 	handle, _, tsr = router.Lookup(http.MethodGet, "/user/gopher/")
 	if handle != nil {
@@ -572,6 +610,65 @@ func TestRouterParamsFromContext(t *testing.T) {
 	}
 }
 
+func TestRouterMatchedRoutePath(t *testing.T) {
+	route1 := "/user/:name"
+	routed1 := false
+	handle1 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
+		route := ps.MatchedRoutePath()
+		if route != route1 {
+			t.Fatalf("Wrong matched route: want %s, got %s", route1, route)
+		}
+		routed1 = true
+	}
+
+	route2 := "/user/:name/details"
+	routed2 := false
+	handle2 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
+		route := ps.MatchedRoutePath()
+		if route != route2 {
+			t.Fatalf("Wrong matched route: want %s, got %s", route2, route)
+		}
+		routed2 = true
+	}
+
+	route3 := "/"
+	routed3 := false
+	handle3 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
+		route := ps.MatchedRoutePath()
+		if route != route3 {
+			t.Fatalf("Wrong matched route: want %s, got %s", route3, route)
+		}
+		routed3 = true
+	}
+
+	router := New()
+	router.SaveMatchedRoutePath = true
+	router.Handle(http.MethodGet, route1, handle1)
+	router.Handle(http.MethodGet, route2, handle2)
+	router.Handle(http.MethodGet, route3, handle3)
+
+	w := new(mockResponseWriter)
+	r, _ := http.NewRequest(http.MethodGet, "/user/gopher", nil)
+	router.ServeHTTP(w, r)
+	if !routed1 || routed2 || routed3 {
+		t.Fatal("Routing failed!")
+	}
+
+	w = new(mockResponseWriter)
+	r, _ = http.NewRequest(http.MethodGet, "/user/gopher/details", nil)
+	router.ServeHTTP(w, r)
+	if !routed2 || routed3 {
+		t.Fatal("Routing failed!")
+	}
+
+	w = new(mockResponseWriter)
+	r, _ = http.NewRequest(http.MethodGet, "/", nil)
+	router.ServeHTTP(w, r)
+	if !routed3 {
+		t.Fatal("Routing failed!")
+	}
+}
+
 type mockFileSystem struct {
 	opened bool
 }
diff --git a/tree.go b/tree.go
index c9fdf5b..6eb4fe6 100644
--- a/tree.go
+++ b/tree.go
@@ -17,21 +17,49 @@ func min(a, b int) int {
 	return b
 }
 
-const maxParamCount uint8 = ^uint8(0)
+func longestCommonPrefix(a, b string) int {
+	i := 0
+	max := min(len(a), len(b))
+	for i < max && a[i] == b[i] {
+		i++
+	}
+	return i
+}
 
-func countParams(path string) uint8 {
-	var n uint
-	for i := 0; i < len(path); i++ {
-		if path[i] != ':' && path[i] != '*' {
+// Search for a wildcard segment and check the name for invalid characters.
+// Returns -1 as index, if no wildcard was found.
+func findWildcard(path string) (wilcard string, i int, valid bool) {
+	// Find start
+	for start, c := range []byte(path) {
+		// A wildcard starts with ':' (param) or '*' (catch-all)
+		if c != ':' && c != '*' {
 			continue
 		}
-		n++
-	}
-	if n >= uint(maxParamCount) {
-		return maxParamCount
+
+		// Find end and check for invalid characters
+		valid = true
+		for end, c := range []byte(path[start+1:]) {
+			switch c {
+			case '/':
+				return path[start : start+1+end], start, valid
+			case ':', '*':
+				valid = false
+			}
+		}
+		return path[start:], start, valid
 	}
+	return "", -1, false
+}
 
-	return uint8(n)
+func countParams(path string) uint16 {
+	var n uint
+	for i := range []byte(path) {
+		switch path[i] {
+		case ':', '*':
+			n++
+		}
+	}
+	return uint16(n)
 }
 
 type nodeType uint8
@@ -45,34 +73,32 @@ const (
 
 type node struct {
 	path      string
+	indices   string
 	wildChild bool
 	nType     nodeType
-	maxParams uint8
 	priority  uint32
-	indices   string
 	children  []*node
 	handle    Handle
 }
 
-// increments priority of the given child and reorders if necessary
+// Increments priority of the given child and reorders if necessary
 func (n *node) incrementChildPrio(pos int) int {
-	n.children[pos].priority++
-	prio := n.children[pos].priority
+	cs := n.children
+	cs[pos].priority++
+	prio := cs[pos].priority
 
-	// adjust position (move to front)
+	// Adjust position (move to front)
 	newPos := pos
-	for newPos > 0 && n.children[newPos-1].priority < prio {
-		// swap node positions
-		n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
-
-		newPos--
+	for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
+		// Swap node positions
+		cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
 	}
 
-	// build new index char string
+	// Build new index char string
 	if newPos != pos {
-		n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
-			n.indices[pos:pos+1] + // the index char we move
-			n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
+		n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
+			n.indices[pos:pos+1] + // The index char we move
+			n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
 	}
 
 	return newPos
@@ -83,251 +109,212 @@ func (n *node) incrementChildPrio(pos int) int {
 func (n *node) addRoute(path string, handle Handle) {
 	fullPath := path
 	n.priority++
-	numParams := countParams(path)
-
-	// non-empty tree
-	if len(n.path) > 0 || len(n.children) > 0 {
-	walk:
-		for {
-			// Update maxParams of the current node
-			if numParams > n.maxParams {
-				n.maxParams = numParams
-			}
-
-			// Find the longest common prefix.
-			// This also implies that the common prefix contains no ':' or '*'
-			// since the existing key can't contain those chars.
-			i := 0
-			max := min(len(path), len(n.path))
-			for i < max && path[i] == n.path[i] {
-				i++
-			}
-
-			// Split edge
-			if i < len(n.path) {
-				child := node{
-					path:      n.path[i:],
-					wildChild: n.wildChild,
-					nType:     static,
-					indices:   n.indices,
-					children:  n.children,
-					handle:    n.handle,
-					priority:  n.priority - 1,
-				}
 
-				// Update maxParams (max of all children)
-				for i := range child.children {
-					if child.children[i].maxParams > child.maxParams {
-						child.maxParams = child.children[i].maxParams
-					}
-				}
+	// Empty tree
+	if n.path == "" && n.indices == "" {
+		n.insertChild(path, fullPath, handle)
+		n.nType = root
+		return
+	}
 
-				n.children = []*node{&child}
-				// []byte for proper unicode char conversion, see #65
-				n.indices = string([]byte{n.path[i]})
-				n.path = path[:i]
-				n.handle = nil
-				n.wildChild = false
+walk:
+	for {
+		// Find the longest common prefix.
+		// This also implies that the common prefix contains no ':' or '*'
+		// since the existing key can't contain those chars.
+		i := longestCommonPrefix(path, n.path)
+
+		// Split edge
+		if i < len(n.path) {
+			child := node{
+				path:      n.path[i:],
+				wildChild: n.wildChild,
+				nType:     static,
+				indices:   n.indices,
+				children:  n.children,
+				handle:    n.handle,
+				priority:  n.priority - 1,
 			}
 
-			// Make new node a child of this node
-			if i < len(path) {
-				path = path[i:]
+			n.children = []*node{&child}
+			// []byte for proper unicode char conversion, see #65
+			n.indices = string([]byte{n.path[i]})
+			n.path = path[:i]
+			n.handle = nil
+			n.wildChild = false
+		}
 
-				if n.wildChild {
-					n = n.children[0]
-					n.priority++
+		// Make new node a child of this node
+		if i < len(path) {
+			path = path[i:]
 
-					// Update maxParams of the child node
-					if numParams > n.maxParams {
-						n.maxParams = numParams
-					}
-					numParams--
-
-					// Check if the wildcard matches
-					if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
-						// Adding a child to a catchAll is not possible
-						n.nType != catchAll &&
-						// Check for longer wildcard, e.g. :name and :names
-						(len(n.path) >= len(path) || path[len(n.path)] == '/') {
-						continue walk
-					} else {
-						// Wildcard conflict
-						var pathSeg string
-						if n.nType == catchAll {
-							pathSeg = path
-						} else {
-							pathSeg = strings.SplitN(path, "/", 2)[0]
-						}
-						prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
-						panic("'" + pathSeg +
-							"' in new path '" + fullPath +
-							"' conflicts with existing wildcard '" + n.path +
-							"' in existing prefix '" + prefix +
-							"'")
+			if n.wildChild {
+				n = n.children[0]
+				n.priority++
+
+				// Check if the wildcard matches
+				if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
+					// Adding a child to a catchAll is not possible
+					n.nType != catchAll &&
+					// Check for longer wildcard, e.g. :name and :names
+					(len(n.path) >= len(path) || path[len(n.path)] == '/') {
+					continue walk
+				} else {
+					// Wildcard conflict
+					pathSeg := path
+					if n.nType != catchAll {
+						pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
 					}
+					prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
+					panic("'" + pathSeg +
+						"' in new path '" + fullPath +
+						"' conflicts with existing wildcard '" + n.path +
+						"' in existing prefix '" + prefix +
+						"'")
 				}
+			}
 
-				c := path[0]
-
-				// slash after param
-				if n.nType == param && c == '/' && len(n.children) == 1 {
-					n = n.children[0]
-					n.priority++
-					continue walk
-				}
+			idxc := path[0]
 
-				// Check if a child with the next path byte exists
-				for i := 0; i < len(n.indices); i++ {
-					if c == n.indices[i] {
-						i = n.incrementChildPrio(i)
-						n = n.children[i]
-						continue walk
-					}
-				}
+			// '/' after param
+			if n.nType == param && idxc == '/' && len(n.children) == 1 {
+				n = n.children[0]
+				n.priority++
+				continue walk
+			}
 
-				// Otherwise insert it
-				if c != ':' && c != '*' {
-					// []byte for proper unicode char conversion, see #65
-					n.indices += string([]byte{c})
-					child := &node{
-						maxParams: numParams,
-					}
-					n.children = append(n.children, child)
-					n.incrementChildPrio(len(n.indices) - 1)
-					n = child
+			// Check if a child with the next path byte exists
+			for i, c := range []byte(n.indices) {
+				if c == idxc {
+					i = n.incrementChildPrio(i)
+					n = n.children[i]
+					continue walk
 				}
-				n.insertChild(numParams, path, fullPath, handle)
-				return
+			}
 
-			} else if i == len(path) { // Make node a (in-path) leaf
-				if n.handle != nil {
-					panic("a handle is already registered for path '" + fullPath + "'")
-				}
-				n.handle = handle
+			// Otherwise insert it
+			if idxc != ':' && idxc != '*' {
+				// []byte for proper unicode char conversion, see #65
+				n.indices += string([]byte{idxc})
+				child := &node{}
+				n.children = append(n.children, child)
+				n.incrementChildPrio(len(n.indices) - 1)
+				n = child
 			}
+			n.insertChild(path, fullPath, handle)
 			return
 		}
-	} else { // Empty tree
-		n.insertChild(numParams, path, fullPath, handle)
-		n.nType = root
+
+		// Otherwise add handle to current node
+		if n.handle != nil {
+			panic("a handle is already registered for path '" + fullPath + "'")
+		}
+		n.handle = handle
+		return
 	}
 }
 
-func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) {
-	var offset int // already handled bytes of the path
+func (n *node) insertChild(path, fullPath string, handle Handle) {
+	for {
+		// Find prefix until first wildcard
+		wildcard, i, valid := findWildcard(path)
+		if i < 0 { // No wilcard found
+			break
+		}
 
-	// find prefix until first wildcard (beginning with ':'' or '*'')
-	for i, max := 0, len(path); numParams > 0; i++ {
-		c := path[i]
-		if c != ':' && c != '*' {
-			continue
+		// The wildcard name must not contain ':' and '*'
+		if !valid {
+			panic("only one wildcard per path segment is allowed, has: '" +
+				wildcard + "' in path '" + fullPath + "'")
 		}
 
-		// find wildcard end (either '/' or path end)
-		end := i + 1
-		for end < max && path[end] != '/' {
-			switch path[end] {
-			// the wildcard name must not contain ':' and '*'
-			case ':', '*':
-				panic("only one wildcard per path segment is allowed, has: '" +
-					path[i:] + "' in path '" + fullPath + "'")
-			default:
-				end++
-			}
+		// Check if the wildcard has a name
+		if len(wildcard) < 2 {
+			panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
 		}
 
-		// check if this Node existing children which would be
+		// Check if this node has existing children which would be
 		// unreachable if we insert the wildcard here
 		if len(n.children) > 0 {
-			panic("wildcard route '" + path[i:end] +
+			panic("wildcard segment '" + wildcard +
 				"' conflicts with existing children in path '" + fullPath + "'")
 		}
 
-		// check if the wildcard has a name
-		if end-i < 2 {
-			panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
-		}
-
-		if c == ':' { // param
-			// split path at the beginning of the wildcard
+		// param
+		if wildcard[0] == ':' {
 			if i > 0 {
-				n.path = path[offset:i]
-				offset = i
+				// Insert prefix before the current wildcard
+				n.path = path[:i]
+				path = path[i:]
 			}
 
+			n.wildChild = true
 			child := &node{
-				nType:     param,
-				maxParams: numParams,
+				nType: param,
+				path:  wildcard,
 			}
 			n.children = []*node{child}
-			n.wildChild = true
 			n = child
 			n.priority++
-			numParams--
 
-			// if the path doesn't end with the wildcard, then there
+			// If the path doesn't end with the wildcard, then there
 			// will be another non-wildcard subpath starting with '/'
-			if end < max {
-				n.path = path[offset:end]
-				offset = end
-
+			if len(wildcard) < len(path) {
+				path = path[len(wildcard):]
 				child := &node{
-					maxParams: numParams,
-					priority:  1,
+					priority: 1,
 				}
 				n.children = []*node{child}
 				n = child
+				continue
 			}
 
-		} else { // catchAll
-			if end != max || numParams > 1 {
-				panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
-			}
-
-			if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
-				panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
-			}
+			// Otherwise we're done. Insert the handle in the new leaf
+			n.handle = handle
+			return
+		}
 
-			// currently fixed width 1 for '/'
-			i--
-			if path[i] != '/' {
-				panic("no / before catch-all in path '" + fullPath + "'")
-			}
+		// catchAll
+		if i+len(wildcard) != len(path) {
+			panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
+		}
 
-			n.path = path[offset:i]
+		if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
+			panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
+		}
 
-			// first node: catchAll node with empty path
-			child := &node{
-				wildChild: true,
-				nType:     catchAll,
-				maxParams: 1,
-			}
-			// update maxParams of the parent node
-			if n.maxParams < 1 {
-				n.maxParams = 1
-			}
-			n.children = []*node{child}
-			n.indices = string(path[i])
-			n = child
-			n.priority++
+		// Currently fixed width 1 for '/'
+		i--
+		if path[i] != '/' {
+			panic("no / before catch-all in path '" + fullPath + "'")
+		}
 
-			// second node: node holding the variable
-			child = &node{
-				path:      path[i:],
-				nType:     catchAll,
-				maxParams: 1,
-				handle:    handle,
-				priority:  1,
-			}
-			n.children = []*node{child}
+		n.path = path[:i]
 
-			return
+		// First node: catchAll node with empty path
+		child := &node{
+			wildChild: true,
+			nType:     catchAll,
+		}
+		n.children = []*node{child}
+		n.indices = string('/')
+		n = child
+		n.priority++
+
+		// Second node: node holding the variable
+		child = &node{
+			path:     path[i:],
+			nType:    catchAll,
+			handle:   handle,
+			priority: 1,
 		}
+		n.children = []*node{child}
+
+		return
 	}
 
-	// insert remaining path part and handle to the leaf
-	n.path = path[offset:]
+	// If no wildcard was found, simply insert the path and handle
+	n.path = path
 	n.handle = handle
 }
 
@@ -336,19 +323,21 @@ func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle
 // If no handle can be found, a TSR (trailing slash redirect) recommendation is
 // made if a handle exists with an extra (without the) trailing slash for the
 // given path.
-func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) {
-walk: // outer loop for walking the tree
+func (n *node) getValue(path string, params func() *Params) (handle Handle, ps *Params, tsr bool) {
+walk: // Outer loop for walking the tree
 	for {
-		if len(path) > len(n.path) {
-			if path[:len(n.path)] == n.path {
-				path = path[len(n.path):]
+		prefix := n.path
+		if len(path) > len(prefix) {
+			if path[:len(prefix)] == prefix {
+				path = path[len(prefix):]
+
 				// If this node does not have a wildcard (param or catchAll)
-				// child,  we can just look up the next child node and continue
+				// child, we can just look up the next child node and continue
 				// to walk down the tree
 				if !n.wildChild {
-					c := path[0]
-					for i := 0; i < len(n.indices); i++ {
-						if c == n.indices[i] {
+					idxc := path[0]
+					for i, c := range []byte(n.indices) {
+						if c == idxc {
 							n = n.children[i]
 							continue walk
 						}
@@ -359,30 +348,33 @@ walk: // outer loop for walking the tree
 					// trailing slash if a leaf exists for that path.
 					tsr = (path == "/" && n.handle != nil)
 					return
-
 				}
 
-				// handle wildcard child
+				// Handle wildcard child
 				n = n.children[0]
 				switch n.nType {
 				case param:
-					// find param end (either '/' or path end)
+					// Find param end (either '/' or path end)
 					end := 0
 					for end < len(path) && path[end] != '/' {
 						end++
 					}
 
-					// save param value
-					if p == nil {
-						// lazy allocation
-						p = make(Params, 0, n.maxParams)
+					// Save param value
+					if params != nil {
+						if ps == nil {
+							ps = params()
+						}
+						// Expand slice within preallocated capacity
+						i := len(*ps)
+						*ps = (*ps)[:i+1]
+						(*ps)[i] = Param{
+							Key:   n.path[1:],
+							Value: path[:end],
+						}
 					}
-					i := len(p)
-					p = p[:i+1] // expand slice within preallocated capacity
-					p[i].Key = n.path[1:]
-					p[i].Value = path[:end]
 
-					// we need to go deeper!
+					// We need to go deeper!
 					if end < len(path) {
 						if len(n.children) > 0 {
 							path = path[end:]
@@ -401,21 +393,25 @@ walk: // outer loop for walking the tree
 						// No handle found. Check if a handle for this path + a
 						// trailing slash exists for TSR recommendation
 						n = n.children[0]
-						tsr = (n.path == "/" && n.handle != nil)
+						tsr = (n.path == "/" && n.handle != nil) || (n.path == "" && n.indices == "/")
 					}
 
 					return
 
 				case catchAll:
-					// save param value
-					if p == nil {
-						// lazy allocation
-						p = make(Params, 0, n.maxParams)
+					// Save param value
+					if params != nil {
+						if ps == nil {
+							ps = params()
+						}
+						// Expand slice within preallocated capacity
+						i := len(*ps)
+						*ps = (*ps)[:i+1]
+						(*ps)[i] = Param{
+							Key:   n.path[2:],
+							Value: path,
+						}
 					}
-					i := len(p)
-					p = p[:i+1] // expand slice within preallocated capacity
-					p[i].Key = n.path[2:]
-					p[i].Value = path
 
 					handle = n.handle
 					return
@@ -424,13 +420,16 @@ walk: // outer loop for walking the tree
 					panic("invalid node type")
 				}
 			}
-		} else if path == n.path {
+		} else if path == prefix {
 			// We should have reached the node containing the handle.
 			// Check if this node has a handle registered.
 			if handle = n.handle; handle != nil {
 				return
 			}
 
+			// If there is no handle for this route, but this route has a
+			// wildcard child, there must be a handle for this path with an
+			// additional trailing slash
 			if path == "/" && n.wildChild && n.nType != root {
 				tsr = true
 				return
@@ -438,23 +437,22 @@ walk: // outer loop for walking the tree
 
 			// No handle found. Check if a handle for this path + a
 			// trailing slash exists for trailing slash recommendation
-			for i := 0; i < len(n.indices); i++ {
-				if n.indices[i] == '/' {
+			for i, c := range []byte(n.indices) {
+				if c == '/' {
 					n = n.children[i]
 					tsr = (len(n.path) == 1 && n.handle != nil) ||
 						(n.nType == catchAll && n.children[0].handle != nil)
 					return
 				}
 			}
-
 			return
 		}
 
 		// Nothing found. We can recommend to redirect to the same URL with an
 		// extra trailing slash if a leaf exists for that path
 		tsr = (path == "/") ||
-			(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
-				path == n.path[:len(n.path)-1] && n.handle != nil)
+			(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
+				path == prefix[:len(prefix)-1] && n.handle != nil)
 		return
 	}
 }
@@ -463,16 +461,27 @@ walk: // outer loop for walking the tree
 // It can optionally also fix trailing slashes.
 // It returns the case-corrected path and a bool indicating whether the lookup
 // was successful.
-func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
-	return n.findCaseInsensitivePathRec(
+func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (fixedPath string, found bool) {
+	const stackBufSize = 128
+
+	// Use a static sized buffer on the stack in the common case.
+	// If the path is too long, allocate a buffer on the heap instead.
+	buf := make([]byte, 0, stackBufSize)
+	if l := len(path) + 1; l > stackBufSize {
+		buf = make([]byte, 0, l)
+	}
+
+	ciPath := n.findCaseInsensitivePathRec(
 		path,
-		make([]byte, 0, len(path)+1), // preallocate enough memory for new path
-		[4]byte{},                    // empty rune buffer
+		buf,       // Preallocate enough memory for new path
+		[4]byte{}, // Empty rune buffer
 		fixTrailingSlash,
 	)
+
+	return string(ciPath), ciPath != nil
 }
 
-// shift bytes in array by n bytes left
+// Shift bytes in array by n bytes left
 func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
 	switch n {
 	case 0:
@@ -488,14 +497,13 @@ func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
 	}
 }
 
-// recursive case-insensitive lookup function used by n.findCaseInsensitivePath
-func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) ([]byte, bool) {
+// Recursive case-insensitive lookup function used by n.findCaseInsensitivePath
+func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte {
 	npLen := len(n.path)
 
-walk: // outer loop for walking the tree
+walk: // Outer loop for walking the tree
 	for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {
-		// add common prefix to result
-
+		// Add common prefix to result
 		oldPath := path
 		path = path[npLen:]
 		ciPath = append(ciPath, n.path...)
@@ -505,13 +513,14 @@ walk: // outer loop for walking the tree
 			// we can just look up the next child node and continue to walk down
 			// the tree
 			if !n.wildChild {
-				// skip rune bytes already processed
+				// Skip rune bytes already processed
 				rb = shiftNRuneBytes(rb, npLen)
 
 				if rb[0] != 0 {
-					// old rune not finished
-					for i := 0; i < len(n.indices); i++ {
-						if n.indices[i] == rb[0] {
+					// Old rune not finished
+					idxc := rb[0]
+					for i, c := range []byte(n.indices) {
+						if c == idxc {
 							// continue with child node
 							n = n.children[i]
 							npLen = len(n.path)
@@ -519,12 +528,12 @@ walk: // outer loop for walking the tree
 						}
 					}
 				} else {
-					// process a new rune
+					// Process a new rune
 					var rv rune
 
-					// find rune start
-					// runes are up to 4 byte long,
-					// -4 would definitely be another rune
+					// Find rune start.
+					// Runes are up to 4 byte long,
+					// -4 would definitely be another rune.
 					var off int
 					for max := min(npLen, 3); off < max; off++ {
 						if i := npLen - off; utf8.RuneStart(oldPath[i]) {
@@ -534,38 +543,40 @@ walk: // outer loop for walking the tree
 						}
 					}
 
-					// calculate lowercase bytes of current rune
+					// Calculate lowercase bytes of current rune
 					lo := unicode.ToLower(rv)
 					utf8.EncodeRune(rb[:], lo)
 
-					// skip already processed bytes
+					// Skip already processed bytes
 					rb = shiftNRuneBytes(rb, off)
 
-					for i := 0; i < len(n.indices); i++ {
-						// lowercase matches
-						if n.indices[i] == rb[0] {
+					idxc := rb[0]
+					for i, c := range []byte(n.indices) {
+						// Lowercase matches
+						if c == idxc {
 							// must use a recursive approach since both the
 							// uppercase byte and the lowercase byte might exist
 							// as an index
-							if out, found := n.children[i].findCaseInsensitivePathRec(
+							if out := n.children[i].findCaseInsensitivePathRec(
 								path, ciPath, rb, fixTrailingSlash,
-							); found {
-								return out, true
+							); out != nil {
+								return out
 							}
 							break
 						}
 					}
 
-					// if we found no match, the same for the uppercase rune,
+					// If we found no match, the same for the uppercase rune,
 					// if it differs
 					if up := unicode.ToUpper(rv); up != lo {
 						utf8.EncodeRune(rb[:], up)
 						rb = shiftNRuneBytes(rb, off)
 
-						for i, c := 0, rb[0]; i < len(n.indices); i++ {
-							// uppercase matches
-							if n.indices[i] == c {
-								// continue with child node
+						idxc := rb[0]
+						for i, c := range []byte(n.indices) {
+							// Uppercase matches
+							if c == idxc {
+								// Continue with child node
 								n = n.children[i]
 								npLen = len(n.path)
 								continue walk
@@ -576,52 +587,55 @@ walk: // outer loop for walking the tree
 
 				// Nothing found. We can recommend to redirect to the same URL
 				// without a trailing slash if a leaf exists for that path
-				return ciPath, (fixTrailingSlash && path == "/" && n.handle != nil)
+				if fixTrailingSlash && path == "/" && n.handle != nil {
+					return ciPath
+				}
+				return nil
 			}
 
 			n = n.children[0]
 			switch n.nType {
 			case param:
-				// find param end (either '/' or path end)
-				k := 0
-				for k < len(path) && path[k] != '/' {
-					k++
+				// Find param end (either '/' or path end)
+				end := 0
+				for end < len(path) && path[end] != '/' {
+					end++
 				}
 
-				// add param value to case insensitive path
-				ciPath = append(ciPath, path[:k]...)
+				// Add param value to case insensitive path
+				ciPath = append(ciPath, path[:end]...)
 
-				// we need to go deeper!
-				if k < len(path) {
+				// We need to go deeper!
+				if end < len(path) {
 					if len(n.children) > 0 {
-						// continue with child node
+						// Continue with child node
 						n = n.children[0]
 						npLen = len(n.path)
-						path = path[k:]
+						path = path[end:]
 						continue
 					}
 
 					// ... but we can't
-					if fixTrailingSlash && len(path) == k+1 {
-						return ciPath, true
+					if fixTrailingSlash && len(path) == end+1 {
+						return ciPath
 					}
-					return ciPath, false
+					return nil
 				}
 
 				if n.handle != nil {
-					return ciPath, true
+					return ciPath
 				} else if fixTrailingSlash && len(n.children) == 1 {
 					// No handle found. Check if a handle for this path + a
 					// trailing slash exists
 					n = n.children[0]
 					if n.path == "/" && n.handle != nil {
-						return append(ciPath, '/'), true
+						return append(ciPath, '/')
 					}
 				}
-				return ciPath, false
+				return nil
 
 			case catchAll:
-				return append(ciPath, path...), true
+				return append(ciPath, path...)
 
 			default:
 				panic("invalid node type")
@@ -630,24 +644,24 @@ walk: // outer loop for walking the tree
 			// We should have reached the node containing the handle.
 			// Check if this node has a handle registered.
 			if n.handle != nil {
-				return ciPath, true
+				return ciPath
 			}
 
 			// No handle found.
 			// Try to fix the path by adding a trailing slash
 			if fixTrailingSlash {
-				for i := 0; i < len(n.indices); i++ {
-					if n.indices[i] == '/' {
+				for i, c := range []byte(n.indices) {
+					if c == '/' {
 						n = n.children[i]
 						if (len(n.path) == 1 && n.handle != nil) ||
 							(n.nType == catchAll && n.children[0].handle != nil) {
-							return append(ciPath, '/'), true
+							return append(ciPath, '/')
 						}
-						return ciPath, false
+						return nil
 					}
 				}
 			}
-			return ciPath, false
+			return nil
 		}
 	}
 
@@ -655,12 +669,12 @@ walk: // outer loop for walking the tree
 	// Try to fix the path by adding / removing a trailing slash
 	if fixTrailingSlash {
 		if path == "/" {
-			return ciPath, true
+			return ciPath
 		}
 		if len(path)+1 == npLen && n.path[len(path)] == '/' &&
 			strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handle != nil {
-			return append(ciPath, n.path...), true
+			return append(ciPath, n.path...)
 		}
 	}
-	return ciPath, false
+	return nil
 }
diff --git a/tree_test.go b/tree_test.go
index 2e309af..2209abc 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -13,15 +13,15 @@ import (
 	"testing"
 )
 
-func printChildren(n *node, prefix string) {
-	fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)
-	for l := len(n.path); l > 0; l-- {
-		prefix += " "
-	}
-	for _, child := range n.children {
-		printChildren(child, prefix)
-	}
-}
+// func printChildren(n *node, prefix string) {
+// 	fmt.Printf(" %02d %s%s[%d] %v %t %d \r\n", n.priority, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)
+// 	for l := len(n.path); l > 0; l-- {
+// 		prefix += " "
+// 	}
+// 	for _, child := range n.children {
+// 		printChildren(child, prefix)
+// 	}
+// }
 
 // Used as a workaround since we can't compare functions or their addresses
 var fakeHandlerValue string
@@ -39,23 +39,34 @@ type testRequests []struct {
 	ps         Params
 }
 
+func getParams() *Params {
+	ps := make(Params, 0, 20)
+	return &ps
+}
+
 func checkRequests(t *testing.T, tree *node, requests testRequests) {
 	for _, request := range requests {
-		handler, ps, _ := tree.getValue(request.path)
+		handler, psp, _ := tree.getValue(request.path, getParams)
 
-		if handler == nil {
+		switch {
+		case handler == nil:
 			if !request.nilHandler {
 				t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
 			}
-		} else if request.nilHandler {
+		case request.nilHandler:
 			t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
-		} else {
+		default:
 			handler(nil, nil, nil)
 			if fakeHandlerValue != request.route {
 				t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
 			}
 		}
 
+		var ps Params
+		if psp != nil {
+			ps = *psp
+		}
+
 		if !reflect.DeepEqual(ps, request.ps) {
 			t.Errorf("Params mismatch for route '%s'", request.path)
 		}
@@ -82,33 +93,11 @@ func checkPriorities(t *testing.T, n *node) uint32 {
 	return prio
 }
 
-func checkMaxParams(t *testing.T, n *node) uint8 {
-	var maxParams uint8
-	for i := range n.children {
-		params := checkMaxParams(t, n.children[i])
-		if params > maxParams {
-			maxParams = params
-		}
-	}
-	if n.nType > root && !n.wildChild {
-		maxParams++
-	}
-
-	if n.maxParams != maxParams {
-		t.Errorf(
-			"maxParams mismatch for node '%s': is %d, should be %d",
-			n.path, n.maxParams, maxParams,
-		)
-	}
-
-	return maxParams
-}
-
 func TestCountParams(t *testing.T) {
 	if countParams("/path/:param1/static/*catch-all") != 2 {
 		t.Fail()
 	}
-	if countParams(strings.Repeat("/:param", 256)) != 255 {
+	if countParams(strings.Repeat("/:param", 256)) != 256 {
 		t.Fail()
 	}
 }
@@ -133,7 +122,7 @@ func TestTreeAddAndGet(t *testing.T) {
 		tree.addRoute(route, fakeHandler(route))
 	}
 
-	//printChildren(tree, "")
+	// printChildren(tree, "")
 
 	checkRequests(t, tree, testRequests{
 		{"/a", false, "/a", nil},
@@ -150,7 +139,6 @@ func TestTreeAddAndGet(t *testing.T) {
 	})
 
 	checkPriorities(t, tree)
-	checkMaxParams(t, tree)
 }
 
 func TestTreeWildcard(t *testing.T) {
@@ -176,7 +164,7 @@ func TestTreeWildcard(t *testing.T) {
 		tree.addRoute(route, fakeHandler(route))
 	}
 
-	//printChildren(tree, "")
+	// printChildren(tree, "")
 
 	checkRequests(t, tree, testRequests{
 		{"/", false, "/", nil},
@@ -196,7 +184,6 @@ func TestTreeWildcard(t *testing.T) {
 	})
 
 	checkPriorities(t, tree)
-	checkMaxParams(t, tree)
 }
 
 func catchPanic(testFunc func()) (recv interface{}) {
@@ -216,7 +203,8 @@ type testRoute struct {
 func testRoutes(t *testing.T, routes []testRoute) {
 	tree := &node{}
 
-	for _, route := range routes {
+	for i := range routes {
+		route := routes[i]
 		recv := catchPanic(func() {
 			tree.addRoute(route.path, nil)
 		})
@@ -230,7 +218,7 @@ func testRoutes(t *testing.T, routes []testRoute) {
 		}
 	}
 
-	//printChildren(tree, "")
+	// printChildren(tree, "")
 }
 
 func TestTreeWildcardConflict(t *testing.T) {
@@ -280,7 +268,8 @@ func TestTreeDupliatePath(t *testing.T) {
 		"/search/:query",
 		"/user_:name",
 	}
-	for _, route := range routes {
+	for i := range routes {
+		route := routes[i]
 		recv := catchPanic(func() {
 			tree.addRoute(route, fakeHandler(route))
 		})
@@ -297,7 +286,7 @@ func TestTreeDupliatePath(t *testing.T) {
 		}
 	}
 
-	//printChildren(tree, "")
+	// printChildren(tree, "")
 
 	checkRequests(t, tree, testRequests{
 		{"/", false, "/", nil},
@@ -317,7 +306,8 @@ func TestEmptyWildcardName(t *testing.T) {
 		"/cmd/:/",
 		"/src/*",
 	}
-	for _, route := range routes {
+	for i := range routes {
+		route := routes[i]
 		recv := catchPanic(func() {
 			tree.addRoute(route, nil)
 		})
@@ -350,7 +340,6 @@ func TestTreeCatchMaxParams(t *testing.T) {
 	tree := &node{}
 	var route = "/cmd/*filepath"
 	tree.addRoute(route, fakeHandler(route))
-	checkMaxParams(t, tree)
 }
 
 func TestTreeDoubleWildcard(t *testing.T) {
@@ -362,7 +351,8 @@ func TestTreeDoubleWildcard(t *testing.T) {
 		"/:foo*bar",
 	}
 
-	for _, route := range routes {
+	for i := range routes {
+		route := routes[i]
 		tree := &node{}
 		recv := catchPanic(func() {
 			tree.addRoute(route, nil)
@@ -374,17 +364,6 @@ func TestTreeDoubleWildcard(t *testing.T) {
 	}
 }
 
-/*func TestTreeDuplicateWildcard(t *testing.T) {
-	tree := &node{}
-
-	routes := [...]string{
-		"/:id/:name/:id",
-	}
-	for _, route := range routes {
-		...
-	}
-}*/
-
 func TestTreeTrailingSlashRedirect(t *testing.T) {
 	tree := &node{}
 
@@ -413,8 +392,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		"/no/a",
 		"/no/b",
 		"/api/hello/:name",
+		"/vendor/:x/*y",
 	}
-	for _, route := range routes {
+	for i := range routes {
+		route := routes[i]
 		recv := catchPanic(func() {
 			tree.addRoute(route, fakeHandler(route))
 		})
@@ -423,7 +404,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		}
 	}
 
-	//printChildren(tree, "")
+	// printChildren(tree, "")
 
 	tsrRoutes := [...]string{
 		"/hi/",
@@ -440,9 +421,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		"/admin/config/",
 		"/admin/config/permissions/",
 		"/doc/",
+		"/vendor/x",
 	}
 	for _, route := range tsrRoutes {
-		handler, _, tsr := tree.getValue(route)
+		handler, _, tsr := tree.getValue(route, nil)
 		if handler != nil {
 			t.Fatalf("non-nil handler for TSR route '%s", route)
 		} else if !tsr {
@@ -459,7 +441,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		"/api/world/abc",
 	}
 	for _, route := range noTsrRoutes {
-		handler, _, tsr := tree.getValue(route)
+		handler, _, tsr := tree.getValue(route, nil)
 		if handler != nil {
 			t.Fatalf("non-nil handler for No-TSR route '%s", route)
 		} else if tsr {
@@ -478,7 +460,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
 		t.Fatalf("panic inserting test route: %v", recv)
 	}
 
-	handler, _, tsr := tree.getValue("/")
+	handler, _, tsr := tree.getValue("/", nil)
 	if handler != nil {
 		t.Fatalf("non-nil handler")
 	} else if tsr {
@@ -489,6 +471,9 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
 func TestTreeFindCaseInsensitivePath(t *testing.T) {
 	tree := &node{}
 
+	longPath := "/l" + strings.Repeat("o", 128) + "ng"
+	lOngPath := "/l" + strings.Repeat("O", 128) + "ng/"
+
 	routes := [...]string{
 		"/hi",
 		"/b/",
@@ -522,9 +507,11 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
 		"/w/♭/", // 3 byte, last byte differs
 		"/w/𠜎",  // 4 byte
 		"/w/𠜏/", // 4 byte
+		longPath,
 	}
 
-	for _, route := range routes {
+	for i := range routes {
+		route := routes[i]
 		recv := catchPanic(func() {
 			tree.addRoute(route, fakeHandler(route))
 		})
@@ -535,21 +522,23 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
 
 	// Check out == in for all registered routes
 	// With fixTrailingSlash = true
-	for _, route := range routes {
+	for i := range routes {
+		route := routes[i]
 		out, found := tree.findCaseInsensitivePath(route, true)
 		if !found {
 			t.Errorf("Route '%s' not found!", route)
-		} else if string(out) != route {
-			t.Errorf("Wrong result for route '%s': %s", route, string(out))
+		} else if out != route {
+			t.Errorf("Wrong result for route '%s': %s", route, out)
 		}
 	}
 	// With fixTrailingSlash = false
-	for _, route := range routes {
+	for i := range routes {
+		route := routes[i]
 		out, found := tree.findCaseInsensitivePath(route, false)
 		if !found {
 			t.Errorf("Route '%s' not found!", route)
-		} else if string(out) != route {
-			t.Errorf("Wrong result for route '%s': %s", route, string(out))
+		} else if out != route {
+			t.Errorf("Wrong result for route '%s': %s", route, out)
 		}
 	}
 
@@ -614,13 +603,14 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
 		{"/w/♭", "/w/♭/", true, true},
 		{"/w/𠜎/", "/w/𠜎", true, true},
 		{"/w/𠜏", "/w/𠜏/", true, true},
+		{lOngPath, longPath, true, true},
 	}
 	// With fixTrailingSlash = true
 	for _, test := range tests {
 		out, found := tree.findCaseInsensitivePath(test.in, true)
-		if found != test.found || (found && (string(out) != test.out)) {
+		if found != test.found || (found && (out != test.out)) {
 			t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
-				test.in, string(out), found, test.out, test.found)
+				test.in, out, found, test.out, test.found)
 			return
 		}
 	}
@@ -629,12 +619,12 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
 		out, found := tree.findCaseInsensitivePath(test.in, false)
 		if test.slash {
 			if found { // test needs a trailingSlash fix. It must not be found!
-				t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
+				t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, out)
 			}
 		} else {
-			if found != test.found || (found && (string(out) != test.out)) {
+			if found != test.found || (found && (out != test.out)) {
 				t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
-					test.in, string(out), found, test.out, test.found)
+					test.in, out, found, test.out, test.found)
 				return
 			}
 		}
@@ -653,7 +643,7 @@ func TestTreeInvalidNodeType(t *testing.T) {
 
 	// normal lookup
 	recv := catchPanic(func() {
-		tree.getValue("/test")
+		tree.getValue("/test", nil)
 	})
 	if rs, ok := recv.(string); !ok || rs != panicMsg {
 		t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
@@ -682,7 +672,9 @@ func TestTreeWildcardConflictEx(t *testing.T) {
 		{"/conooo/xxx", "ooo", `/con:tact`, `:tact`},
 	}
 
-	for _, conflict := range conflicts {
+	for i := range conflicts {
+		conflict := conflicts[i]
+
 		// I have to re-create a 'tree', because the 'tree' will be
 		// in an inconsistent state when the loop recovers from the
 		// panic which threw by 'addRoute' function.
@@ -693,7 +685,8 @@ func TestTreeWildcardConflictEx(t *testing.T) {
 			"/who/foo/hello",
 		}
 
-		for _, route := range routes {
+		for i := range routes {
+			route := routes[i]
 			tree.addRoute(route, fakeHandler(route))
 		}