People searching for go-memdb, go memdb, memdb, golang in memory database, golang in memory db, go in memory database, in memory database golang, or mem db are usually looking for HashiCorp’s github.com/hashicorp/go-memdb: an embedded, in-process indexed store—not a network server and not AWS DynamoDB (unrelated “dynamdob” typos in search logs). It gives you tables, multiple indexes, MVCC-style readers, and read/write transactions (*memdb.Txn) on top of immutable radix trees. You model rows as Go structs (see structs in Go). Official API reference: pkg.go.dev/github.com/hashicorp/go-memdb.
Tested with Go 1.24 on Linux and
github.com/hashicorp/go-memdbv1.3.5.
Install github.com/hashicorp/go-memdb
From a module:
go get github.com/hashicorp/go-memdbgo: added github.com/hashicorp/go-immutable-radix v1.3.1
go: added github.com/hashicorp/go-memdb v1.3.5
go: added github.com/hashicorp/golang-lru v0.5.4Import the package as memdb (the module path ends in go-memdb).
Schema, primary index id, and transactions
You declare a memdb.DBSchema with tables and memdb.IndexSchema entries. The primary index must be named id (the implementation looks up that key internally). Other indexes can use any names (name, score, …).
memdb.NewMemDB(schema)builds the database.db.Txn(true)starts a writable transaction;db.Txn(false)is read-only.- Always
defer txn.Abort()on read transactions (it is safe afterCommiton writers too; readers rely on it for cleanup). txn.Insert(table, obj)adds or replaces a row;txn.Commit()publishes writes atomically.
Reads: First, Get, LowerBound, and *memdb.Txn
Typical read helpers on memdb.Txn:
First(table, index, args...)— first row matching exact index constraints.Get(table, index, args...)— iterator over all rows for that index lookup.LowerBound(table, index, args...)— range scan from a lower bound (walk withNext()until your upper bound).
Prefix scans are available when the indexer supports them: append _prefix to the index name as described in the package docs.
package main
import (
"fmt"
"github.com/hashicorp/go-memdb"
)
type Student struct {
Id int
Name string
Score int
}
func main() {
schema := &memdb.DBSchema{
Tables: map[string]*memdb.TableSchema{
"student": {
Name: "student",
Indexes: map[string]*memdb.IndexSchema{
"id": {
Name: "id",
Unique: true,
Indexer: &memdb.IntFieldIndex{Field: "Id"},
},
"name": {
Name: "name",
Unique: false,
Indexer: &memdb.StringFieldIndex{Field: "Name"},
},
"score": {
Name: "score",
Unique: false,
Indexer: &memdb.IntFieldIndex{Field: "Score"},
},
},
},
},
}
db, err := memdb.NewMemDB(schema)
if err != nil {
panic(err)
}
txn := db.Txn(true)
for _, p := range []*Student{
{1, "Anna", 95},
{2, "Bob", 75},
{3, "Dorothy", 80},
{4, "Daniel", 90},
} {
if err := txn.Insert("student", p); err != nil {
panic(err)
}
}
txn.Commit()
txn = db.Txn(false)
defer txn.Abort()
raw, err := txn.First("student", "name", "Anna")
if err != nil {
panic(err)
}
fmt.Printf("Hello student %s!\n", raw.(*Student).Name)
it, err := txn.Get("student", "name")
if err != nil {
panic(err)
}
fmt.Println("All the students:")
for obj := it.Next(); obj != nil; obj = it.Next() {
fmt.Printf(" %s\n", obj.(*Student).Name)
}
it, err = txn.LowerBound("student", "score", 75)
if err != nil {
panic(err)
}
fmt.Println("Student with score 75 - 90:")
for obj := it.Next(); obj != nil; obj = it.Next() {
p := obj.(*Student)
if p.Score > 90 {
break
}
fmt.Printf(" %s with score %d\n", p.Name, p.Score)
}
}Hello student Anna!
All the students:
Anna
Bob
Daniel
Dorothy
Student with score 75 - 90:
Bob with score 75
Dorothy with score 80
Daniel with score 90Iteration order for Get follows the chosen index (here name), not insertion order.
Deletes: Delete and DeleteAll
Delete(table, obj)removes one object; the object must exist (primary fields are read fromobjvia theidindexer).DeleteAll(table, index, args...)removes every row matching the index constraint.
package main
import (
"fmt"
"github.com/hashicorp/go-memdb"
)
type Student struct {
Id int
Name string
Score int
}
func main() {
schema := &memdb.DBSchema{
Tables: map[string]*memdb.TableSchema{
"student": {
Name: "student",
Indexes: map[string]*memdb.IndexSchema{
"id": {Name: "id", Unique: true, Indexer: &memdb.IntFieldIndex{Field: "Id"}},
"name": {Name: "name", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "Name"}},
"score": {Name: "score", Unique: false, Indexer: &memdb.IntFieldIndex{Field: "Score"}},
},
},
},
}
db, err := memdb.NewMemDB(schema)
if err != nil {
panic(err)
}
txn := db.Txn(true)
for _, p := range []*Student{
{1, "Anna", 80},
{2, "Bob", 85},
{3, "Clair", 90},
{4, "Dorothy", 95},
{5, "Caitlyn", 90},
} {
if err := txn.Insert("student", p); err != nil {
panic(err)
}
}
txn.Commit()
printAll(db.Txn(false))
fmt.Println("deleting one")
txn = db.Txn(true)
if err := txn.Delete("student", &Student{Id: 1}); err != nil {
panic(err)
}
txn.Commit()
printAll(db.Txn(false))
fmt.Println("deleting many")
txn = db.Txn(true)
if _, err := txn.DeleteAll("student", "score", 90); err != nil {
panic(err)
}
txn.Commit()
printAll(db.Txn(false))
}
func printAll(txn *memdb.Txn) {
defer txn.Abort()
it, err := txn.Get("student", "id")
if err != nil {
panic(err)
}
fmt.Println("All the students:")
for obj := it.Next(); obj != nil; obj = it.Next() {
fmt.Printf(" %s\n", obj.(*Student).Name)
}
}All the students:
Anna
Bob
Clair
Dorothy
Caitlyn
deleting one
All the students:
Bob
Clair
Dorothy
Caitlyn
deleting many
All the students:
Bob
DorothyWhat go-memdb gives you (and what it does not)
Go-memdb is ideal when a single Go process needs a fast, transactional, indexed working set (similar intent to “golang in memory database” tutorials) with many concurrent readers and serialized writers via MVCC on immutable radix trees. It provides atomicity and isolation for transactions; because everything lives in memory, it does not offer durability—restart the process and the data is gone unless you snapshot or persist elsewhere. For cross-process or replicated data, use a real database or service; for cryptographic randomness, pair with crypto/rand instead of math/rand patterns.
Summary
Go memdb (github.com/hashicorp/go-memdb) is the usual answer to go-memdb and memdb searches: define a schema whose primary index is literally named id, open read or write transactions, insert structs with Insert, query with First, Get, or LowerBound, and mutate data with Delete / DeleteAll. It fits the “golang in memory db” niche inside one application binary, not cloud DynamoDB workloads. If you see signatures like getPolicy(txn *memdb.Txn) in the wild, read that as “policy code receives a read txn over memdb-backed state,” not a built-in memdb method.
References
github.com/hashicorp/go-memdbrepository- Package documentation (
memdb.Txn, indexes) - Immutable radix tree implementation
- In-memory database (concept)

