<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris</title>
    <description>The latest articles on DEV Community by Chris (@chris_ad4e762f2200b40f49a).</description>
    <link>https://dev.to/chris_ad4e762f2200b40f49a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4148345%2F09e6dd4f-b3e6-4e76-978f-b423e4aebdd8.jpg</url>
      <title>DEV Community: Chris</title>
      <link>https://dev.to/chris_ad4e762f2200b40f49a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chris_ad4e762f2200b40f49a"/>
    <language>en</language>
    <item>
      <title>Stop guessing your freelance rate: what 2026 survey data says</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 04:24:50 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/stop-guessing-your-freelance-rate-what-2026-survey-data-says-2j02</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/stop-guessing-your-freelance-rate-what-2026-survey-data-says-2j02</guid>
      <description>&lt;p&gt;Ask a room of freelancers what their hourly rate is and half of them will hesitate. Ask how they arrived at it and the honest answer is usually "my old salary divided by two thousand" or "whatever the freelancers around me on Upwork seem to charge." Then they never revisit the number. Years pass.&lt;/p&gt;

&lt;p&gt;The number you quote shapes everything downstream: which clients take you seriously, how fast the business grows, whether you can afford to say no. It deserves better than a guess. Here is what actual 2026 survey data says.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bands by experience level
&lt;/h2&gt;

&lt;p&gt;Arc's 2026 survey of 5,302 freelance developers worldwide reports these global hourly ranges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Junior: $20 to $50&lt;/li&gt;
&lt;li&gt;Mid: $40 to $90&lt;/li&gt;
&lt;li&gt;Senior: $75 to $150&lt;/li&gt;
&lt;li&gt;Staff or Principal: $120 to $220&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;US numbers skew higher, roughly $82 to $130 across levels in the same survey. The DACH market (freelancermap 2026, 5,400+ respondents) averages €103 per hour — down from €104 in 2025, the first dip since €96 in 2022.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bands by role
&lt;/h2&gt;

&lt;p&gt;A 2026 compilation across marketplaces and salary databases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Junior developer: $35 to $60&lt;/li&gt;
&lt;li&gt;Mid developer: $55 to $85&lt;/li&gt;
&lt;li&gt;Senior developer: $85 to $130&lt;/li&gt;
&lt;li&gt;Tech lead: $100 to $150&lt;/li&gt;
&lt;li&gt;Architect: $120 to $175&lt;/li&gt;
&lt;li&gt;AI or ML engineer: $110 to $175&lt;/li&gt;
&lt;li&gt;DevOps engineer: $80 to $130&lt;/li&gt;
&lt;li&gt;UI or UX designer: $60 to $100&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are approximate — geography, niche, and reputation move real people well outside the bands. But the direction across sources is consistent, which is what makes them useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specialty beats seniority
&lt;/h2&gt;

&lt;p&gt;The premium for specialization is real and measurable: cloud and mobile expertise earn 10 to 30 percent more than generalist developers, while AI and ML command 25 to 60 percent premiums, with niche AI work reaching 100 percent above generalist baselines. Specialized consultants — cloud, cybersecurity, data — report median rates of $160 to $220 an hour. Niche specialist positioning is arguably the single highest-leverage pricing decision a freelancer makes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to answer "what is your rate"
&lt;/h2&gt;

&lt;p&gt;Never open with a number. Open with discovery:&lt;/p&gt;

&lt;p&gt;"What does this project look like in terms of scope, timeline, and team? I want to quote something accurate rather than a guess."&lt;/p&gt;

&lt;p&gt;Anchoring research shows the first number mentioned sets the range for everything after. When the prospect answers with a budget, they have given you their anchor and their constraints — calibrate your quote to the project's value, not their number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The raise you never gave yourself
&lt;/h2&gt;

&lt;p&gt;Pick a target — one band up. Compute the monthly cost of not being there. Then build a ledger: every win, every testimonial, every scope successfully delivered, every metric moved. Review it monthly. Each entry is evidence for the next quote.&lt;/p&gt;

&lt;p&gt;The freelancers who raise rates on schedule share the same operating rhythm: they set calendar reminders for rate reviews, build evidence between reviews, and quote the new number to new prospects instead of renegotiating old clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  The negotiation rules that matter
&lt;/h2&gt;

&lt;p&gt;Project pricing beats hourly pricing whenever the client's problem has a dollar value. Give three options — Good, Better, Best — instead of one number. Never discount without removing scope; a discount on the same work teaches the client the first price was theater. And when you are too busy to take a project, quote a deliberately high number instead of declining: the "too busy" premium. The worst outcome is a very profitable project.&lt;/p&gt;




&lt;p&gt;This is one chapter of &lt;strong&gt;The Freelancer Client-Acquisition Kit&lt;/strong&gt; — 37 pages covering outreach scripts, proposals, rate tables, contracts, retainers, and referrals. Every data point labeled with its source and evidence strength; where the research found no data, it says so. No guru fluff: &lt;a href="https://cjettoostudent.gumroad.com/l/qfckgb" rel="noopener noreferrer"&gt;https://cjettoostudent.gumroad.com/l/qfckgb&lt;/a&gt;&lt;/p&gt;

</description>
      <category>freelancing</category>
      <category>career</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Cold outreach for freelance developers: 6 scripts that actually get replies</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 04:18:32 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/cold-outreach-for-freelance-developers-6-scripts-that-actually-get-replies-12ag</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/cold-outreach-for-freelance-developers-6-scripts-that-actually-get-replies-12ag</guid>
      <description>&lt;p&gt;Cold outreach has a reputation problem among freelancers, and the reputation is mostly earned. The average freelancer cold email: generic flattery up top, a wall of credentials, three links, a demand for a 30-minute call. Sent to five hundred people. Zero replies. Then the freelancer concludes "cold outreach does not work" and goes back to refreshing job boards.&lt;/p&gt;

&lt;p&gt;Cold outreach works. Bad cold outreach does not. The data — mostly vendor and aggregator studies of sales outreach, so treat the numbers as directional — points to two levers above everything else: personalization and follow-up. Roughly 55 percent of replies come from a follow-up, not the first email. Micro-lists of 500 to 1,000 hyper-targeted prospects dramatically outperform hundred-thousand-contact blasts.&lt;/p&gt;

&lt;p&gt;Every effective cold message follows the same shape: &lt;strong&gt;signal, proof, low-friction ask.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signal&lt;/strong&gt; proves the message is for them specifically — one sentence on something you actually observed. &lt;strong&gt;Proof&lt;/strong&gt; shows you understand the business problem, not just the technology. &lt;strong&gt;The ask&lt;/strong&gt; is one easy question, not a meeting demand. Keep cold emails to 75-125 words and LinkedIn DMs under 50. Here are six scripts built on that shape. Replace every [BRACKET] before sending.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The cold email
&lt;/h2&gt;

&lt;p&gt;Subject: [specific observation, e.g. "your checkout migration"]&lt;/p&gt;

&lt;p&gt;Hi [Name],&lt;/p&gt;

&lt;p&gt;[Signal: one sentence on something specific you observed.]&lt;/p&gt;

&lt;p&gt;[Proof: one sentence showing you understand the problem — e.g. "Those migrations usually stall on [specific pain point]; I have untangled that exact problem for [type of company]."]&lt;/p&gt;

&lt;p&gt;Worth a 15-minute call to compare notes on [specific topic]?&lt;/p&gt;

&lt;p&gt;[Your Name]&lt;br&gt;
[One-line credential]&lt;br&gt;
[One portfolio link only]&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The LinkedIn DM
&lt;/h2&gt;

&lt;p&gt;Hi [Name], [signal in one clause]. [Proof in one clause]. I help [niche] teams fix exactly that — worth a brief chat?&lt;/p&gt;

&lt;p&gt;No links in the first message.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The follow-up bump (day 4)
&lt;/h2&gt;

&lt;p&gt;Hi [Name], floating this back up in case it got buried. Still happy to share what I have seen work for [specific problem] at [type of company] — worth 15 minutes?&lt;/p&gt;

&lt;p&gt;Short, polite, adds nothing new. Most conversions happen on the first or second follow-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The new-value follow-up (day 10)
&lt;/h2&gt;

&lt;p&gt;Hi [Name], one more thought since my last note: [one genuinely useful observation or example relevant to their situation].&lt;/p&gt;

&lt;p&gt;Each follow-up must add new value. "Just checking in" trains prospects to ignore you.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The breakup email (day 18)
&lt;/h2&gt;

&lt;p&gt;Hi [Name], I will stop following up — no point being a pest. If [specific problem] becomes a priority down the road, I am easy to find. Good luck with [specific initiative].&lt;/p&gt;

&lt;p&gt;Then stop. Continuing past the breakup burns the relationship for zero expected return.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The referral request (past clients and peers)
&lt;/h2&gt;

&lt;p&gt;Hi [Name], hope [project] is going well. Quick favor: I am taking on [number] new [niche] projects this quarter. If you know one person dealing with [specific problem], I would appreciate an introduction — happy to return the favor anytime.&lt;/p&gt;

&lt;p&gt;Send this quarterly to every past client and friendly peer. Referral networks run on explicit asks, not good intentions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Track it in a spreadsheet
&lt;/h2&gt;

&lt;p&gt;You do not need a CRM. Columns: prospect, company, signal observed, date of first message, script used, follow-up dates, outcome. At 50 rows you have your own reply-rate benchmarks — real data about your market that no vendor study can give you.&lt;/p&gt;




&lt;p&gt;These scripts are one chapter of &lt;strong&gt;The Freelancer Client-Acquisition Kit&lt;/strong&gt; — 37 pages on getting freelance clients: channels, proposals, 2026 rate tables, contracts, retainers, referrals, and knowing when to walk away. Every stat labeled with its source. No guru fluff: &lt;a href="https://cjettoostudent.gumroad.com/l/qfckgb" rel="noopener noreferrer"&gt;https://cjettoostudent.gumroad.com/l/qfckgb&lt;/a&gt;&lt;/p&gt;

</description>
      <category>freelancing</category>
      <category>career</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The 10 contract clauses every freelance developer needs</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 04:17:50 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/the-10-contract-clauses-every-freelance-developer-needs-2i2k</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/the-10-contract-clauses-every-freelance-developer-needs-2i2k</guid>
      <description>&lt;p&gt;Most freelancers treat contracts as paperwork. The freelancers who get paid on time treat them as a system. Late-payment and non-payment trouble is widespread enough that second-hand industry figures put it at the majority of freelancers at some point — the exact numbers are soft, but the direction is not. Here are the ten clauses that separate the freelancers who get paid from the ones who chase invoices, drawn from collective practitioner experience. (Practical guidance, not legal advice — for large engagements, pay a local attorney to review your template.)&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Scope: what exactly are you delivering?
&lt;/h2&gt;

&lt;p&gt;Numbered deliverables, verifiable outcomes, acceptance criteria. "Migrated checkout flow" is not scope. "Checkout flow migrated to Stripe, handling 500 concurrent users, sub-2-second response, verified by load test" is scope. If it is not written down, it does not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The out-of-scope list
&lt;/h2&gt;

&lt;p&gt;The mirror of clause 1, and the highest-leverage block of text in the contract. List what you are NOT doing. Every scope dispute you will ever have is a sentence that should have been here.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Payment terms with a real deposit
&lt;/h2&gt;

&lt;p&gt;Total, schedule, method, currency — and a 30 to 50 percent deposit upfront, non-negotiable, before work begins. A client who will not pay a deposit is telling you politely that they will not pay the final invoice either.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Deadlines on both sides
&lt;/h2&gt;

&lt;p&gt;Your phase deadlines plus theirs: feedback within 5 business days, assets by agreed dates. Add a deemed-acceptance provision — if the client does not reject a deliverable within 15 business days, it is accepted. One unresponsive client can otherwise stall your cash flow indefinitely.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Revision limits
&lt;/h2&gt;

&lt;p&gt;State the number of revision rounds per deliverable, and define the line between a revision (correction inside the agreed scope) and a new project. The "just make the text bigger" pattern is how four free favors become a second unpaid job.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Change orders in writing, always
&lt;/h2&gt;

&lt;p&gt;Client requests change, you provide a written estimate, client approves in writing, then work begins. Verbal change orders evaporate the moment there is a dispute — which is exactly the moment you need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. A kill fee
&lt;/h2&gt;

&lt;p&gt;If the client cancels mid-project: 25 to 50 percent of the remaining contract value, plus payment for work performed through termination. Either side can terminate with written notice. The fee makes endings orderly instead of catastrophic.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. IP transfer timing
&lt;/h2&gt;

&lt;p&gt;State explicitly when intellectual property transfers: on final payment (safer for you) or on delivery. Refuse IP transfer before payment, and refuse blanket assignment of your pre-existing IP, methods, and tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Late-payment terms
&lt;/h2&gt;

&lt;p&gt;Interest on overdue invoices plus fixed compensation per late invoice. But prevention beats enforcement: deposits, milestone invoicing, and IP transfer on final payment only.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. A liability cap
&lt;/h2&gt;

&lt;p&gt;Cap your liability at the fees paid under the contract, and exclude consequential loss. One bug in a client's revenue system should not be able to bankrupt you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phrases that should make you flinch
&lt;/h2&gt;

&lt;p&gt;Watch for these in client-drafted contracts: "as may be reasonably requested" (unlimited scope in reasonable clothing), "contractor shall ensure client is satisfied" (an infinite revision clause), "pay-when-paid" (you underwriting someone else's credit risk for free), and uncapped liability. Strike or rewrite every one.&lt;/p&gt;




&lt;p&gt;This is one chapter of a bigger system. I put together &lt;strong&gt;The Freelancer Client-Acquisition Kit&lt;/strong&gt; — 37 pages covering outreach scripts, proposal structure, 2026 rate tables, contracts, retainers, and referrals, with every data point labeled by source and evidence strength. If this article saved you one bad contract, the kit will save you ten: &lt;a href="https://cjettoostudent.gumroad.com/l/qfckgb" rel="noopener noreferrer"&gt;https://cjettoostudent.gumroad.com/l/qfckgb&lt;/a&gt;&lt;/p&gt;

</description>
      <category>freelancing</category>
      <category>career</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>15 ATS Keywords Software Engineers Forget to Put on Their Resume</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 03:20:19 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/15-ats-keywords-software-engineers-forget-to-put-on-their-resume-4cal</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/15-ats-keywords-software-engineers-forget-to-put-on-their-resume-4cal</guid>
      <description>&lt;p&gt;Your resume lists React, TypeScript, and Node. So does everyone else's. Meanwhile the keyword that would have matched you to the role — the one describing work you &lt;em&gt;actually did&lt;/em&gt; — never made it onto the page, and the ATS moved on.&lt;/p&gt;

&lt;p&gt;This isn't about stuffing buzzwords. Recruiters search the ATS database with specific terms, and if your resume doesn't contain the terms for work you've genuinely done, you're invisible for roles you'd be great at. Engineers are especially guilty of this: we write what we built, not what it's &lt;em&gt;called&lt;/em&gt; in hiring language.&lt;/p&gt;

&lt;p&gt;Here are 15 terms engineers do constantly but forget to write down, grouped by category. For each one: what recruiters search, and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practices and process
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. CI/CD.&lt;/strong&gt; You push code and it deploys itself — that's CI/CD, and it's one of the most searched terms in backend and DevOps-adjacent postings. If you've touched GitHub Actions, Jenkins, GitLab CI, or CircleCI, the term "CI/CD" belongs on your resume. Name the tool too; recruiters search both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Code review.&lt;/strong&gt; "Reviewed pull requests" sounds mundane, so engineers leave it off. But "code review" signals seniority — it means someone trusted your judgment on other people's code. One bullet is enough: "Reviewed 10+ PRs weekly across a 6-engineer team."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Unit testing / integration testing.&lt;/strong&gt; Writing tests is invisible work that hiring managers specifically filter for. Don't just list "Jest" or "JUnit" in your skills — write the practice into a bullet: "Raised test coverage from 40% to 75% with Jest unit and integration tests." The words "unit testing" and "integration testing" are what get searched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Agile / Scrum.&lt;/strong&gt; Most teams run some flavor of it, and "Agile" still appears in a huge share of job descriptions. If you did sprint planning, standups, and retros, say "Agile/Scrum" plainly instead of assuming it's obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;5. SQL / query optimization.&lt;/strong&gt; Backend engineers write SQL daily and list "PostgreSQL" in skills — then never write the word "SQL" itself. Some ATS searches match the literal term "SQL" and miss the database name. Include both. And if you've ever fixed a slow query, "query optimization" is a high-signal phrase: "Optimized slow queries, cutting report generation time 60%."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Caching.&lt;/strong&gt; You added Redis to stop hammering the database — that's "caching," a term architects and backend leads search for. Write it as a strategy, not just a tool: "Implemented Redis caching layer, reducing database load 70%."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Data modeling.&lt;/strong&gt; Designed a schema? That's data modeling, and it's a distinct searched skill from just "knowing Postgres." Engineers treat schema design as part of the job; hiring managers treat it as a qualification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;8. RESTful APIs / API design.&lt;/strong&gt; "Built endpoints" is what you write; "RESTful API design" is what gets searched. If you designed routes, versioned an API, or wrote API contracts, use the formal term at least once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Authentication &amp;amp; authorization.&lt;/strong&gt; You implemented login with OAuth or JWT — recruiters search "authentication," "OAuth 2.0," and "JWT." "Set up auth" in a bullet matches nothing. "Implemented OAuth 2.0 authentication with JWT-based session management" matches three searches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Microservices.&lt;/strong&gt; Even if your architecture was "a few services talking to each other" rather than textbook microservices, the term describes the reality of distributed work: service boundaries, inter-service communication, independent deploys. If that's what you did, name it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;11. Docker / containerization.&lt;/strong&gt; "The app runs in Docker" is infrastructure reality for most teams now, and "Docker" plus "containerization" are baseline filters for backend and platform roles. If you wrote a Dockerfile or debugged a container issue, it counts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. Monitoring &amp;amp; logging.&lt;/strong&gt; You added Datadog dashboards or dug through CloudWatch logs at 2 AM — that's "monitoring and logging," and reliability-focused roles search for exactly that. One bullet: "Set up Datadog monitoring and alerting, cutting mean time to detection from hours to minutes."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. Cloud deployment.&lt;/strong&gt; Listing "AWS" in skills is table stakes; "deployed to AWS" or "cloud deployment" describes what you &lt;em&gt;did&lt;/em&gt; with it. Recruiters distinguish between engineers who've used a cloud console and engineers who've shipped to one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Craft
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;14. Performance optimization.&lt;/strong&gt; Made something faster — a query, a page load, a bundle size? The umbrella term "performance optimization" is what gets searched, and it covers all of it. Pair it with the number: "Cut initial page load 45% via code-splitting and lazy loading."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. Technical documentation / API documentation.&lt;/strong&gt; Wrote a README that saved onboarding time? Documented endpoints with OpenAPI/Swagger? "Documentation" signals the senior-level habit of making knowledge durable. Teams hiring senior engineers filter for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to put them (placement matters as much as the words)
&lt;/h2&gt;

&lt;p&gt;Keywords in the wrong place look stuffed; in the right place they look like a career. Three zones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Skills section — exact terms, both forms.&lt;/strong&gt; CI/CD (GitHub Actions), SQL (PostgreSQL), OAuth 2.0 / JWT. This is the machine-readable index. Mirror the job description's exact phrasing here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experience bullets — terms inside real work.&lt;/strong&gt; Every keyword above should appear inside a bullet describing something you actually built, with an outcome. "Implemented Redis caching, cutting p95 latency 50%" beats a lonely "Redis" in a skills list, for both the parser and the human.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summary line — the 3–4 heaviest hitters.&lt;/strong&gt; If the role is backend-heavy, your summary mentions REST APIs, SQL, and CI/CD. This is the first thing the parser and the recruiter both read.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And the rule that keeps all of this honest: &lt;strong&gt;only list what you've actually done.&lt;/strong&gt; Keyword optimization is translation, not invention — describing real work in the language hiring uses. The moment you list a tool you've never touched, it becomes lying, and it &lt;em&gt;will&lt;/em&gt; surface in a technical interview.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-minute keyword audit
&lt;/h2&gt;

&lt;p&gt;Pull up the last job description you applied to (or want to apply to). Highlight every technical term in it. Now search your resume for each one — Ctrl+F, literally. Every highlighted term that's missing but describes work you've done is a keyword to add this week, in a skills entry or a bullet.&lt;/p&gt;

&lt;p&gt;Do that against five job descriptions and a pattern emerges: the same 10–15 terms keep appearing. Those are your market's vocabulary. Learn to speak it on paper the way you already speak it in standup.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want a head start
&lt;/h2&gt;

&lt;p&gt;Rewriting a resume around the right keywords from a blank page is slow, which is why I put together the &lt;a href="https://cjettoostudent.gumroad.com/l/ats-resume-template-pack" rel="noopener noreferrer"&gt;ATS Resume Template Pack for Software Engineers&lt;/a&gt; ($15): three ATS-safe templates — classic single-column, skills-forward, and senior/staff — plus a setup guide and the full ATS checklist, so the formatting is handled and you can focus on the words.&lt;/p&gt;

&lt;p&gt;But honestly? Even if you never buy anything, run the Ctrl+F audit against your target job descriptions today. Ten minutes, and you'll see exactly which of these 15 terms your resume is missing.&lt;/p&gt;

</description>
      <category>career</category>
      <category>resume</category>
      <category>jobsearch</category>
    </item>
    <item>
      <title>How to Answer 'Tell Me About Yourself' as a Software Engineer (With a Word-for-Word Framework)</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 03:19:35 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/how-to-answer-tell-me-about-yourself-as-a-software-engineer-with-a-word-for-word-framework-2jk4</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/how-to-answer-tell-me-about-yourself-as-a-software-engineer-with-a-word-for-word-framework-2jk4</guid>
      <description>&lt;p&gt;"Tell me about yourself" is the first question in almost every interview, and most engineers waste it.&lt;/p&gt;

&lt;p&gt;The typical answer is a chronological tour of the resume: "Well, I graduated in 2019, then I worked at Company A where I did some frontend stuff, then I moved to Company B..." Two minutes in, the interviewer's eyes glaze over, and you've burned your best first impression reciting information they already read.&lt;/p&gt;

&lt;p&gt;Here's what the interviewer is actually listening for — it's only three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Can you communicate clearly?&lt;/strong&gt; If you can't explain your own career in 90 seconds, they doubt you can explain a technical decision to the team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are you relevant to this role?&lt;/strong&gt; They're pattern-matching your background against the job description in real time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you have direction?&lt;/strong&gt; A candidate who knows why they're here beats a candidate who's just... interviewing everywhere.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The framework below hits all three. It takes 60–90 seconds, and you can reuse it for every interview by swapping the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  The framework: Present → Prove → Pivot
&lt;/h2&gt;

&lt;p&gt;Three parts, in this order. Think of it as a commit message for your career: context, what changed, why it matters now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Present — who you are right now (2 sentences)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Current title, current stack, what you're working on today. Not your life story — your &lt;em&gt;current&lt;/em&gt; state. This orients the interviewer immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Prove — your trajectory in proof points (3–4 sentences)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick two or three highlights that are &lt;em&gt;relevant to this specific role&lt;/em&gt;, each with a concrete outcome. Numbers beat adjectives. "Led migration" is forgettable; "led the migration of our checkout flow to React, which cut page load time 40%" is memorable. Skip everything that doesn't serve the role you're interviewing for — that's what the resume is for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Pivot — why this role, why now (2 sentences)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Connect the dots forward. What about &lt;em&gt;this&lt;/em&gt; team, &lt;em&gt;this&lt;/em&gt; product, &lt;em&gt;this&lt;/em&gt; stack makes it the obvious next step? This is the part almost everyone skips, and it's the part that makes you sound intentional instead of desperate.&lt;/p&gt;

&lt;p&gt;Total: 7–8 sentences. About 90 seconds spoken. Then stop talking and let them ask the next question.&lt;/p&gt;

&lt;h2&gt;
  
  
  A word-for-word example (full-stack engineer)
&lt;/h2&gt;

&lt;p&gt;Here's the framework filled in, so you can hear what it sounds like. Treat it as a template — swap in your own stack, numbers, and story:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm a full-stack engineer currently building internal tooling with React on the frontend and Java with Spring Boot on the backend. Most of my recent work has been on our deployment pipeline and admin dashboards — the unglamorous stuff that keeps 200+ engineers shipping.&lt;/p&gt;

&lt;p&gt;Before this, I spent two years at a logistics startup where I rebuilt their order-tracking API in Node and Postgres. That cut our p95 response time from 900ms to under 200ms and took us from constant timeout complaints to basically zero. Earlier in my career I did frontend-heavy work, which is why I'm comfortable owning features end to end — I've been the person debugging both the React component and the database query behind it.&lt;/p&gt;

&lt;p&gt;I'm talking to you because this role is exactly that intersection: a product team that needs someone who can ship full features without handoffs, and your stack is the one I've spent the last three years getting good at."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Count the sentences: eight. Notice what it &lt;em&gt;doesn't&lt;/em&gt; do — no graduation year, no exhaustive job history, no "I'm passionate about" filler. Every sentence either proves competence or points at this role.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three mistakes that kill this answer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mistake 1: The autobiography.&lt;/strong&gt; Starting from college and walking forward year by year. The interviewer has your resume; they don't need the audiobook version. Start with &lt;em&gt;now&lt;/em&gt; and work backward only as far as relevance requires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 2: The five-minute monologue.&lt;/strong&gt; If your answer needs a breath in the middle, it's too long. Long answers signal you can't prioritize information — a bad look for an engineer. Time yourself: if it's over two minutes, cut the weakest proof point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 3: Vague and numberless.&lt;/strong&gt; "I've worked on various projects across the stack and collaborated with cross-functional teams." That sentence contains zero information. Every claim needs a what and a so-what: what you built, what changed because of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailor it in 10 minutes before every call
&lt;/h2&gt;

&lt;p&gt;The framework is fixed; the content changes per role. Before each interview:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the job description and highlight 3–4 must-haves&lt;/strong&gt; — the stack, the seniority signals, the domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick proof points that match.&lt;/strong&gt; If they want AWS experience, your Prove section leads with the AWS migration, not the CSS refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the Pivot last&lt;/strong&gt;, and make it specific. "Your team" is weak; "your checkout team rebuilding payments on Spring Boot" shows you did homework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say it out loud once&lt;/strong&gt;, timed. You'll catch the rambling parts immediately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One rehearsal out loud is worth ten silent read-throughs. Your mouth finds the awkward transitions your eyes skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want the full question bank
&lt;/h2&gt;

&lt;p&gt;"Tell me about yourself" is one question. A real interview loop has thirty more — behavioral rounds, system design, live coding, and the tricky ones like "why are you leaving your current role" where one wrong sentence ends the process.&lt;/p&gt;

&lt;p&gt;I put together &lt;a href="https://cjettoostudent.gumroad.com/l/tech-job-search-kit" rel="noopener noreferrer"&gt;The Tech Job Search Kit&lt;/a&gt; ($39) partly because I kept seeing engineers lose offers they should have won in the interview stage. It includes a full interview question bank with frameworks like the one above for behavioral and technical rounds, plus the resume templates, LinkedIn playbook, outreach scripts, and salary-negotiation guide.&lt;/p&gt;

&lt;p&gt;But even if you never buy anything: write your Present → Prove → Pivot tonight, time it, and say it out loud once. You'll walk into your next interview with the first 90 seconds already won.&lt;/p&gt;

</description>
      <category>career</category>
      <category>jobsearch</category>
      <category>interview</category>
    </item>
    <item>
      <title>Two $150k+ Job Offers Compared Line by Line — The Higher Salary Lost by $59k</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 03:15:16 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/i-compared-two-150k-job-offers-line-by-line-the-higher-salary-lost-by-70k-ecg</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/i-compared-two-150k-job-offers-line-by-line-the-higher-salary-lost-by-70k-ecg</guid>
      <description>&lt;p&gt;When the offers land, almost everyone does the same thing: compares the base salaries, picks the bigger number, and signs. It's understandable — base salary is the biggest, boldest number on the page.&lt;/p&gt;

&lt;p&gt;It's also the most misleading number on the page.&lt;/p&gt;

&lt;p&gt;Let me walk you through a worked example with two realistic offers. The base salaries are $15,000 apart. The actual gap, once you do the math honestly, is nearly $60,000 in year one — and roughly $234,000 over four years. Nobody would ever know that from the base salaries alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: base salary is one line of many
&lt;/h2&gt;

&lt;p&gt;An offer is a package: base salary, bonus, equity, 401(k) match, health insurance, PTO, and a dozen smaller items. Comparing two offers on base alone is like comparing two apartments by square footage while ignoring that one has no windows.&lt;/p&gt;

&lt;p&gt;Here's the framework. For every offer, calculate four-year total comp: Year 1 total = base + (bonus × realistic payout %) + (equity vesting in year 1) + 401(k) match + quantifiable benefits. Run this for each offer. The "lower base" offer wins far more often than you'd expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: the target is not the payout
&lt;/h2&gt;

&lt;p&gt;A "15% target bonus" means nothing until you know the payout history. If the company paid out 90% of target last year, that 15% is really 13.5%. If a startup paid 70% of a 10% target, that's a 7% bonus — less than half of what the headline suggests.&lt;/p&gt;

&lt;p&gt;Always ask: "What did this bonus actually pay out the last two years?" It's a completely normal question. Evasive answers are a red flag — treat a non-answer as a low payout in your math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Equity: RSUs are math, options are lottery tickets
&lt;/h2&gt;

&lt;p&gt;This is where the biggest hidden gaps live.&lt;/p&gt;

&lt;p&gt;Public-company RSUs are straightforward: shares × current stock price, vesting over four years with a one-year cliff. Discount 15–25% in your head for taxes and volatility, but it's real, countable money.&lt;/p&gt;

&lt;p&gt;Startup options are a different animal: value = (exit value − strike price) × shares × probability of a meaningful exit. Be brutally honest about that last term. Most startup equity ends up worth exactly $0. Unless the company is late-stage with clear metrics, treat options as a lottery ticket — a wonderful bonus if it hits, not compensation.&lt;/p&gt;

&lt;p&gt;Interrogate the mechanics: vesting schedule and cliff (four years with a one-year cliff is standard; five-year or back-loaded is a pay cut in disguise), post-termination exercise window (standard 90 days is a trap; two-plus years is far more employee-friendly), refresh grants (without them your comp quietly declines after year two), and current valuation and runway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits have dollar values — use them
&lt;/h2&gt;

&lt;p&gt;A $200/month health-insurance premium difference = $2,400/year. A 6% 401(k) match on a $160k salary = $9,600/year of free money. 15 vs. 25 days PTO = two full weeks of your life, every single year. These aren't perks. They're compensation with different names, and they belong in the spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The worked example
&lt;/h2&gt;

&lt;p&gt;Offer A (public company): $165k base, 15% target bonus, $120k RSUs over 4 years, 4% 401(k) match, 20 days PTO.&lt;br&gt;
Offer B (Series B startup): $150k base, 10% target bonus, 0.15% equity (options), 3% 401(k) match, "unlimited" PTO.&lt;/p&gt;

&lt;p&gt;Year one, counted honestly: Offer A — base $165,000; bonus $22,275 (90% payout); equity $30,000 (RSUs); 401(k) $6,600; total $223,875. Offer B — base $150,000; bonus $10,500 (70% payout); equity $0 (options valued at $0 until exit); 401(k) $4,500; total $165,000.&lt;/p&gt;

&lt;p&gt;That's a $58,875 gap in year one from a $15,000 base-salary difference. Over four years the countable gap grows to roughly $234,000.&lt;/p&gt;

&lt;p&gt;For the startup equity to close that gap, you'd need something like a 20x outcome and a successful exit and staying all four years. Possible — but that's the lottery-ticket framing, and you should be honest with yourself about the odds.&lt;/p&gt;

&lt;p&gt;To be clear: there are valid reasons to take Offer B anyway. Broader scope, faster learning, a team you admire, genuine conviction in the company — all legitimate. Just don't take it because the equity "might be worth millions" without running this math first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you sign anything
&lt;/h2&gt;

&lt;p&gt;Two rules. First: never accept on the spot. "This is exciting — can I have a few days to review everything?" Always. Second: the first offer is rarely the best offer. A polite counter with justification works far more often than engineers expect. Pick one thing to push on (base, equity, or signing bonus); signing bonuses are often the easiest yes since they're one-time. Negotiate over email when you can: it gives you time to think, creates a record, and removes real-time pressure.&lt;/p&gt;

&lt;p&gt;The complete system — when to discuss comp at each interview stage, the exact script for answering "what are your salary expectations" without anchoring low, five counter-offer email templates, the equity interrogation checklist, and the full list of what never to say — is in the salary negotiation guide inside The Tech Job Search Kit ($39): &lt;a href="https://cjettoostudent.gumroad.com/l/tech-job-search-kit" rel="noopener noreferrer"&gt;https://cjettoostudent.gumroad.com/l/tech-job-search-kit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do the math before you sign. It's the highest-paid hour of your career.&lt;/p&gt;

</description>
      <category>career</category>
      <category>salary</category>
      <category>jobsearch</category>
    </item>
    <item>
      <title>The 30-Minute LinkedIn Routine That Makes Recruiters Message You First</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 03:14:32 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/the-30-minute-linkedin-routine-that-makes-recruiters-message-you-first-1bml</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/the-30-minute-linkedin-routine-that-makes-recruiters-message-you-first-1bml</guid>
      <description>&lt;p&gt;Most engineers treat LinkedIn like a digital filing cabinet: set it up once during a job hunt, then forget it exists. Meanwhile, recruiters live on LinkedIn. A huge share of tech hiring starts with a recruiter search — not with your application.&lt;/p&gt;

&lt;p&gt;That means your LinkedIn profile isn't a resume backup. It's a landing page. And right now, for most engineers, it's a landing page with no headline, no call to action, and no reason for anyone to stay.&lt;/p&gt;

&lt;p&gt;The good news: you don't need to become a "LinkedIn influencer." You need about 30 focused minutes a week — once the profile itself is fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix the headline first — it's a search field, not a job title
&lt;/h2&gt;

&lt;p&gt;Your headline is the single most-searched field on LinkedIn. Recruiters search by title + technology + location. "Software Engineer at Acme Corp" wastes that field on information LinkedIn already displays in your experience entry.&lt;/p&gt;

&lt;p&gt;Write for the search instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active job seeker: Full-Stack Engineer | React, TypeScript, Node.js | SaaS &amp;amp; Fintech | Open to Remote Roles&lt;/li&gt;
&lt;li&gt;Specialist: Backend Engineer specializing in distributed systems | Java/Spring Boot | 10M+ req/day&lt;/li&gt;
&lt;li&gt;Career switcher: Frontend Engineer | React, TypeScript | ex-Teacher bringing curriculum-design rigor to UI&lt;/li&gt;
&lt;li&gt;Senior/staff: Staff Software Engineer | Platform &amp;amp; Infrastructure | Led teams of 8 | Fintech&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few rules: use standard titles recruiters actually type ("Frontend Engineer," not "Code Ninja"). Include your top 3–4 technologies — these are keyword fields. Set your location to your metro area or "Remote," because recruiters filter by it.&lt;/p&gt;

&lt;p&gt;And turn on "Open to Work." The old advice that it signals desperation is outdated — it increases inbound. If you're employed and hunting quietly, use the private "visible to recruiters only" setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewrite your About section in three paragraphs
&lt;/h2&gt;

&lt;p&gt;First person. No buzzword soup. Three short paragraphs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who you are (2 sentences): "I'm a [title] with [X] years building [what] with [stack]. Currently focused on [specialty]."&lt;/li&gt;
&lt;li&gt;Proof (3–4 sentences): your biggest achievement with a number, one more proof point, and one genuine engineering value. Pick one that's actually true.&lt;/li&gt;
&lt;li&gt;Call to action (1–2 sentences): what you're looking for and the best way to reach you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Compare that to what most profiles have: "Passionate software engineer with experience in full-stack development." That could describe literally every engineer on the platform. It's invisible. Replace every adjective with evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Curate your Featured section — it's above the fold
&lt;/h2&gt;

&lt;p&gt;The Featured section renders above your experience, which makes it prime real estate. Put exactly three things there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your best proof of work — a blog post, a conference talk, a project with your name on it.&lt;/li&gt;
&lt;li&gt;Your resume as a PDF. Recruiters who find your profile want it in one click.&lt;/li&gt;
&lt;li&gt;One personality signal — an open-source contribution, a thoughtful post, a side project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rotate these quarterly. A featured post from 2021 signals a dormant profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prune your skills list
&lt;/h2&gt;

&lt;p&gt;Keep the list to 15–20 max. Pin your top 3 to match your target roles. Delete aspirational skills you can't defend in an interview: if a recruiter finds you via "Kubernetes" and you fumble the technical screen, you've burned a lead.&lt;/p&gt;

&lt;p&gt;One more high-leverage move: ask 3–5 former colleagues for short written recommendations (not endorsements — actual recommendations, a paragraph each). The ask is simple: "I'm job hunting and building out my LinkedIn — would you be open to writing 2–3 sentences about working together on [project]? Happy to return the favor."&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't copy-paste your resume into the Experience section
&lt;/h2&gt;

&lt;p&gt;Your LinkedIn experience is not your resume. Different medium, different reader, different rules. Keep 3–4 bullets per role (fewer than a resume — LinkedIn readers skim harder), and make the first bullet the headline achievement. Include one or two things a resume would cut for space: the interesting side project, the tech talk you gave. Attach media where it exists — the architecture write-up, the shipped product link.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-minute weekly routine
&lt;/h2&gt;

&lt;p&gt;Monday (10 min) — Visibility. Comment thoughtfully on 3–5 posts from engineers, hiring managers, or companies you follow. Not "Great post!" — add a sentence of substance or a genuine question.&lt;/p&gt;

&lt;p&gt;Wednesday (10 min) — Outreach. Send 5 connection requests with personalized notes to engineers at target companies, plus 2 to recruiters. Always add a note — blank requests from strangers get ignored.&lt;/p&gt;

&lt;p&gt;Friday (10 min) — Content or maintenance. Every other week, post something small: a thing you learned, a project update. On the off weeks, improve one profile section, request a recommendation, or prune your skills list.&lt;/p&gt;

&lt;p&gt;That's the whole system. Consistency beats intensity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want the full playbook?
&lt;/h2&gt;

&lt;p&gt;This is the short version. The complete LinkedIn playbook — all five headline formulas with filled examples, the About-section template, the Experience-vs-resume structure rules, connection and referral DM scripts, the skills triage, and a full before/after profile teardown — is one of seven components in The Tech Job Search Kit ($39): &lt;a href="https://cjettoostudent.gumroad.com/l/tech-job-search-kit" rel="noopener noreferrer"&gt;https://cjettoostudent.gumroad.com/l/tech-job-search-kit&lt;/a&gt; — which also covers ATS resumes, interview prep, outreach scripts, and salary negotiation.&lt;/p&gt;

&lt;p&gt;Your move: open your profile right now and rewrite the headline. It's the highest-leverage five minutes in your entire job search.&lt;/p&gt;

</description>
      <category>career</category>
      <category>jobsearch</category>
      <category>linkedin</category>
    </item>
    <item>
      <title>Why Your Engineering Resume Might Never Reach a Human (And How to Fix It in 20 Minutes)</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 02:56:34 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/why-your-engineering-resume-gets-rejected-by-the-ats-and-how-to-fix-it-in-20-minutes-1o6</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/why-your-engineering-resume-gets-rejected-by-the-ats-and-how-to-fix-it-in-20-minutes-1o6</guid>
      <description>&lt;p&gt;You can be the perfect candidate for a role and never get a callback — because your resume sat in a database nobody ever queried.&lt;/p&gt;

&lt;p&gt;Most mid-to-large companies run every application through an Applicant Tracking System (ATS) before a recruiter glances at it. The ATS doesn't "read" your resume like a person. It parses it: rips the text out of your file and tries to slot it into fields — name, title, skills, experience. If your resume is built like a design portfolio, the parser mangles it, and your application shows up as gibberish.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable truth: the fanciest resume template is often the worst one for getting hired.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the ATS actually does to your resume
&lt;/h2&gt;

&lt;p&gt;Think of the parser as a very literal, very dumb reader. It goes top to bottom, left to right, extracting plain text. Anything that isn't plain text in a single column confuses it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Multi-column layouts scramble your content.&lt;/strong&gt; That sleek two-column template — skills sidebar on the left, experience on the right — gets read as one interleaved stream of text. "React" from your sidebar lands in the middle of a job bullet. Some parsers try to reconstruct the order and fail; the result is a jumbled mess no recruiter will bother decoding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Tables, text boxes, and graphics are invisible or garbled.&lt;/strong&gt; Parsers frequently can't read text inside tables or text boxes at all. Your carefully formatted experience section can parse as empty. Icons, skill-bar charts, and headshots are worse than useless — the parser ignores them, and the human never gets the chance to see them because the parse failed first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Headers and footers get skipped.&lt;/strong&gt; Some parsers ignore header and footer content entirely. If your name, phone number, and email live in the header, the system may file your application with no contact info attached. Keep everything in the body of the document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Non-standard fonts and sloppy filenames create friction.&lt;/strong&gt; Decorative or custom fonts can fail to embed or render in the parser — stick to Calibri, Arial, Helvetica, Georgia, or Garamond. And when a recruiter downloads &lt;code&gt;resume_final_v3_UPDATED.pdf&lt;/code&gt;, it looks sloppy. Hiring is a game of small signals; don't waste any.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Keyword mismatch kills you silently.&lt;/strong&gt; Recruiters search the ATS database by keywords: "React," "TypeScript," "AWS." If the job posting says "TypeScript" and your resume only says "TS," some parsers won't match them. If the posting says "Amazon Web Services (AWS)," use both forms at least once — parsers and humans search both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 20-minute fix
&lt;/h2&gt;

&lt;p&gt;You don't need a redesign. You need a de-design. Here's the checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Go single-column.&lt;/strong&gt; One column, top to bottom. No sidebars, no exceptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strip tables, text boxes, columns, graphics, icons, and headshots.&lt;/strong&gt; All of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a standard font&lt;/strong&gt; at 10.5pt minimum for body text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move contact info into the body&lt;/strong&gt; — name, phone, email, city/state, LinkedIn URL, GitHub URL, all as plain text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use simple section headers&lt;/strong&gt;: EXPERIENCE, SKILLS, EDUCATION. Bold or slightly larger is fine; colored banners are not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep dates consistent everywhere&lt;/strong&gt;: &lt;code&gt;Jan 2023 – Present&lt;/code&gt;, not &lt;code&gt;1/23 – now&lt;/code&gt; in one place and something else in another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use standard job titles.&lt;/strong&gt; An ATS searching "Frontend Engineer" may never match "UI Wizard" or "Code Ninja."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export as PDF from Google Docs or Word&lt;/strong&gt; (not a scanned image, not "Print to PDF" from a design tool), and name it &lt;code&gt;FirstName-LastName-Resume.pdf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test selectability&lt;/strong&gt;: open the PDF and try to highlight your phone number with your cursor. If you can't select it as text, the ATS can't read it either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the 10-second plaintext test&lt;/strong&gt;: copy your entire resume and paste it into a plain text editor. If it reads in a sensible order, the parser will handle it. If it's scrambled, fix the layout until it isn't.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last test is the single most useful thing in this article. Run it before every application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keywords: mirror, don't stuff
&lt;/h2&gt;

&lt;p&gt;Pull up the job description and note the exact terms it uses — "React," "Node.js," "PostgreSQL," "CI/CD." Now check your resume: do those exact terms appear in your experience bullets, describing work you actually did?&lt;/p&gt;

&lt;p&gt;Honest keyword optimization is just good writing: describe your work using standard industry terms. If you built APIs in Express, write "Node.js/Express" at least once, because that's what the recruiter searches. If the posting says "Kubernetes," and you've genuinely used it, make sure the word appears — don't assume "container orchestration" will match.&lt;/p&gt;

&lt;p&gt;What not to do: a hidden block of white-on-white keywords at the bottom. Some parsers flag keyword stuffing, and if a human ever selects the text and sees it, your credibility is gone. Write the terms into real bullets about real work.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing: bullets matter more than templates
&lt;/h2&gt;

&lt;p&gt;An ATS-safe resume gets you &lt;em&gt;parsed&lt;/em&gt;. But a human still has to want to interview you, and the difference between a bullet that gets skimmed and a bullet that gets you a call is specificity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weak:&lt;/strong&gt; "Worked on the company dashboard using React."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong:&lt;/strong&gt; "Rebuilt the analytics dashboard in React + TypeScript, cutting load time from 4.2s to 1.1s and lifting weekly active usage 35% among 2,000+ users."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same work. One gets interviews; the other doesn't. The formula: specific action + quantified outcome + scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want a head start
&lt;/h2&gt;

&lt;p&gt;Getting all of this right from a blank page takes a while, which is why I put together the ATS Resume Template Pack for Software Engineers ($15): three ATS-safe templates — a classic single-column, a skills-forward layout for career switchers and bootcamp grads, and a senior/staff version — plus the 20-minute setup guide and the full ATS checklist from this article: &lt;a href="https://cjettoostudent.gumroad.com/l/ats-resume-template-pack" rel="noopener noreferrer"&gt;https://cjettoostudent.gumroad.com/l/ats-resume-template-pack&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But honestly? Even if you never buy anything, run the plaintext test on your resume today. Twenty minutes, and you’ll never again wonder whether a parser mangled your application.&lt;/p&gt;

</description>
      <category>career</category>
      <category>resume</category>
      <category>beginners</category>
      <category>jobsearch</category>
    </item>
    <item>
      <title>How to Actually Diagnose a Postgres Deadlock in a Spring Boot App</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 29 Sep 2026 02:34:56 +0000</pubDate>
      <link>https://dev.to/chris_ad4e762f2200b40f49a/how-to-actually-diagnose-a-postgres-deadlock-in-a-spring-boot-app-163i</link>
      <guid>https://dev.to/chris_ad4e762f2200b40f49a/how-to-actually-diagnose-a-postgres-deadlock-in-a-spring-boot-app-163i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; Spring Boot 3.x, Postgres via Docker, basic JPA.&lt;/p&gt;

&lt;p&gt;The pager goes off: &lt;code&gt;PSQLException: deadlock detected&lt;/code&gt;. Your first instinct is to slap &lt;code&gt;@Retryable&lt;/code&gt; on the service and go back to sleep. Sometimes that even works — which is exactly why it's dangerous. Retry hides the bug; the deadlock keeps happening, just quieter. In this post I'll show you how to reproduce a Postgres deadlock in five minutes, read the error properly, map it back to your code, and fix the root cause — a lock-ordering bug — instead of papering over it with retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it in 5 minutes
&lt;/h2&gt;

&lt;p&gt;Two &lt;code&gt;@Transactional&lt;/code&gt; methods locking the same rows in opposite order. Docker Postgres, Spring Boot 3.x:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransferService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@PersistenceContext&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;EntityManager&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Thread A: locks account 1, then account 2&lt;/span&gt;
    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;transferAtoB&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;a1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"SELECT a FROM Account a WHERE a.id = :id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLockMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LockModeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSingleResult&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// widen the race window so it deadlocks reliably&lt;/span&gt;
        &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"SELECT a FROM Account a WHERE a.id = :id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLockMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LockModeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSingleResult&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Thread B: locks account 2, then account 1 — opposite order&lt;/span&gt;
    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;transferBtoA&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;a1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... same shape, locks a2 first, then a1&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run both concurrently. You'll get the deadlock on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the error — actually read it
&lt;/h2&gt;

&lt;p&gt;Postgres tells you everything in the &lt;code&gt;DETAIL&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: deadlock detected
DETAIL: Process 1234 waits for ShareLock on transaction 5678; blocked by process 9012.
Process 9012 waits for ShareLock on transaction 1234; blocked by process 1234.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two processes, each waiting on the other. That's the whole story: &lt;strong&gt;a lock-ordering bug&lt;/strong&gt;, not a database problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find it while it's happening
&lt;/h2&gt;

&lt;p&gt;Three things to run against Postgres during (or right after) an incident:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 1. Who is blocked right now?&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait_event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait_event&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_database&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pg_backend_pid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;wait_event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Lock'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 2. The full blocker → blocked graph&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;blocked_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;blocked_pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;blocking_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;blocking_pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;blocked_activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;blocked_query&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_locks&lt;/span&gt; &lt;span class="n"&gt;blocked_locks&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt; &lt;span class="n"&gt;blocked_activity&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;blocked_activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blocked_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_locks&lt;/span&gt; &lt;span class="n"&gt;blocking_locks&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;blocking_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locktype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blocked_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locktype&lt;/span&gt;
 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;blocking_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blocked_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;
 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;blocking_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;blocked_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt; &lt;span class="n"&gt;blocking_activity&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;blocking_activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blocking_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;blocked_locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;granted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to catch the &lt;em&gt;next&lt;/em&gt; one with full context, enable this once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;log_lock_waits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_reload_conf&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every lock wait over &lt;code&gt;deadlock_timeout&lt;/code&gt; lands in the Postgres log with the offending query attached.&lt;/p&gt;

&lt;p&gt;Once you have the blocked query text, trace it back to your code. The query in &lt;code&gt;blocked_query&lt;/code&gt; is the raw SQL Hibernate issued — table names, locked columns, and the &lt;code&gt;WHERE&lt;/code&gt; clause it locked on. Enable SQL logging in dev (&lt;code&gt;spring.jpa.show-sql=true&lt;/code&gt; or a &lt;code&gt;DataSource&lt;/code&gt; proxy like p6spy) so you can see every query your app issues with the repository method that triggered it. Take the SQL from the blocker query, grep it against your query log, and you'll land on the exact repository methods or &lt;code&gt;@Query&lt;/code&gt; annotations on both sides of the conflict — the two code paths whose lock acquisition order forms the cycle. In our example, the log would show one path locking &lt;code&gt;account&lt;/code&gt; rows ordered 1 → 2 and another locking them 2 → 1, and that's your bug: the two methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: order your locks
&lt;/h2&gt;

&lt;p&gt;The rule: &lt;strong&gt;within a transaction, always acquire row locks in the same deterministic order&lt;/strong&gt; — e.g., ascending ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT a FROM Account a WHERE a.id IN :ids ORDER BY a.id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Lock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LockModeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;lockAllOrdered&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ids"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both threads now lock account 1 before account 2. No cycle, no deadlock. One thread waits briefly; nobody dies.&lt;/p&gt;

&lt;p&gt;Two more things people get wrong:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Isolation levels.&lt;/strong&gt; Postgres defaults to &lt;code&gt;READ COMMITTED&lt;/code&gt;, which is correct for most of this. Reaching for &lt;code&gt;REPEATABLE READ&lt;/code&gt; or &lt;code&gt;SERIALIZABLE&lt;/code&gt; &lt;em&gt;increases&lt;/em&gt; deadlock/failure rates — don't escalate isolation to fix a locking bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry.&lt;/strong&gt; Retry is for the &lt;em&gt;unavoidable&lt;/em&gt; remainder (two genuinely concurrent writers), not the systematic case. And if you retry, be specific:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Retryable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;retryFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;DeadlockLoserDataAccessException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
               &lt;span class="nc"&gt;CannotAcquireLockException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;maxAttempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@Backoff&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrying on generic &lt;code&gt;DataAccessException&lt;/code&gt; will retry things that will never succeed. Be precise.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Deadlocks are a lock-ordering bug in your code, not a Postgres tuning problem. Reproduce → read the DETAIL → map it to the two code paths → lock in a consistent order → retry only the narrow exception types.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm turning this into a full field guide — 10 production incidents like this (N+1s, pool exhaustion, OOMs, Kafka lag…) each with diagnosis steps, fixes, and prevention checklists, plus a complete Spring Boot → AWS deployment walkthrough. Preorder is $49 (goes to $79 at launch): &lt;a href="https://cjettoostudent.gumroad.com/l/spring-boot-production-rescue-kit" rel="noopener noreferrer"&gt;https://cjettoostudent.gumroad.com/l/spring-boot-production-rescue-kit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>postgres</category>
      <category>database</category>
    </item>
  </channel>
</rss>
