<?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: zhihu wu</title>
    <description>The latest articles on DEV Community by zhihu wu (@zhihu_wu_dea1d82af01a04d7).</description>
    <link>https://dev.to/zhihu_wu_dea1d82af01a04d7</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%2F3921997%2Fa775d61f-ed57-461b-ac46-ed108350189e.png</url>
      <title>DEV Community: zhihu wu</title>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zhihu_wu_dea1d82af01a04d7"/>
    <language>en</language>
    <item>
      <title>5 Cron Gotchas That Silently Break Your Jobs (and How to Fix Them)</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:04:02 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/5-cron-gotchas-that-silently-break-your-jobs-and-how-to-fix-them-1658</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/5-cron-gotchas-that-silently-break-your-jobs-and-how-to-fix-them-1658</guid>
      <description>&lt;p&gt;Cron looks simple: five fields and a command. But some of its oldest quirks are exactly where jobs fail silently — usually at 3 AM when nobody is watching. Here are five gotchas I've hit (or watched teammates hit), with the fixes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Day-of-month and day-of-week are OR, not AND&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;30 2 1 * 1&lt;/code&gt; does NOT mean "the first of the month AND Monday." Cron fires the job if EITHER field matches — so this runs at 2:30 AM on the 1st of every month AND at 2:30 AM every Monday. To target a true "first Monday," keep the schedule simple and add a guard inside the command that checks the day of month is within the first 7 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Cron runs in the server's timezone, not yours&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0 0 * * *&lt;/code&gt; means midnight in whatever timezone the host is set to. Docker containers default to UTC, and cloud VMs are often UTC too — so your "daily midnight" job can silently run at 8 AM your time. Check with the &lt;code&gt;date&lt;/code&gt; command in the same environment, and use &lt;code&gt;CRON_TZ&lt;/code&gt; if your cron implementation supports it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The percent sign means newline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a crontab entry, &lt;code&gt;%&lt;/code&gt; is translated to a newline. A command like &lt;code&gt;date +%Y-%m-%d&lt;/code&gt; will break or behave oddly. Escape it as &lt;code&gt;\%&lt;/code&gt; — or better, put the logic in a script file and keep the crontab line trivial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Cron gives you a minimal environment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cron does not source your shell profile. PATH is often just &lt;code&gt;/usr/bin:/bin&lt;/code&gt;, so anything installed via nvm, pyenv, or a project virtualenv "works in my terminal" but fails only under cron. Fix: use absolute paths in the script and export PATH at the top of the script itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Overlaps and silence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a job runs longer than its interval, cron happily starts a second instance — wrapped in &lt;code&gt;flock&lt;/code&gt; to prevent it. And if stdout isn't redirected and no mail daemon exists, error output quietly disappears. Redirect to a log file and add an external health check so a dead job actually alerts you.&lt;/p&gt;

&lt;p&gt;Gotchas like these are why I test a schedule before deploying it. When I need to decode an unfamiliar expression or build one from scratch, I use the free &lt;a href="https://codetoolbox.pro/tools/cron-generator" rel="noopener noreferrer"&gt;CodeToolbox Cron Generator&lt;/a&gt; — it validates each field and explains the schedule in plain language, and everything runs locally in your browser, so nothing gets uploaded.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>UUIDv7 vs UUIDv4: Why Time-Sortable IDs Are Winning (and When v4 Is Still Right)</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Fri, 04 Sep 2026 13:03:21 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/uuidv7-vs-uuidv4-why-time-sortable-ids-are-winning-and-when-v4-is-still-right-5h1m</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/uuidv7-vs-uuidv4-why-time-sortable-ids-are-winning-and-when-v4-is-still-right-5h1m</guid>
      <description>&lt;p&gt;If you have ever used a random UUID as a database primary key and watched writes slow down as the table grew, it was not your imagination — and the fix became an official standard back in May 2024.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem with random keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A UUID v4 is 122 bits of pure randomness. Insert a few million rows and every new row lands at a random position in your B-tree index. The database keeps splitting pages and evicting cache lines, and your writes pay for it. On write-heavy tables this fragmentation is a real, measurable cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What UUIDv7 changes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UUIDv7 keeps the familiar 128-bit shape but replaces the first 48 bits with a Unix timestamp in milliseconds. The remaining bits are still random. Because IDs generated close together are close in value, new rows append near the end of the index instead of poking holes in the middle. B-tree locality improves, page splits drop, and you get a free bonus: rows are roughly sortable by creation time, which makes "latest first" queries and cursor pagination noticeably cheaper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adoption is already here&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.14 added &lt;code&gt;uuid.uuid7()&lt;/code&gt; to the standard library&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;uuid&lt;/code&gt; npm package ships &lt;code&gt;uuidv7()&lt;/code&gt; (v11+)&lt;/li&gt;
&lt;li&gt;Go's &lt;code&gt;google/uuid&lt;/code&gt; has &lt;code&gt;NewV7()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Postgres has the &lt;code&gt;pg_uuidv7&lt;/code&gt; extension, and built-in support keeps spreading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The trade-offs you should know&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UUIDv7 leaks timing information — the timestamp is right there in the ID. That is fine for database keys but wrong for security tokens, session IDs, or anything where you do not want to reveal when a record was created. Use v4 or a dedicated random token there.&lt;/p&gt;

&lt;p&gt;Also, "time-sortable" is best-effort, not a guarantee: if one process generates IDs in a tight loop, or a machine clock rolls back, ordering can drift. For most backends that is noise; for strict event ordering you need a sequence, not a UUID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When v4 is still the right call&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your table is read-heavy or sits under roughly ten million rows, v4 is perfectly fine — the index overhead is negligible compared with the simplicity. v4 is also what &lt;code&gt;crypto.randomUUID()&lt;/code&gt; in browsers and Node.js produces today, and it works everywhere with zero new tooling. Do not migrate an existing happy system just to chase v7.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it yourself&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I need a UUID in the middle of debugging, I use a free client-side generator at CodeToolbox — everything runs in the browser, nothing is uploaded, and the page includes a quick v4 vs v7 vs ULID comparison if you want the full picture before choosing an ID strategy for your next project.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why Your JSON Won't Parse: 6 Invisible Character Traps (and How to Fix Them)</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Tue, 01 Sep 2026 13:02:29 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/why-your-json-wont-parse-6-invisible-character-traps-and-how-to-fix-them-4o77</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/why-your-json-wont-parse-6-invisible-character-traps-and-how-to-fix-them-4o77</guid>
      <description>&lt;p&gt;You've written JSON that looks perfectly fine. Braces match, keys are quoted, commas are in place. Yet &lt;code&gt;JSON.parse()&lt;/code&gt; throws &lt;code&gt;Unexpected token&lt;/code&gt; at a position that makes no sense.&lt;/p&gt;

&lt;p&gt;Sound familiar? Here are six invisible traps that break perfectly "correct-looking" JSON — and how to spot each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Trailing commas&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{"a": 1, "b": 2,}&lt;/code&gt; — the comma after the last item is invalid in strict JSON (it's legal in JavaScript objects, which is why it sneaks in). Editors rarely flag it. Drop the final comma and it parses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Smart (curly) quotes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Copy JSON out of a word processor, email client, or chat app and the quotes come back curly: " and " instead of ". JSON only accepts the straight double quote (U+0022). Curly quotes are the #1 cause of "my JSON works in my head but not in code".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The hidden BOM&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Files saved as UTF-8 with BOM start with an invisible byte sequence before the first &lt;code&gt;{&lt;/code&gt;. &lt;code&gt;JSON.parse()&lt;/code&gt; sees it as an unexpected token — the classic "error at line 1, column 1" mystery. Save as UTF-8 without BOM and it disappears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Non-breaking spaces&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A space that isn't a space. NBSP (U+00A0) copied from formatted text looks like whitespace to your eyes but is invalid JSON syntax when it hides between keys and colons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Single quotes and unquoted keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{'a': 1}&lt;/code&gt; and &lt;code&gt;{a: 1}&lt;/code&gt; are valid JavaScript — but not JSON. RFC 8259 requires double-quoted keys and values. Hand-written config files drift into JS-object syntax all the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Duplicate keys don't throw — they silently overwrite&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{"id": 1, "id": 2}&lt;/code&gt; parses fine, but the first value is silently discarded. Different parsers disagree on which value wins, so this causes subtle, hard-to-debug data loss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to debug these fast&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't eyeball it — get a precise error location. Paste the payload into a formatter that reports the exact line and column of the first syntax error (a BOM shows up as an error at position 0, a dead giveaway). A collapsible tree view also makes duplicate keys easy to spot after a successful parse.&lt;/p&gt;

&lt;p&gt;I use the free &lt;a href="https://codetoolbox.pro/tools/json-formatter.html" rel="noopener noreferrer"&gt;JSON Formatter&lt;/a&gt; on CodeToolbox for this — it's 100% client-side (your data never leaves the browser), reports exact error positions, and switches between format / minify / validate modes. No signup, no uploads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 30-second fix checklist&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace curly quotes with straight quotes&lt;/li&gt;
&lt;li&gt;Drop trailing commas&lt;/li&gt;
&lt;li&gt;Save without BOM&lt;/li&gt;
&lt;li&gt;Double-quote every key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nine times out of ten it's one of these. Happy parsing!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Compress Images for the Web Without Losing Quality</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Mon, 31 Aug 2026 13:08:19 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-to-compress-images-for-the-web-without-losing-quality-152c</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-to-compress-images-for-the-web-without-losing-quality-152c</guid>
      <description>&lt;p&gt;Images are the single biggest contributor to slow page loads — yet most developers only compress them once, at export time, and never think about it again. A 5MB photo that should weigh 150KB is the difference between a page that loads in 0.8s and one that crawls past 4s on mobile.&lt;/p&gt;

&lt;p&gt;Here are the compression techniques that actually matter, in order of impact:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Resize before you compress.&lt;/strong&gt;&lt;br&gt;
If your original is 4000x3000px but your layout only needs 1200px wide, every pixel beyond that is wasted bytes. Resizing first, then compressing, routinely turns a 5MB photo into under 100KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use WebP as your output format.&lt;/strong&gt;&lt;br&gt;
WebP is typically 25-35% smaller than JPEG at the same visual quality, and every modern browser supports it. For screenshots and UI mockups, converting PNG to WebP is often an 80-90% reduction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Find the quality breakpoint, don't guess.&lt;/strong&gt;&lt;br&gt;
Start at 100% quality and slide down while previewing the result side by side. The moment you notice degradation, bump back up 5%. For most web images that lands between 70-80% — visually near-identical, 60-80% smaller.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Match quality to usage.&lt;/strong&gt;&lt;br&gt;
Hero images and photography portfolios deserve ~85%. Thumbnails and icons can drop to 60% where detail matters less. One setting for everything means you're either bloating thumbnails or degrading heroes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Watch your Core Web Vitals.&lt;/strong&gt;&lt;br&gt;
Image weight is the #1 factor in Largest Contentful Paint (LCP). Cutting image size on your top pages can shave seconds off LCP on mobile — and since Google uses LCP as a ranking signal, it's a free SEO win alongside the speed gain.&lt;/p&gt;

&lt;p&gt;When I need to batch-process images quickly, I use a browser-based compressor at &lt;a href="https://codetoolbox.pro/tools/image-compressor" rel="noopener noreferrer"&gt;codetoolbox.pro/tools/image-compressor&lt;/a&gt; — it runs entirely locally via the Canvas API, so nothing gets uploaded to a server, which matters when you're compressing screenshots of internal dashboards or customer data.&lt;/p&gt;

&lt;p&gt;The rule of thumb: if a page's images are under 100KB total, you're probably fine. If a single image is over 500KB, it's costing you ranking and conversion. Compress once, deploy everywhere.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Hashing vs Encryption vs Encoding: The Difference Every Developer Gets Wrong</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Sat, 29 Aug 2026 13:08:58 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/hashing-vs-encryption-vs-encoding-the-difference-every-developer-gets-wrong-1d7b</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/hashing-vs-encryption-vs-encoding-the-difference-every-developer-gets-wrong-1d7b</guid>
      <description>&lt;p&gt;Hashing, encryption, and encoding sound similar — but they solve completely different problems. Mixing them up causes security bugs, broken APIs, and confused code reviews. Here's the mental model that finally made it click for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encoding: not security at all
&lt;/h2&gt;

&lt;p&gt;Encoding converts data from one format to another so it can be transported or displayed. Base64, URL encoding, hex — all reversible without any key. &lt;code&gt;btoa()&lt;/code&gt;/&lt;code&gt;atob()&lt;/code&gt;, &lt;code&gt;encodeURIComponent&lt;/code&gt;, &lt;code&gt;Buffer.from(x, 'base64')&lt;/code&gt; — none of these protect anything. Anyone can decode them. Encoding is about compatibility, not confidentiality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hashing: one-way by design
&lt;/h2&gt;

&lt;p&gt;A hash function maps any input to a fixed-length output. SHA-256 always produces 64 hex characters, whether the input is "hi" or a 2GB video. Three properties matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic&lt;/strong&gt; — same input, same hash, always.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-way&lt;/strong&gt; — you can't reverse a hash into its input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avalanche effect&lt;/strong&gt; — changing one character flips roughly half the bits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why hashes verify integrity: file checksums, Git commit IDs, npm lockfiles, API signatures. You can't "decrypt" a hash — you compare hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption: reversible, but only with a key
&lt;/h2&gt;

&lt;p&gt;Encryption is the only one that provides confidentiality. AES, RSA, ChaCha20 — ciphertext turns back into plaintext, but only with the right key. TLS, HTTPS, disk encryption, JWT payloads — all encryption.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Reversible?&lt;/th&gt;
&lt;th&gt;Needs a key?&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Encoding&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Format conversion (Base64, URL encoding)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hashing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Integrity checks (SHA-256 checksums)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Confidentiality (AES, RSA)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common bugs this clears up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Storing passwords with MD5/SHA-256? That's hashing — fine for integrity, but passwords need slow salted algorithms like bcrypt or argon2.&lt;/li&gt;
&lt;li&gt;"Let me encrypt this string with Base64" — Base64 is encoding; it obfuscates nothing.&lt;/li&gt;
&lt;li&gt;Verifying a downloaded file? Compare its SHA-256 checksum — a hash, not encryption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 When I need to quickly hash a string or verify a checksum, I use the &lt;a href="https://codetoolbox.pro/tools/hash-generator.html" rel="noopener noreferrer"&gt;SHA-256 Hash Generator on CodeToolbox&lt;/a&gt; — it computes SHA-1/256/384/512 right in the browser, nothing is uploaded.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's a hashing/encryption mixup you've seen in production? Drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>URL Encoding 101: Why %20 Isn't a Space (and When to Use encodeURIComponent)</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Wed, 26 Aug 2026 13:08:21 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/url-encoding-101-why-20-isnt-a-space-and-when-to-use-encodeuricomponent-fh</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/url-encoding-101-why-20-isnt-a-space-and-when-to-use-encodeuricomponent-fh</guid>
      <description>&lt;p&gt;Ever pasted a URL with spaces, Chinese characters, or an emoji into an API call and watched it explode with a 400 error? That's URL encoding — or the lack of it — biting you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What URL encoding actually is
&lt;/h2&gt;

&lt;p&gt;URLs are restricted to a small set of characters per RFC 3986: &lt;code&gt;A-Z&lt;/code&gt;, &lt;code&gt;a-z&lt;/code&gt;, &lt;code&gt;0-9&lt;/code&gt;, and a handful of reserved symbols (&lt;code&gt;-&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt;). Everything else — spaces, non-ASCII characters, and the reserved delimiters themselves (&lt;code&gt;/&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;) — must be percent-encoded.&lt;/p&gt;

&lt;p&gt;Each encoded character becomes a &lt;code&gt;%&lt;/code&gt; followed by two hex digits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;th&gt;Encoded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;space&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%20&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%2F&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%3F&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%26&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%23&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;你&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%E4%BD%A0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The classic JavaScript bug
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;encodeURI()&lt;/code&gt; encodes everything EXCEPT the reserved characters — which is why it's fine for the URL as a whole but WRONG for query values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;C# &amp;amp; JavaScript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/search?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// broken: # and &amp;amp; survive&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/search?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "C%23%20%26%20JavaScript"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a value contains &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, or &lt;code&gt;=&lt;/code&gt;, those characters change the meaning of the URL if they pass through raw. &lt;code&gt;encodeURIComponent()&lt;/code&gt; escapes all of them — use it for every query parameter and path segment you build dynamically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoding matters just as much
&lt;/h2&gt;

&lt;p&gt;You'll hit the reverse problem too: a file literally named &lt;code&gt;my%20file.pdf&lt;/code&gt; because someone double-encoded it, or log output showing &lt;code&gt;%E4%BD%A0&lt;/code&gt; where a Chinese word should be. A reliable decoder that handles UTF-8 and edge cases saves real debugging time.&lt;/p&gt;

&lt;p&gt;If you ever need to quickly encode or decode a string — or double-check what your code is actually sending — I keep coming back to &lt;a href="https://codetoolbox.pro/tools/url-encoder.html" rel="noopener noreferrer"&gt;CodeToolbox URL Encoder&lt;/a&gt;. It encodes and decodes in both directions with full UTF-8 support, no uploads, no signup required.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Many Pages Is 1000 Words? The 500/250 Rule Every Writer Should Know</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Mon, 24 Aug 2026 13:09:36 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-many-pages-is-1000-words-the-500250-rule-every-writer-should-know-3e98</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-many-pages-is-1000-words-the-500250-rule-every-writer-should-know-3e98</guid>
      <description>&lt;p&gt;Word counts and page counts live in different worlds. Your editor says 1,000 words, your client asks for "three pages," and the PDF comes out at four. Who's right? Everyone, sort of — page count depends entirely on formatting. Here's the rule of thumb that actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 500/250 rule
&lt;/h2&gt;

&lt;p&gt;A standard page in a 12pt font with 1-inch margins holds roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;500 words per page&lt;/strong&gt; single-spaced&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;250 words per page&lt;/strong&gt; double-spaced&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. It's not precise — font choice, line spacing, margins, and images all shift the number — but it's accurate enough to plan with. If you're writing a 2,000-word article, expect it to land around 4 single-spaced pages or 8 double-spaced. If it comes out noticeably different, the layout (not the writing) is what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why editors and Word disagree
&lt;/h2&gt;

&lt;p&gt;Microsoft Word's page count reflects &lt;em&gt;its&lt;/em&gt; layout engine: your margins, your font metrics, your paragraph spacing. Google Docs renders slightly differently, so the same text can "be" 5 pages in one and 5.3 in the other. Neither is wrong — page count is a rendering detail, not a property of the text itself.&lt;/p&gt;

&lt;p&gt;Word count, by contrast, is a property of the text. Ten words is ten words in any editor. That's why word count is the contract between writers, clients, and publishers: it's the only number that doesn't change when you switch fonts.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quoting a deliverable&lt;/strong&gt; → use word count. "A 1,500-word blog post" is unambiguous.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimating print layout&lt;/strong&gt; → use the 500/250 rule. It gets you in the ballpark before you open a layout tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meeting a platform limit&lt;/strong&gt; → use character count. Twitter/X caps at 280 characters, meta descriptions at ~155, App Store descriptions at 4,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The practical workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write the draft.&lt;/li&gt;
&lt;li&gt;Check the word count.&lt;/li&gt;
&lt;li&gt;If you're targeting pages, divide by 500 (single-spaced) or 250 (double-spaced).&lt;/li&gt;
&lt;li&gt;If you're targeting a character limit, trim against the character count, not the word count.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When I need these numbers fast, I paste the draft into a free online word counter that shows words, characters, sentences, and estimated reading time in real time — no upload, everything stays in the browser. It makes the word-count-vs-pages dance a five-second check instead of a formatting fight.&lt;/p&gt;

&lt;p&gt;Next time someone asks for "three pages," you'll know the real question is "about 1,500 words" — and you can deliver with confidence.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Much House Can You Actually Afford? The 28/36 Rule, Explained</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 13:26:41 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-much-house-can-you-actually-afford-the-2836-rule-explained-4oeh</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-much-house-can-you-actually-afford-the-2836-rule-explained-4oeh</guid>
      <description>&lt;p&gt;Buying a home? Before you look at listings, run the numbers on what you can actually afford — not what the bank pre-approves you for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 28/36 rule in 30 seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lenders use two debt-to-income ratios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Front-end DTI&lt;/strong&gt;: your housing payment (principal + interest + taxes + insurance) should be at most &lt;strong&gt;28%&lt;/strong&gt; of your gross monthly income.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Back-end DTI&lt;/strong&gt;: ALL your debt payments (housing + car + student loans + credit cards) should be at most &lt;strong&gt;36%&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: $7,000/month gross income -&amp;gt; housing payment target = $1,960 (28%). If you also pay $400/month in car and student loans, your max housing payment drops to $2,120 (36% back-end) — and the 28% cap wins at $1,960.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the rule matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The 28% cap isn't about approval — it's about margin. Life happens: job changes, medical bills, surprise repairs. A payment that eats 40% of your income leaves almost no room to absorb those. Banks will lend you more than you should borrow; the rule keeps the decision in your hands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run your own numbers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't trust the calculator on the listing site — they assume a 20% down payment and often skip taxes and insurance. Punch in your real numbers instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gross monthly income x 0.28 = your housing payment ceiling&lt;/li&gt;
&lt;li&gt;Adjust loan amount, rate, and term until the payment lands at or under that ceiling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I use the free &lt;a href="https://codetoolbox.pro/tools/loan-calculator.html" rel="noopener noreferrer"&gt;Loan Calculator&lt;/a&gt; for this — it shows the full amortization schedule and total interest, so you also see how much the house &lt;em&gt;really&lt;/em&gt; costs over 30 years. No signup, and everything runs locally in your browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The 28/36 rule gives you a defensible, math-backed answer to "how much house can I afford" — before a realtor or lender tells you theirs. Run the numbers first, then go shopping.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Read a Diff Like a Developer (and Catch Bugs Before They Merge)</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Wed, 19 Aug 2026 13:17:39 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-to-read-a-diff-like-a-developer-and-catch-bugs-before-they-merge-5d05</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-to-read-a-diff-like-a-developer-and-catch-bugs-before-they-merge-5d05</guid>
      <description>&lt;p&gt;We all look at diffs every day — pull request reviews, &lt;code&gt;git diff&lt;/code&gt; output, merge conflict resolutions. But most of us only skim for red and green lines. Learning to read a diff structurally turns code review from a chore into the cheapest bug-finding tool you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a diff actually is
&lt;/h2&gt;

&lt;p&gt;A diff is a line-by-line comparison between two versions of a file. The concept comes from the classic Unix &lt;code&gt;diff&lt;/code&gt; utility, and it's the foundation of every version control system. Behind the scenes, tools compute the longest common subsequence (LCS) of the two texts: lines that appear in both versions in the same order are "unchanged", and everything else is either added or removed. Understanding this helps you see why a diff looks the way it does — a big block of red followed by a big block of green is usually one rewritten chunk, not a deletion plus an unrelated addition.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical reading order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the boundaries first.&lt;/strong&gt; Where do the changed hunks start and end? A change touching the first few lines often shifts indentation or imports — two different concerns that Git shows as a single hunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look for structural changes before logic.&lt;/strong&gt; A changed function signature, an extra parameter, a renamed variable — these explain most "why did my code break?" moments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for whitespace-only noise.&lt;/strong&gt; Trailing spaces, line endings, and re-indentation can bury a real change among hundreds of fake ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the removed lines carefully.&lt;/strong&gt; The deleted line is usually where the bug actually was — it's often the line someone's logic still depends on.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When you don't have git history
&lt;/h2&gt;

&lt;p&gt;Not every comparison involves Git. I regularly compare two API responses to debug a payload difference, two CSV dumps to find the row that changed, or a config file (nginx.conf, .env, Docker Compose) before and after an edit. For those quick checks, I use the &lt;a href="https://codetoolbox.pro/tools/diff-checker.html" rel="noopener noreferrer"&gt;CodeToolbox Diff Checker&lt;/a&gt; — paste two versions, and it highlights added lines in green and removed lines in red, with everything processed locally in your browser (nothing gets uploaded). It has saved me from exporting files and squinting at them side by side more times than I can count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Diffs are the universal language of code change. The faster you can read one, the faster you can review a pull request, resolve a conflict, or spot the single line that broke the build.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>JSON vs XML in 2026: Why JSON Won (and Where XML Still Matters)</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Sun, 16 Aug 2026 13:10:58 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/json-vs-xml-in-2026-why-json-won-and-where-xml-still-matters-37fb</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/json-vs-xml-in-2026-why-json-won-and-where-xml-still-matters-37fb</guid>
      <description>&lt;p&gt;In the early 2000s, if you wanted two systems to talk to each other, XML was the answer. Today, JSON is the default for almost every web API. How did that happen, and does XML still have a place?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why JSON won&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JSON's biggest advantage is that it maps directly onto the data structures programming languages already use. An object is a dictionary, an array is a list, a string is a string. When your API client is JavaScript running in a browser, JSON isn't just convenient, it's native: &lt;code&gt;JSON.parse()&lt;/code&gt; and &lt;code&gt;JSON.stringify()&lt;/code&gt; are built in.&lt;/p&gt;

&lt;p&gt;It's also much lighter. Compare a typical XML document with its JSON equivalent and the JSON version is often half the size, because there are no opening and closing tags repeated for every field. That means less bandwidth, faster parsing, and happier mobile users.&lt;/p&gt;

&lt;p&gt;And it's genuinely human-readable in a way XML struggles to match. Compact JSON is still easy to skim; the equivalent XML is dominated by tag noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where XML still matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;None of this means XML is dead. It remains the foundation of several ecosystems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SVG&lt;/strong&gt; is XML, so every icon you ship is technically XML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOAP-based enterprise APIs&lt;/strong&gt; are still running inside banks, airlines, and government systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Office document formats&lt;/strong&gt; (DOCX, XLSX) are zipped XML files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration files&lt;/strong&gt; like Maven's &lt;code&gt;pom.xml&lt;/code&gt; or Android layouts lean on XML's strictness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;XML's real strength is namespaces, schemas (XSD), and mixed content, where text is interleaved with child elements. JSON has no direct answer for those. If your document needs attributes and structured validation, XML is a legitimate choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practical takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For new APIs, choose JSON. It's simpler, faster, and the whole ecosystem, from OpenAPI to JSON Schema to every language's standard library, has converged on it. For documents with complex validation or long-term archival, XML still earns its keep.&lt;/p&gt;

&lt;p&gt;One habit that saves me time daily: after generating API responses, I run them through a formatter to catch syntax errors before debugging code that isn't the problem. &lt;a href="https://codetoolbox.pro/tools/json-formatter.html" rel="noopener noreferrer"&gt;JSON Formatter&lt;/a&gt; is my go-to, it validates, pretty-prints, and works entirely in the browser, so nothing sensitive ever leaves my machine.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Base64 Data URIs: When They Help and When They Hurt</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 13:14:38 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/base64-data-uris-when-they-help-and-when-they-hurt-398d</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/base64-data-uris-when-they-help-and-when-they-hurt-398d</guid>
      <description>&lt;p&gt;If you've ever pasted a small image straight into your HTML, you've used a Base64 data URI: &lt;code&gt;data:image/png;base64,iVBORw0KGgo...&lt;/code&gt;. The browser decodes it as if it were a file — no extra HTTP request, no separate asset to deploy. It's a genuinely useful trick, but it's also easy to overuse. Here's a practical breakdown of when data URIs help, when they hurt, and how to spot the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The math that matters: +33%
&lt;/h2&gt;

&lt;p&gt;Base64 encodes 3 bytes of input into 4 text characters. That means a data URI is always about &lt;strong&gt;33% larger&lt;/strong&gt; than the original file. A 300 KB PNG becomes a ~400 KB string sitting inline in your HTML. On a slow mobile connection, that's real bytes the browser has to download before it can render anything above the fold.&lt;/p&gt;

&lt;p&gt;So the first rule is: &lt;strong&gt;the bigger the asset, the worse the trade-off.&lt;/strong&gt; Data URIs are great for things measured in kilobytes — a 1 KB logo, a 2 KB SVG icon, a few emoji. They're terrible for hero images and product photos.&lt;/p&gt;

&lt;h2&gt;
  
  
  When data URIs genuinely help
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Email signatures&lt;/strong&gt; — the image is embedded in the message body, so it renders even when the recipient's mail client blocks remote images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small icons and SVG backgrounds&lt;/strong&gt; — no extra round trip, and SVGs scale at any resolution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-file demos and prototypes&lt;/strong&gt; — one HTML file with everything inline is trivial to share, attach, or screenshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline contexts&lt;/strong&gt; — anything embedded travels with the document.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When they hurt
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Any image over ~10-20 KB&lt;/strong&gt; — the 33% overhead plus the base64 decode cost rarely beats a regular request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP/2+ sites&lt;/strong&gt; — modern browsers multiplex requests cheaply; one more small request is not the penalty it was in 2012.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images reused on multiple pages&lt;/strong&gt; — each page re-downloads the same encoded string. A cached file is fetched once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching and CDNs&lt;/strong&gt; — you can't give a data URI cache headers, vary it, or serve it from a CDN edge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The quick check
&lt;/h2&gt;

&lt;p&gt;Before inlining, ask: &lt;em&gt;would this file survive as a separate request?&lt;/em&gt; If it's tiny, static, and page-specific — inline it. If it's large, shared, or likely to change — keep it as a real file.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; When you do need Base64 — for a data URI, a JWT payload, or an API response — a free online &lt;a href="https://codetoolbox.pro/tools/base64.html" rel="noopener noreferrer"&gt;Base64 encoder/decoder&lt;/a&gt; makes it a two-second job: paste, encode, copy. No installs, and everything happens in the browser.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Password Entropy, Explained: Why Length Beats Complexity</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:14:58 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/password-entropy-explained-why-length-beats-complexity-4djm</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/password-entropy-explained-why-length-beats-complexity-4djm</guid>
      <description>&lt;p&gt;Some passwords look strong and aren't. &lt;code&gt;Tr0ub4dor&amp;amp;3&lt;/code&gt; has 11 characters, an uppercase letter, a digit, and a symbol — yet crackers love it. Meanwhile &lt;code&gt;correct horse battery staple&lt;/code&gt; is 28 characters of lowercase words and is vastly harder to guess. The difference is &lt;strong&gt;entropy&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is password entropy?
&lt;/h2&gt;

&lt;p&gt;Entropy measures unpredictability, in bits. Every bit doubles the number of possible passwords an attacker must try. The formula is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;entropy = length × log₂(charset size)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lowercase letters (26): 4.7 bits per character&lt;/li&gt;
&lt;li&gt;+ Uppercase (52): 5.7 bits&lt;/li&gt;
&lt;li&gt;+ Digits (62): 6.0 bits&lt;/li&gt;
&lt;li&gt;+ Symbols (~95): 6.6 bits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The charset matters — but only a little. Going from lowercase-only to full symbols buys you under 2 extra bits per character. Adding &lt;strong&gt;one character&lt;/strong&gt; to a lowercase-only password buys you 4.7 bits. That's the whole argument in one sentence: &lt;strong&gt;length beats complexity, roughly two and a half times over.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's do the math
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Tr0ub4dor&amp;amp;3&lt;/code&gt; (11 chars, 4 charsets): 11 × 6.6 ≈ 73 bits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;correcthorsebatterystaple&lt;/code&gt; (25 chars, lowercase): 25 × 4.7 ≈ 118 bits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lowercase passphrase has more than 2⁴⁵ times the combinations. This is why modern guidelines (NIST SP 800-63B included) emphasize length over forced complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use 16+ characters.&lt;/strong&gt; At 16 random characters you're past 90 bits — beyond practical brute force.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let a generator do the work.&lt;/strong&gt; Humans pick &lt;code&gt;Password1!&lt;/code&gt;; CSPRNGs don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique per site.&lt;/strong&gt; A 100-bit password is worthless if it's reused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect site limits.&lt;/strong&gt; If a site caps length or bans symbols, longer lowercase-only is still strong — just don't shrink a good password to 8 characters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I generate all my passwords with a local tool: the &lt;a href="https://codetoolbox.pro/tools/password-generator.html" rel="noopener noreferrer"&gt;CodeToolbox Password Generator&lt;/a&gt; runs entirely in the browser via &lt;code&gt;crypto.getRandomValues()&lt;/code&gt;, so nothing ever leaves my machine. No signup, no upload — just generate and drop the result into your password manager.&lt;/p&gt;

&lt;p&gt;Length beats complexity. Always.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
