I Forgot That Order Matters — A Debugging Story
I forgot that the order matters when passing values into a function. For a moment I thought everything was structs.
It started with a simple “invalid credentials” error. Every login attempt on my Truth or Dare app failed. The kind of bug that makes you question everything — your auth logic, your database, your understanding of cryptography.
Here’s how I debugged my way through it.
Step 1: Suspecting bcrypt
My first instinct was that bcrypt needed some kind of secret or salt to compare hashes correctly. It doesn’t. Bcrypt embeds a randomly generated salt *inside* the hash string itself — that long `$2a$12$...` value contains everything needed to re-hash and compare. No external secret required. Cross that off the list.
Step 2: Byte-level logging
I added raw byte logging on both the register and login paths in Go:
DEBUG register password bytes: [79 110 101]
DEBUG login password bytes: [79 110 101 49 49 49 49 49]Different byte slices. Different passwords. I assumed I was mistyping during testing and moved on. I was wrong to dismiss this so quickly.
Step 3: The frontend
I dug into the Next.js login form and found something interesting — the password field was running format validation (uppercase letter, number required) *before* sending the request. That meant users who registered under looser rules could never log in. Fixed. Still broken.
I checked the axios instance, the request interceptors, the response parsers. Nothing was transforming the password. I logged the outgoing request directly from the browser:
password: “One11111” The frontend was sending the right value. So the bug was in the backend.
Step 4: The actual bug
My Go service function had this signature:
func (s *Service) Register(
ctx context.Context,
username, email, fullName, password string,
) (uuid.UUID, error) And my handler was calling it like this:
h.service.Register(
ctx,
req.Username,
req.Email,
req.Password, // ← supposed to be fullName
req.FullName, // ← supposed to be password
)I had swapped `fullName` and `password`. The app was hashing the user’s *display name* as their password on every registration. When they tried to log in with their actual password, it never matched.
One line. Hours of debugging.
The takeaway
Go (like many languages) uses positional arguments. When two parameters share the same type — in this case both are `string` — the compiler has no way to catch the swap. It silently does the wrong thing.
This is exactly the scenario where passing a struct instead of individual arguments shines:
type RegisterParams struct {
Username string
Email string
FullName string
Password string
}With a struct, the field names make intent explicit. Order becomes irrelevant. The bug becomes impossible.
Next time you’re writing a function that takes more than two or three parameters of the same type — reach for a struct. Your future self will thank you.
Building a multiplayer Truth or Dare game in Go + Next.js. Writing about the bugs I hit along the way.
