I have a CS degree and I've written production code. I know what a database index is, I can read a diff, and I understand the difference between a synchronous API call and an async job queue. By PM standards, this is supposed to make me better at working with engineers.
It helps. It also creates a specific failure mode that took me a while to recognize: I sometimes think I understand the technical constraints well enough to have opinions about implementation, and I'm wrong. The engineers are too professional to say "you don't know what you're talking about" directly, so they explain patiently, and I nod, and sometimes I still don't fully get it. The CS background gives me enough vocabulary to sound informed while being wrong in ways that are harder to catch than if I just said "I don't know how this works."
The honest version: they know more than me about almost everything technical. My job is to make sure that knowledge gets applied to the right problems.
The spec that cost us a week
We had an engineer spend a week building a batch job that ran nightly when what we actually needed was a real-time webhook handler. The spec said "sync order status from the logistics provider." I meant: immediately on status change, trigger an update so the customer sees the right status in the app. He reasonably read it as: keep the statuses in sync. A nightly batch job does keep them in sync. It also means a customer's order shows as "processing" for 23 hours after it shipped.
The failure wasn't technical. The engineer built exactly what the spec described. The failure was that I wrote "sync" when I meant "real-time event-driven update," and I didn't think to specify the latency requirement because it seemed obvious to me. It wasn't obvious from the spec.
Sync order status from the logistics provider
Update order status within 30 seconds of the logistics provider webhook firing. Implies a webhook receiver, a queue, a consumer, and retry logic for failed updates.
After that I started writing latency requirements explicitly. Not "sync order status" but "update order status within 30 seconds of the logistics provider webhook firing." That's a different technical problem - it implies a webhook receiver, a queue, a consumer, retry logic for failed updates. The engineer needs to know that's what you're asking for, because the implementation is completely different from a nightly batch job and the infrastructure cost is different too.
What I actually do that helps
The most useful thing I do for the engineering team is protect their time from things that aren't engineering problems. Every stakeholder request I handle without pulling them in, every ambiguous requirement I resolve before it becomes a blocker in the middle of a sprint, every meeting I keep them out of - that's time they spend building instead of context-switching. Context-switching is expensive for engineers in a way it isn't for most other roles. Getting pulled out of a complex debugging session to answer a question that could have been a Slack message costs more than the five minutes the question takes.
The second thing is being honest about uncertainty early. If a requirement might change - because we're waiting on a business decision, because we're not sure about user behavior, because the regulatory situation is unclear - I say that upfront. Engineers hate building something and then being told the requirements changed. They hate it less when they knew the requirements were uncertain from the start, because they can make different architectural choices. Building something that's easy to change is different from building something optimized for a specific requirement that might not hold.
The third thing is asking for their input on product decisions, not just technical ones. Engineers who've been working on a system for a year have opinions about what's actually causing user problems. They see the error logs. They know which parts of the system are fragile. They know what the support tickets are actually about at a technical level. The PM who treats engineers as implementation resources misses a huge amount of signal that's sitting right there.
Engineers who've been on a system for a year see the error logs, know the fragile parts, and understand what support tickets are really about at a technical level. Ask them about product problems, not just implementation details.
The estimate problem
The worst thing I've done to an engineering team is commit to a timeline without checking with them first. I did this early in my PM career - went to a stakeholder and said "we can have this by end of month" because it seemed reasonable to me based on my (incorrect) estimate of the complexity. The engineers then had to either kill themselves to hit a deadline they hadn't agreed to, or I had to go back to the stakeholder and explain why the timeline had changed.
Both outcomes are bad. The first one burns out the team and creates resentment. The second one damages your credibility with the stakeholder and, more importantly, damages the team's trust in you. If they know you'll commit to timelines without consulting them, they stop giving you honest estimates. They pad everything. Your estimates get worse, which makes you more likely to commit to bad timelines, which makes them pad more. It's a bad spiral and it starts with one conversation you shouldn't have had without checking first.
The engineers on my team know more than me about how to build things. I know more than them about why we're building them and what the business constraints are. When both sides respect that division and communicate across it clearly, the work goes well. When either side forgets it - when I start having opinions about implementation details I don't fully understand, or when they start making product decisions without surfacing the tradeoffs - things get messy in ways that are hard to untangle.