strings.HasSuffix ist das Gegenstück zu HasPrefix und beantwortet eine einzige Frage: Endet der String s mit der Zeichenfolge suffix? Die Rückgabe ist ein schlichter bool — keine Position, kein neuer String, keine Allokation. Intern wird zunächst die Länge geprüft (ist suffix länger als s, kann es kein Suffix sein), dann erfolgt ein byteweiser Vergleich des Endbereichs von s mit suffix. Weil Go-Strings UTF-8-kodiert sind und der Vergleich auf Bytes operiert, ist die Funktion automatisch sicher für Mehrbyte-Zeichen: identische Byte-Sequenzen am Ende bedeuten identische Runen am Ende. Typische Einsatzgebiete sind Dateiendungs-Erkennung, MIME-Type-Heuristiken und URL-Pfad-Routing.

Die Signatur ist minimal und liest sich wie ein logisches Prädikat. Zwei Strings rein, ein Wahrheitswert raus — ohne Fehlerkanal, ohne Optionen, ohne überraschende Sonderfälle.

Go signatur.go
func HasSuffix(s, suffix string) bool

Der erste Parameter ist der zu prüfende String, der zweite das gesuchte Suffix. Die Reihenfolge ist wichtig und entspricht der natürlichen Leserichtung — „hat s das Suffix suffix?".

Ein Sonderfall, der in der Praxis oft übersehen wird: Ein leeres Suffix ist immer ein gültiges Suffix jedes Strings, auch des leeren Strings selbst. Das ergibt sich mathematisch sauber aus der Definition (jeder String endet mit der leeren Sequenz) und ist konsistent mit HasPrefix("", "").

Go leer.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.HasSuffix("report.pdf", ""))
	fmt.Println(strings.HasSuffix("", ""))
	fmt.Println(strings.HasSuffix("", ".pdf"))
}
Output
true
true
false

Wenn ein Suffix dynamisch aus Konfiguration oder Nutzereingabe stammt, lohnt sich eine explizite Leer-Prüfung vorab — sonst klassifiziert die Funktion plötzlich „alles" als Match.

Die drei Suffix-Funktionen der Standardbibliothek bilden eine sinnvolle Familie. HasSuffix antwortet nur, TrimSuffix handelt nur, und CutSuffix (ab Go 1.20) kombiniert beides in einem Aufruf. Welche Variante passt, hängt davon ab, ob nach dem Check noch ein „rest" gebraucht wird.

FunktionRückgabeModifiziert String?Erfolg signalisiert?
HasSuffix(s, suffix)boolneinüber Rückgabewert
TrimSuffix(s, suffix)stringja, falls Suffix passtnein — nur Ergebnis
CutSuffix(s, suffix)(string, bool)ja, falls Suffix passtüber zweite Rückgabe

Wer nur eine Bedingung in einem if braucht, greift zu HasSuffix. Wer immer trimmt (und der Original-String reichen würde, falls nichts passt), nutzt TrimSuffix. Wer sowohl wissen will, ob getrimmt wurde, als auch das Ergebnis braucht, ist mit CutSuffix am besten bedient.

HasSuffix läuft in O(len(suffix)) — also linear in der Länge des Suffix, nicht in der Länge von s. Bei typischen Dateiendungen mit drei bis fünf Bytes ist das im Bereich weniger CPU-Zyklen. Es findet keine Allokation statt, der Vergleich passiert in-place auf den zugrundeliegenden Byte-Slices.

In Hot-Paths (z. B. einem Router, der pro Request mehrere Endungen prüft) ist HasSuffix deutlich schneller als jede Regex-basierte Alternative — selbst eine kompilierte regexp.Regexp zahlt Overhead für Zustandsmaschine und Capture-Logik, den der Längen-Check hier komplett spart.

Oft soll geprüft werden, ob ein Pfad auf eine von mehreren Endungen passt — Bilder, Videos, Archive. Die idiomatische Lösung ist ein simpler for range-Loop über einen Slice von Endungen.

Go mehrere.go
package main

import (
	"fmt"
	"strings"
)

func hatBildEndung(name string) bool {
	endungen := []string{".jpg", ".jpeg", ".png", ".gif", ".webp"}
	for _, e := range endungen {
		if strings.HasSuffix(name, e) {
			return true
		}
	}
	return false
}

func main() {
	fmt.Println(hatBildEndung("urlaub.jpg"))
	fmt.Println(hatBildEndung("doku.pdf"))
	fmt.Println(hatBildEndung("logo.webp"))
}
Output
true
false
true

Bei einer kleinen, festen Liste ist der Loop perfekt: lesbar, allokationsfrei, branchpredictor-freundlich. Bei sehr vielen Endungen (mehrere Dutzend) lohnt sich ein map[string]struct{}-Set in Kombination mit filepath.Ext, weil der Hash-Lookup dann konstante Zeit garantiert statt linearer Suche.

HasSuffix vergleicht byteweise — und damit strikt case-sensitiv. "foto.JPG" endet aus Go-Sicht nicht auf ".jpg", weil J (0x4A) und j (0x6A) unterschiedliche Bytes sind. Bei Dateinamen aus Nutzereingaben, Uploads oder fremden Dateisystemen (insbesondere Windows-Quellen) ist Großschreibung in Endungen aber häufig.

Go case.go
package main

import (
	"fmt"
	"strings"
)

func istJPG(name string) bool {
	return strings.HasSuffix(strings.ToLower(name), ".jpg")
}

func main() {
	fmt.Println(istJPG("urlaub.jpg"))
	fmt.Println(istJPG("URLAUB.JPG"))
	fmt.Println(istJPG("Foto.Jpg"))
}
Output
true
true
true

Das Pattern HasSuffix(ToLower(name), ".endung") ist der etablierte Weg. Alternativ funktioniert strings.EqualFold(filepath.Ext(name), ".jpg") — etwas teurer, dafür ohne komplette Lower-Konvertierung des gesamten Pfads.

In Upload-Handlern, Galerie-Scannern oder Konverter-Tools muss eine Datei oft anhand der Endung in eine Kategorie einsortiert werden. Eine kompakte Klassifizierungsfunktion liefert pro Datei einen sprechenden Typ-String, den das aufrufende System weiterverarbeiten kann.

Go klassifizierung.go
package main

import (
	"fmt"
	"strings"
)

func kategorie(name string) string {
	n := strings.ToLower(name)
	switch {
	case hatEine(n, ".jpg", ".jpeg", ".png", ".gif", ".webp", ".avif"):
		return "bild"
	case hatEine(n, ".mp4", ".mov", ".mkv", ".webm"):
		return "video"
	case hatEine(n, ".zip", ".tar", ".gz", ".7z"):
		return "archiv"
	case hatEine(n, ".pdf", ".docx", ".odt"):
		return "dokument"
	default:
		return "unbekannt"
	}
}

func hatEine(s string, endungen ...string) bool {
	for _, e := range endungen {
		if strings.HasSuffix(s, e) {
			return true
		}
	}
	return false
}

func main() {
	dateien := []string{"urlaub.JPG", "clip.mp4", "backup.tar.gz", "vertrag.pdf", "skript.go"}
	for _, d := range dateien {
		fmt.Printf("%-20s -> %s\n", d, kategorie(d))
	}
}
Output
urlaub.JPG           -> bild
clip.mp4             -> video
backup.tar.gz        -> archiv
vertrag.pdf          -> dokument
skript.go            -> unbekannt

Die Variadic-Hilfsfunktion hatEine macht den switch lesbar und lokal — die Endungs-Listen stehen direkt neben dem Case-Label, ohne globale Maps oder Konstanten. Für komplexere Logik (z. B. Magic-Byte-Validierung statt nur Endung) wäre net/http.DetectContentType der seriöse nächste Schritt, aber für Routing- und UI-Hinweise reicht der Endungs-Check.

Im HTTP-Routing taucht HasSuffix regelmäßig auf — etwa um zwischen Verzeichnis-Pfaden (Trailing Slash) und Datei-Pfaden zu unterscheiden, oder um Content-Negotiation per Datei-Endung statt per Accept-Header zu implementieren.

Go routing.go
package main

import (
	"fmt"
	"strings"
)

type Antwort struct {
	Modus string
	Typ   string
}

func klassifiziere(pfad string) Antwort {
	switch {
	case strings.HasSuffix(pfad, "/"):
		return Antwort{Modus: "verzeichnis", Typ: "html"}
	case strings.HasSuffix(pfad, ".json"):
		return Antwort{Modus: "ressource", Typ: "application/json"}
	case strings.HasSuffix(pfad, ".xml"):
		return Antwort{Modus: "ressource", Typ: "application/xml"}
	case strings.HasSuffix(pfad, ".html"):
		return Antwort{Modus: "ressource", Typ: "text/html"}
	default:
		return Antwort{Modus: "ressource", Typ: "text/plain"}
	}
}

func main() {
	pfade := []string{"/blog/", "/api/users.json", "/feed.xml", "/about.html", "/healthz"}
	for _, p := range pfade {
		a := klassifiziere(p)
		fmt.Printf("%-20s -> %-12s %s\n", p, a.Modus, a.Typ)
	}
}
Output
/blog/               -> verzeichnis html
/api/users.json      -> ressource    application/json
/feed.xml            -> ressource    application/xml
/about.html          -> ressource    text/html
/healthz             -> ressource    text/plain

Der Trailing-Slash-Check ist ein klassisches Muster: Pfade mit / am Ende werden als „Verzeichnis-Index" interpretiert (typischerweise zu index.html aufgelöst), alles andere als konkrete Ressource. Für ernsthafte Router empfiehlt sich net/http.ServeMux (ab Go 1.22 mit Pattern-Matching) oder ein dediziertes Routing-Paket — aber für kleine APIs und Static-Sites reicht der direkte HasSuffix-Switch.

O(len(suffix)) Vergleich

Laufzeit hängt nur an der Suffix-Länge, nicht an der Länge von s — typische Endungen kosten wenige CPU-Zyklen.

Leeres Suffix immer true

HasSuffix(s, "") liefert immer true, auch für s == "" — bei dynamischen Suffixen vorab auf leer prüfen.

Strikt case-sensitiv

Byteweiser Vergleich macht .JPG und .jpg zu unterschiedlichen Suffixen — relevant bei Datei-Uploads aus heterogenen Quellen.

Zum Entfernen TrimSuffix

HasSuffix antwortet nur — wer das Suffix abschneiden will, nutzt direkt TrimSuffix ohne vorherigen Check.

CutSuffix kombiniert Check + Trim

Seit Go 1.20 liefert CutSuffix Rest und Erfolgs-Flag in einem Aufruf — spart die Doppel-Prüfung HasSuffix + TrimSuffix.

Dateiendungen mit ToLower normalisieren

Pattern HasSuffix(strings.ToLower(name), ".jpg") ist der idiomatische Weg gegen Großschreibung in Endungen.

Threadsafe ohne State

Reiner Vergleich ohne globalen Zustand — beliebig viele Goroutines können parallel HasSuffix aufrufen.

Schneller als Regex

Für Endungs-Prüfungen schlägt HasSuffix jede regexp-Lösung um Größenordnungen — kein Zustandsautomat, keine Capture-Logik.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das strings-Paket — String-Manipulation

Zur Übersicht