<?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: Mahir Faysal</title>
    <description>The latest articles on DEV Community by Mahir Faysal (@mfaysaldev).</description>
    <link>https://dev.to/mfaysaldev</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%2F4157097%2F4d572c0d-771d-420f-815f-f8227b3a1663.png</url>
      <title>DEV Community: Mahir Faysal</title>
      <link>https://dev.to/mfaysaldev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mfaysaldev"/>
    <language>en</language>
    <item>
      <title>How to Verify a File Checksum (SHA-256) on Windows, Mac and Linux</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Sat, 24 Oct 2026 03:58:25 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/how-to-verify-a-file-checksum-sha-256-on-windows-mac-and-linux-3f68</link>
      <guid>https://dev.to/mfaysaldev/how-to-verify-a-file-checksum-sha-256-on-windows-mac-and-linux-3f68</guid>
      <description>&lt;p&gt;&lt;strong&gt;To verify a file checksum, calculate the file's SHA-256 hash on your own computer and compare it with the value the publisher lists on its download page. On Windows run &lt;code&gt;Get-FileHash&lt;/code&gt; in PowerShell or &lt;code&gt;certutil -hashfile&lt;/code&gt; in Command Prompt, on a Mac run &lt;code&gt;shasum -a 256&lt;/code&gt;, and on Linux run &lt;code&gt;sha256sum&lt;/code&gt;. If the two values match exactly, your copy is identical to the one the publisher hashed. If they differ by a single character, don't open or install the file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That short string of letters and numbers next to a download button is easy to scroll past. It's worth thirty seconds, because it's the simplest way to prove the file on your disk is the one the developer released.&lt;/p&gt;

&lt;p&gt;Below are the exact commands for each system, a way to let the computer compare for you, GPG signature basics and what to do when the numbers don't match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this guide:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What a checksum is and what it proves&lt;/li&gt;
&lt;li&gt;Which hash algorithm should you use?&lt;/li&gt;
&lt;li&gt;How to verify a SHA-256 checksum on Windows with PowerShell&lt;/li&gt;
&lt;li&gt;How to check a hash with certutil in Command Prompt&lt;/li&gt;
&lt;li&gt;How to verify a checksum on a Mac with shasum&lt;/li&gt;
&lt;li&gt;How to verify a checksum on Linux with sha256sum&lt;/li&gt;
&lt;li&gt;Let the computer compare the hash for you&lt;/li&gt;
&lt;li&gt;GPG signatures: the step that proves who published the file&lt;/li&gt;
&lt;li&gt;What a checksum mismatch means and what to do&lt;/li&gt;
&lt;li&gt;Common mistakes when checking file hashes&lt;/li&gt;
&lt;li&gt;Quick reference: checksum commands for every system&lt;/li&gt;
&lt;li&gt;Frequently asked questions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a checksum is and what it proves
&lt;/h2&gt;

&lt;p&gt;A checksum (also called a hash or digest) is a fixed-length fingerprint worked out from every byte of a file. Feed the same file into SHA-256 and you get the same 64-character answer every time. Change one byte, even a single full stop in a text file, and the answer changes completely.&lt;/p&gt;

&lt;p&gt;So it answers one question: &lt;em&gt;is my copy byte-for-byte identical to the one this value was made from?&lt;/em&gt; It catches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;downloads that were cut short or corrupted on the way;&lt;/li&gt;
&lt;li&gt;the wrong file, such as the ARM build when you wanted x64;&lt;/li&gt;
&lt;li&gt;a file that was swapped or altered on a mirror or in transit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It doesn't tell you the software is safe or that the person who published the hash is trustworthy. If an attacker can change the file on a website, they may be able to change the hash printed beside it too. Signatures solve that, as you'll see below.&lt;/p&gt;

&lt;p&gt;Hashes are almost always written in hexadecimal (0-9 and a-f) because it's easy to compare by eye. If you've wondered how that differs from other text encodings, my guide to &lt;a href="https://mfaysal.com/blog/what-is-base64-encoding" rel="noopener noreferrer"&gt;what Base64 encoding is&lt;/a&gt; compares hex and Base64 side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which hash algorithm should you use?
&lt;/h2&gt;

&lt;p&gt;Use whatever the publisher lists, and prefer SHA-256 or SHA-512 when there's a choice. You can usually tell the algorithm from the length of the value:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Length in hex&lt;/th&gt;
&lt;th&gt;Status for download checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MD5&lt;/td&gt;
&lt;td&gt;32 characters&lt;/td&gt;
&lt;td&gt;Broken against deliberate tampering; only spots accidental corruption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;40 characters&lt;/td&gt;
&lt;td&gt;Deprecated; collisions have been shown in practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256&lt;/td&gt;
&lt;td&gt;64 characters&lt;/td&gt;
&lt;td&gt;Recommended, and the most common on download pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-384&lt;/td&gt;
&lt;td&gt;96 characters&lt;/td&gt;
&lt;td&gt;Strong; less common&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-512&lt;/td&gt;
&lt;td&gt;128 characters&lt;/td&gt;
&lt;td&gt;Strong; used by some projects such as Apache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The US National Institute of Standards and Technology keeps the official list of approved algorithms on its &lt;a href="https://csrc.nist.gov/projects/hash-functions" rel="noopener noreferrer"&gt;hash functions project page&lt;/a&gt;, including its plan to retire SHA-1 by the end of 2030. An MD5 match only rules out accidental corruption.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to verify a SHA-256 checksum on Windows with PowerShell
&lt;/h2&gt;

&lt;p&gt;PowerShell is built into Windows 10 and Windows 11, and its &lt;code&gt;Get-FileHash&lt;/code&gt; cmdlet uses SHA-256 unless you ask for something else.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the folder that holds your download in File Explorer.&lt;/li&gt;
&lt;li&gt;Right-click an empty space in the folder and choose &lt;strong&gt;Open in Terminal&lt;/strong&gt; (on Windows 10, hold Shift while you right-click and choose &lt;strong&gt;Open PowerShell window here&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Type the command below, replacing the file name with yours. Pressing Tab after the first few letters fills the name in for you.&lt;/li&gt;
&lt;li&gt;Press Enter and wait.&lt;/li&gt;
&lt;li&gt;Compare the value in the &lt;strong&gt;Hash&lt;/strong&gt; column with the one on the download page.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-FileHash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\installer.exe&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Algorithm&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;Hash&lt;/span&gt;&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;---------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;----&lt;/span&gt;&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="o"&gt;----&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;5891B5B522D5DF086D0FF0B110FBD9D21BB4FC7163AF34D08286A2E846F6BE03&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\you\Downloads\installer.exe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PowerShell prints the hash in capital letters, while most websites use lower case. That's fine: hex isn't case-sensitive, so &lt;code&gt;5891B5&lt;/code&gt; and &lt;code&gt;5891b5&lt;/code&gt; are the same value.&lt;/p&gt;

&lt;p&gt;For another algorithm, add &lt;code&gt;-Algorithm&lt;/code&gt; followed by SHA1, SHA384, SHA512 or MD5. Microsoft's &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash" rel="noopener noreferrer"&gt;Get-FileHash reference&lt;/a&gt; lists every option, including how to hash a whole folder of files at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check a hash with certutil in Command Prompt
&lt;/h2&gt;

&lt;p&gt;If you prefer Command Prompt, &lt;code&gt;certutil&lt;/code&gt; does the same job and ships with every current version of Windows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press the Windows key, type &lt;strong&gt;cmd&lt;/strong&gt; and open Command Prompt.&lt;/li&gt;
&lt;li&gt;Move to your Downloads folder with &lt;code&gt;cd %USERPROFILE%\Downloads&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;certutil -hashfile&lt;/code&gt; with the file name and the algorithm.&lt;/li&gt;
&lt;li&gt;Read the hash on the second line of the output.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;certutil -hashfile installer.exe SHA256

SHA256 hash of installer.exe:
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
CertUtil: -hashfile command completed successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't leave off the algorithm name. Without it, certutil falls back to SHA-1, which won't match a SHA-256 value however correct your file is. It also accepts MD5, SHA384 and SHA512. The full syntax is in Microsoft's &lt;a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/certutil" rel="noopener noreferrer"&gt;certutil documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to verify a checksum on a Mac with shasum
&lt;/h2&gt;

&lt;p&gt;macOS includes &lt;code&gt;shasum&lt;/code&gt;, a Perl-based tool that handles the whole SHA family. You choose the algorithm with &lt;code&gt;-a&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Terminal&lt;/strong&gt; from Applications &amp;gt; Utilities, or search for it with Spotlight (Command + Space).&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;shasum -a 256&lt;/code&gt; followed by a space.&lt;/li&gt;
&lt;li&gt;Drag the downloaded file from Finder into the Terminal window. macOS types its full path for you.&lt;/li&gt;
&lt;li&gt;Press Return and compare the result with the published value.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shasum -a 256 ~/Downloads/installer.dmg
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03  /Users/you/Downloads/installer.dmg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is the hash, two spaces, then the file name. Use &lt;code&gt;-a 512&lt;/code&gt; for SHA-512 or &lt;code&gt;-a 1&lt;/code&gt; for SHA-1. For an MD5 value, macOS has a separate &lt;code&gt;md5&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;If you've downloaded a checksum file as well (often called &lt;code&gt;SHA256SUMS&lt;/code&gt; or &lt;code&gt;installer.dmg.sha256&lt;/code&gt;), put it in the same folder and run &lt;code&gt;shasum -a 256 -c SHA256SUMS&lt;/code&gt;. You'll see &lt;code&gt;OK&lt;/code&gt; or &lt;code&gt;FAILED&lt;/code&gt; next to each file it can find.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to verify a checksum on Linux with sha256sum
&lt;/h2&gt;

&lt;p&gt;Every mainstream Linux distribution ships GNU coreutils, which includes &lt;code&gt;sha256sum&lt;/code&gt;, &lt;code&gt;sha512sum&lt;/code&gt;, &lt;code&gt;sha1sum&lt;/code&gt; and &lt;code&gt;md5sum&lt;/code&gt;. They all work the same way.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a terminal and move to your download folder with &lt;code&gt;cd ~/Downloads&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;sha256sum&lt;/code&gt; followed by the file name.&lt;/li&gt;
&lt;li&gt;Compare the 64-character result with the publisher's value.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sha256sum ubuntu.iso
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03  ubuntu.iso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real strength on Linux is check mode. Many projects publish one &lt;code&gt;SHA256SUMS&lt;/code&gt; file listing a hash for every download. Save it next to your file and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sha256sum -c SHA256SUMS --ignore-missing
ubuntu.iso: OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--ignore-missing&lt;/code&gt; flag tells it to skip files in the list that you didn't download, so you only see results for the ones you have. Without it, you'll get a "No such file or directory" warning for every other entry. The &lt;a href="https://man7.org/linux/man-pages/man1/sha256sum.1.html" rel="noopener noreferrer"&gt;sha256sum manual page&lt;/a&gt; covers the remaining options, including &lt;code&gt;--strict&lt;/code&gt; and &lt;code&gt;--quiet&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the computer compare the hash for you
&lt;/h2&gt;

&lt;p&gt;Comparing 64 characters by eye is where mistakes creep in. People check the first and last few characters and assume the middle matches. Copy the published value from the website and let the system do the comparison instead.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;One-line comparison&lt;/th&gt;
&lt;th&gt;Result if it matches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows PowerShell&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(Get-FileHash .\installer.exe).Hash -eq "paste-hash-here"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mac Terminal&lt;/td&gt;
&lt;td&gt;`echo "paste-hash-here installer.dmg"&lt;/td&gt;
&lt;td&gt;shasum -a 256 -c`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux terminal&lt;/td&gt;
&lt;td&gt;`echo "paste-hash-here ubuntu.iso"&lt;/td&gt;
&lt;td&gt;sha256sum -c`&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Mac and Linux, put &lt;strong&gt;two spaces&lt;/strong&gt; between the hash and the file name. In PowerShell, &lt;code&gt;-eq&lt;/code&gt; ignores case, so capital-letter output still matches a lower-case value.&lt;/p&gt;

&lt;p&gt;If you get &lt;code&gt;False&lt;/code&gt; or &lt;code&gt;FAILED&lt;/code&gt;, check for a stray space copied from the web page first.&lt;/p&gt;

&lt;h2&gt;
  
  
  GPG signatures: the step that proves who published the file
&lt;/h2&gt;

&lt;p&gt;A checksum proves your file matches a value. A digital signature proves who produced that value. Serious projects (Linux distributions, Tor, password managers, many open-source tools) sign their checksum file with a GPG key, so an attacker would need the developer's private key to fake it.&lt;/p&gt;

&lt;p&gt;The usual process looks like this, using Ubuntu's file names as an example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the file, the &lt;code&gt;SHA256SUMS&lt;/code&gt; file and its signature, &lt;code&gt;SHA256SUMS.gpg&lt;/code&gt;, from the official site.&lt;/li&gt;
&lt;li&gt;Install GnuPG if you don't have it: it's preinstalled on most Linux systems, available as Gpg4win on Windows and as GPG Suite or via Homebrew (&lt;code&gt;brew install gnupg&lt;/code&gt;) on a Mac.&lt;/li&gt;
&lt;li&gt;Import the project's public signing key using the key ID or fingerprint the project publishes, for example &lt;code&gt;gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys KEY-ID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check the fingerprint against the one on the project's website.&lt;/li&gt;
&lt;li&gt;Verify the signature with &lt;code&gt;gpg --verify SHA256SUMS.gpg SHA256SUMS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If it reports a good signature, run the checksum check from the previous sections against that signed file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You're looking for &lt;code&gt;Good signature from&lt;/code&gt; followed by the project's name. If you see &lt;code&gt;BAD signature&lt;/code&gt;, stop. GnuPG will often add a warning that the key "is not certified with a trusted signature". That's normal on a personal machine; it means you haven't personally vouched for the key, which is why step 4 matters. Ubuntu's own &lt;a href="https://ubuntu.com/tutorials/how-to-verify-ubuntu" rel="noopener noreferrer"&gt;guide to verifying an Ubuntu download&lt;/a&gt; walks through this with real key IDs.&lt;/p&gt;

&lt;p&gt;Windows and macOS apps also carry code signatures. In PowerShell, &lt;code&gt;Get-AuthenticodeSignature .\installer.exe&lt;/code&gt; shows whether a Windows installer is validly signed and by whom. On a Mac, Gatekeeper checks signatures and notarisation automatically when you open an app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a checksum mismatch means and what to do
&lt;/h2&gt;

&lt;p&gt;A mismatch tells you the file isn't the one the value was made from, but not why. These are the usual causes, most common first:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;How to spot it&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Incomplete or corrupted download&lt;/td&gt;
&lt;td&gt;File size is smaller than listed, or the browser showed an error&lt;/td&gt;
&lt;td&gt;Delete it and download again, ideally on a stable connection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong file or version&lt;/td&gt;
&lt;td&gt;Name differs slightly (arm64 vs x64, or an older version number)&lt;/td&gt;
&lt;td&gt;Match the exact file name next to the published hash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong algorithm&lt;/td&gt;
&lt;td&gt;Your hash is 40 characters but the published one is 64&lt;/td&gt;
&lt;td&gt;Rerun with SHA-256, or add the algorithm to certutil&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy-and-paste error&lt;/td&gt;
&lt;td&gt;Extra space or missing character in the pasted value&lt;/td&gt;
&lt;td&gt;Copy the published value again, or use the checksum file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tampered file or mirror&lt;/td&gt;
&lt;td&gt;Still wrong after a fresh download from the official site&lt;/td&gt;
&lt;td&gt;Don't run it; report it to the project&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a second download from the project's own website still doesn't match, don't open the file, and don't "just try it in case". Delete it, check you're on the genuine site (look-alike domains are common), and let the developers know through their official support or issue tracker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes when checking file hashes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Getting the hash from the same untrusted place as the file.&lt;/strong&gt; If you downloaded from a mirror, take the hash from the project's official HTTPS site, or better, a signed checksum file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hashing the extracted contents instead of the download.&lt;/strong&gt; The published value is for the ZIP, ISO or installer exactly as downloaded. Check it before unzipping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checking a file you've already run.&lt;/strong&gt; Verify before opening an installer, not after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confusing file checksums with password hashes.&lt;/strong&gt; A file checksum is meant to be fast and repeatable. Password storage needs slow, salted hashing, which is a different job entirely; my &lt;a href="https://mfaysal.com/blog/php-password-hash-verify-example" rel="noopener noreferrer"&gt;PHP password_hash example&lt;/a&gt; shows how that works.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick reference: checksum commands for every system
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Windows&lt;/th&gt;
&lt;th&gt;Mac&lt;/th&gt;
&lt;th&gt;Linux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256 of a file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Get-FileHash file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shasum -a 256 file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sha256sum file&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-512 of a file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Get-FileHash file -Algorithm SHA512&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shasum -a 512 file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sha512sum file&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Command Prompt alternative&lt;/td&gt;
&lt;td&gt;&lt;code&gt;certutil -hashfile file SHA256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check against a SHA256SUMS file&lt;/td&gt;
&lt;td&gt;Compare manually or with &lt;code&gt;-eq&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shasum -a 256 -c SHA256SUMS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sha256sum -c SHA256SUMS --ignore-missing&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify a GPG signature&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gpg --verify SHA256SUMS.gpg SHA256SUMS&lt;/code&gt; (Gpg4win)&lt;/td&gt;
&lt;td&gt;Same command (GPG Suite or Homebrew)&lt;/td&gt;
&lt;td&gt;Same command&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bookmark this table for the next download page that lists a SHA-256 value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a SHA-256 checksum?
&lt;/h3&gt;

&lt;p&gt;It's a 64-character fingerprint calculated from every byte of a file using the SHA-256 algorithm. The same file always gives the same value, and any change to the file gives a completely different one, so comparing values shows whether two files are identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I check a SHA-256 hash in Windows 11?
&lt;/h3&gt;

&lt;p&gt;Open Terminal or PowerShell in the folder with your file and run Get-FileHash followed by the file name. It uses SHA-256 by default. In Command Prompt, run certutil -hashfile followed by the file name and SHA256.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the hash case-sensitive?
&lt;/h3&gt;

&lt;p&gt;No. Hexadecimal hashes aren't case-sensitive, so a value in capital letters from PowerShell matches the same value in lower case on a website. Every character still has to be the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why doesn't my checksum match?
&lt;/h3&gt;

&lt;p&gt;The most common reasons are an incomplete download, comparing against the hash for a different file or version, or using the wrong algorithm, such as SHA-1 instead of SHA-256. Download the file again from the official site and recheck before assuming tampering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is MD5 still OK for checking downloads?
&lt;/h3&gt;

&lt;p&gt;MD5 can still spot accidental corruption, but it's broken against deliberate tampering because different files can be made to share the same MD5 value. Use SHA-256 or SHA-512 when the publisher offers them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a matching checksum mean the file is safe?
&lt;/h3&gt;

&lt;p&gt;Not by itself. It proves your file is identical to the one the published value came from. If that value came from a compromised page, both could be malicious, so check signatures where available and only download from official sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between a checksum and a GPG signature?
&lt;/h3&gt;

&lt;p&gt;A checksum checks that the file wasn't changed. A GPG signature checks who produced the checksum file, using the developer's private key, so an attacker can't fake it without that key. Use both when a project provides them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to install anything to check a hash?
&lt;/h3&gt;

&lt;p&gt;No. Windows has Get-FileHash and certutil, macOS has shasum, and Linux has sha256sum built in. You only need extra software, such as GnuPG, if you also want to verify a signature.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last checked: 22 October 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/how-to-verify-a-file-checksum" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>linux</category>
      <category>windows</category>
    </item>
    <item>
      <title>How to Check Open Ports on Windows, Mac and Linux (and Close the Ones You Don't Need)</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Fri, 23 Oct 2026 04:01:18 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/how-to-check-open-ports-on-windows-mac-and-linux-and-close-the-ones-you-dont-need-30nd</link>
      <guid>https://dev.to/mfaysaldev/how-to-check-open-ports-on-windows-mac-and-linux-and-close-the-ones-you-dont-need-30nd</guid>
      <description>&lt;p&gt;&lt;strong&gt;To check open ports on your computer, list the ports that programs are listening on and see which program owns each one. On Windows, run &lt;code&gt;netstat -ano | findstr LISTENING&lt;/code&gt; in Command Prompt or &lt;code&gt;Get-NetTCPConnection -State Listen&lt;/code&gt; in PowerShell. On a Mac, run &lt;code&gt;sudo lsof -iTCP -sTCP:LISTEN -n -P&lt;/code&gt;. On Linux, run &lt;code&gt;sudo ss -tulpn&lt;/code&gt;. Anything listening on 0.0.0.0 or [::] accepts connections from other devices, so stop the program or block the port in your firewall if you don't need it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every service that accepts network connections opens a port: file sharing, remote desktop, a database you installed for a course and forgot. An open port isn't automatically a problem. One you don't know about is.&lt;/p&gt;

&lt;p&gt;This guide sticks to your own computer and each system's built-in tools: reading the output, spotting exposed services, checking what's reachable and closing what you don't need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this guide:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open, listening and established: what the terms mean&lt;/li&gt;
&lt;li&gt;Local only or reachable? How to read the address column&lt;/li&gt;
&lt;li&gt;How to check open ports on Windows with netstat&lt;/li&gt;
&lt;li&gt;How to check open ports in PowerShell with Get-NetTCPConnection&lt;/li&gt;
&lt;li&gt;What's normally listening on a Windows PC&lt;/li&gt;
&lt;li&gt;How to check open ports on a Mac&lt;/li&gt;
&lt;li&gt;How to check open ports on Linux with ss&lt;/li&gt;
&lt;li&gt;How to check which ports are reachable from outside&lt;/li&gt;
&lt;li&gt;How to close or block a port you don't need&lt;/li&gt;
&lt;li&gt;Firewall commands to block a port on each system&lt;/li&gt;
&lt;li&gt;A five-minute monthly port check&lt;/li&gt;
&lt;li&gt;Frequently asked questions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Open, listening and established: what the terms mean
&lt;/h2&gt;

&lt;p&gt;Tools use slightly different words, so here's what they mean.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Should you worry?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LISTENING / LISTEN&lt;/td&gt;
&lt;td&gt;A program is waiting for incoming TCP connections on this port&lt;/td&gt;
&lt;td&gt;Check it if it listens on all addresses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESTABLISHED&lt;/td&gt;
&lt;td&gt;An active connection, usually one your computer started, such as a browser tab&lt;/td&gt;
&lt;td&gt;Usually not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIME_WAIT / CLOSE_WAIT&lt;/td&gt;
&lt;td&gt;A connection that's closing down&lt;/td&gt;
&lt;td&gt;No, these clear on their own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UDP (no state)&lt;/td&gt;
&lt;td&gt;UDP has no connections, so a bound UDP port is shown without LISTEN&lt;/td&gt;
&lt;td&gt;Check it the same way as a TCP listener&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When people say "open port", they almost always mean a listening port that something else could connect to. That's what we'll focus on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local only or reachable? How to read the address column
&lt;/h2&gt;

&lt;p&gt;The address a program listens on matters more than the port number. It decides who can reach it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Address shown&lt;/th&gt;
&lt;th&gt;Who can connect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;127.0.0.1&lt;/code&gt; or &lt;code&gt;[::1]&lt;/code&gt; (also &lt;code&gt;localhost&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Only programs on this computer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;0.0.0.0&lt;/code&gt;, &lt;code&gt;[::]&lt;/code&gt; or &lt;code&gt;*&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Any network interface: other devices on your Wi-Fi, and possibly the internet if a firewall or router allows it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A specific address, such as &lt;code&gt;192.168.1.20&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Devices that can reach that one network interface&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A database or dev server bound to &lt;code&gt;127.0.0.1&lt;/code&gt; is fairly harmless. The same service bound to &lt;code&gt;0.0.0.0&lt;/code&gt; on a laptop you take to coffee shops is worth a second look.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check open ports on Windows with netstat
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;netstat&lt;/code&gt; has been part of Windows for decades and works in any Command Prompt. Run it as administrator so you can see every process.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press the Windows key, type &lt;strong&gt;cmd&lt;/strong&gt;, right-click Command Prompt and choose &lt;strong&gt;Run as administrator&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;netstat -ano | findstr LISTENING&lt;/code&gt; to list TCP listeners with the process ID (PID) in the last column.&lt;/li&gt;
&lt;li&gt;Note any PID you don't recognise.&lt;/li&gt;
&lt;li&gt;Look it up with &lt;code&gt;tasklist /fi "PID eq 1234"&lt;/code&gt;, replacing 1234 with the PID.&lt;/li&gt;
&lt;li&gt;To check a single port, run &lt;code&gt;netstat -ano | findstr :3389&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Proto  Local Address          Foreign Address        State           PID
  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       1092
  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
  TCP    127.0.0.1:5432         0.0.0.0:0              LISTENING       6120
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flags mean: &lt;code&gt;-a&lt;/code&gt; all connections and listening ports, &lt;code&gt;-n&lt;/code&gt; numbers instead of names, and &lt;code&gt;-o&lt;/code&gt; the owning PID. Adding &lt;code&gt;-b&lt;/code&gt; shows the program name directly, but it's slower and needs an elevated window. UDP ports appear in the full &lt;code&gt;netstat -ano&lt;/code&gt; output without a state. The &lt;a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/netstat" rel="noopener noreferrer"&gt;netstat reference on Microsoft Learn&lt;/a&gt; lists every switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check open ports in PowerShell with Get-NetTCPConnection
&lt;/h2&gt;

&lt;p&gt;PowerShell gives tidier output you can filter and sort.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Right-click the Start button and open &lt;strong&gt;Terminal (Admin)&lt;/strong&gt; or &lt;strong&gt;Windows PowerShell (Admin)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;List TCP listeners with their owning process:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-NetTCPConnection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-State&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LocalAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LocalPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OwningProcess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Process'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="n"&gt;Get-Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OwningProcess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessName&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nx"&gt;Sort&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LocalPort&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;For UDP, run &lt;code&gt;Get-NetUDPEndpoint | Sort-Object LocalPort&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To see who's using one port, run &lt;code&gt;Get-NetTCPConnection -LocalPort 3389&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The calculated &lt;code&gt;Process&lt;/code&gt; column saves you looking up each PID by hand. Microsoft documents every parameter in the &lt;a href="https://learn.microsoft.com/en-us/powershell/module/nettcpip/get-nettcpconnection" rel="noopener noreferrer"&gt;Get-NetTCPConnection reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Prefer a window to a command line? Press Windows + R, type &lt;code&gt;resmon&lt;/code&gt; and open the &lt;strong&gt;Network&lt;/strong&gt; tab of Resource Monitor. The &lt;strong&gt;Listening Ports&lt;/strong&gt; section shows each program, its PID, address, port, protocol and whether Windows Firewall allows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's normally listening on a Windows PC
&lt;/h2&gt;

&lt;p&gt;A clean Windows 10 or 11 PC isn't silent. Know the usual entries before you stop anything.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Usually&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;135 (TCP)&lt;/td&gt;
&lt;td&gt;RPC Endpoint Mapper (svchost)&lt;/td&gt;
&lt;td&gt;Core Windows service; leave it alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;139, 445 (TCP)&lt;/td&gt;
&lt;td&gt;NetBIOS and SMB file sharing (System, PID 4)&lt;/td&gt;
&lt;td&gt;Keep it off public networks; block from the internet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3389 (TCP)&lt;/td&gt;
&lt;td&gt;Remote Desktop&lt;/td&gt;
&lt;td&gt;Only there if Remote Desktop is turned on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5040 (TCP)&lt;/td&gt;
&lt;td&gt;Connected Devices Platform service&lt;/td&gt;
&lt;td&gt;Normal on Windows 10 and 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5353, 5355 (UDP)&lt;/td&gt;
&lt;td&gt;mDNS and LLMNR name discovery&lt;/td&gt;
&lt;td&gt;Local network name lookups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49152-65535 (TCP)&lt;/td&gt;
&lt;td&gt;Dynamic RPC ports used by Windows services&lt;/td&gt;
&lt;td&gt;Several entries here are normal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Apps such as Steam, Docker, VPN clients and database servers add their own. If you see an elevation prompt while running these commands, my explainer on &lt;a href="https://mfaysal.com/blog/what-is-uac-in-windows" rel="noopener noreferrer"&gt;what UAC is in Windows&lt;/a&gt; covers why admin rights are needed to see other users' processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check open ports on a Mac
&lt;/h2&gt;

&lt;p&gt;macOS uses the BSD version of &lt;code&gt;netstat&lt;/code&gt;, which can't show process names, so &lt;code&gt;lsof&lt;/code&gt; is the better tool.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Terminal&lt;/strong&gt; (Applications &amp;gt; Utilities).&lt;/li&gt;
&lt;li&gt;List TCP listeners with their program and PID: &lt;code&gt;sudo lsof -iTCP -sTCP:LISTEN -n -P&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Enter your Mac password when asked. Nothing appears as you type; that's normal.&lt;/li&gt;
&lt;li&gt;List UDP ports with &lt;code&gt;sudo lsof -iUDP -n -P&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check one port with &lt;code&gt;sudo lsof -i :5000 -n -P&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COMMAND     PID   USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
ControlCe   512   you    9u  IPv4  0x...      0t0  TCP *:7000 (LISTEN)
ControlCe   512   you   10u  IPv4  0x...      0t0  TCP *:5000 (LISTEN)
postgres   1840   you    7u  IPv4  0x...      0t0  TCP 127.0.0.1:5432 (LISTEN)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-n&lt;/code&gt; and &lt;code&gt;-P&lt;/code&gt; stop lsof translating addresses and ports into names, which makes it much faster and the output easier to read. Without &lt;code&gt;sudo&lt;/code&gt; you only see your own processes. For a quick port list without process names, &lt;code&gt;netstat -an -p tcp | grep LISTEN&lt;/code&gt; works too. The &lt;a href="https://man7.org/linux/man-pages/man8/lsof.8.html" rel="noopener noreferrer"&gt;lsof manual page&lt;/a&gt; explains every selection flag.&lt;/p&gt;

&lt;p&gt;A common surprise: ports 5000 and 7000 owned by &lt;code&gt;ControlCenter&lt;/code&gt;. That's AirPlay Receiver. If you don't stream to your Mac, turn it off in System Settings &amp;gt; General &amp;gt; AirDrop &amp;amp; Handoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check open ports on Linux with ss
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ss&lt;/code&gt; is the modern replacement for &lt;code&gt;netstat&lt;/code&gt; on Linux and comes with the iproute2 package on virtually every distribution.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a terminal.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;sudo ss -tulpn&lt;/code&gt; to list TCP and UDP listeners with their process.&lt;/li&gt;
&lt;li&gt;Read the &lt;strong&gt;Local Address:Port&lt;/strong&gt; column and the &lt;code&gt;users:(("name",pid=...))&lt;/code&gt; part at the end.&lt;/li&gt;
&lt;li&gt;Check one port with &lt;code&gt;sudo ss -tlnp 'sport = :22'&lt;/code&gt;, or use &lt;code&gt;sudo lsof -i :22&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Netid State  Local Address:Port  Peer Address:Port Process
udp   UNCONN 127.0.0.53%lo:53         0.0.0.0:*     users:(("systemd-resolve",pid=612,fd=13))
tcp   LISTEN       0.0.0.0:22         0.0.0.0:*     users:(("sshd",pid=901,fd=3))
tcp   LISTEN     127.0.0.1:3306       0.0.0.0:*     users:(("mysqld",pid=1220,fd=21))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The letters stand for &lt;code&gt;-t&lt;/code&gt; TCP, &lt;code&gt;-u&lt;/code&gt; UDP, &lt;code&gt;-l&lt;/code&gt; listening only, &lt;code&gt;-p&lt;/code&gt; process and &lt;code&gt;-n&lt;/code&gt; numeric. Without &lt;code&gt;sudo&lt;/code&gt;, the process column is blank for services owned by other users. The &lt;a href="https://man7.org/linux/man-pages/man8/ss.8.html" rel="noopener noreferrer"&gt;ss manual page&lt;/a&gt; covers its powerful filter syntax.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sshd&lt;/code&gt; on &lt;code&gt;0.0.0.0:22&lt;/code&gt; is expected on a server, but on a desktop you never log in to remotely, switch it off.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check which ports are reachable from outside
&lt;/h2&gt;

&lt;p&gt;A local listener isn't necessarily reachable. Your firewall, and at home your router, sit in between. To see what another device actually sees, test from a second machine you own.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find your computer's local IP address: &lt;code&gt;ipconfig&lt;/code&gt; on Windows, &lt;code&gt;ipconfig getifaddr en0&lt;/code&gt; on a Mac, or &lt;code&gt;ip addr&lt;/code&gt; on Linux.&lt;/li&gt;
&lt;li&gt;From another device on the same network, test a port. On Windows: &lt;code&gt;Test-NetConnection 192.168.1.20 -Port 445&lt;/code&gt;. On Mac or Linux: &lt;code&gt;nc -zv 192.168.1.20 445&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TcpTestSucceeded : True&lt;/code&gt; or &lt;code&gt;succeeded&lt;/code&gt; means the port is reachable on the local network.&lt;/li&gt;
&lt;li&gt;For internet exposure, check your router's port forwarding, DMZ and UPnP settings. On a typical home connection, nothing is reachable from the internet unless one of those opens it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only test devices and networks you own or have permission to test. Scanning other people's systems can break acceptable use policies and, in the UK, potentially the Computer Misuse Act. If your router has port forwards you don't recognise, my guide to &lt;a href="https://mfaysal.com/blog/how-to-secure-your-home-wifi-router" rel="noopener noreferrer"&gt;securing your home Wi-Fi router&lt;/a&gt; shows where to find and remove them. Network scanners such as Nmap give a fuller picture when you're ready for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to close or block a port you don't need
&lt;/h2&gt;

&lt;p&gt;A port is open because a program is listening, so the cleanest fix is to stop that program rather than hide it behind a firewall. In order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify the program&lt;/strong&gt; from the PID using the commands above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn the feature off in its own settings&lt;/strong&gt; if it's something like Remote Desktop, file sharing, AirPlay Receiver or a game's host server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop and disable the service&lt;/strong&gt; if it's a background service you never use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uninstall software&lt;/strong&gt; you no longer need, such as an old database server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block the port in the firewall&lt;/strong&gt; if you need the program locally but don't want other devices reaching it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the check again&lt;/strong&gt; to confirm the port has gone or is now local only.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a service sounds unfamiliar, search its exact name before disabling it. Many Windows services have odd names but are essential; my guide to &lt;a href="https://mfaysal.com/blog/what-is-msconfig" rel="noopener noreferrer"&gt;what msconfig does&lt;/a&gt; explains how to switch services off safely and undo it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firewall commands to block a port on each system
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Check firewall&lt;/th&gt;
&lt;th&gt;Block or close an inbound port&lt;/th&gt;
&lt;th&gt;Stop a service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;`Get-NetFirewallProfile&lt;/td&gt;
&lt;td&gt;Select Name, Enabled`&lt;/td&gt;
&lt;td&gt;&lt;code&gt;New-NetFirewallRule -DisplayName "Block TCP 5432" -Direction Inbound -Protocol TCP -LocalPort 5432 -Action Block&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mac&lt;/td&gt;
&lt;td&gt;System Settings &amp;gt; Network &amp;gt; Firewall&lt;/td&gt;
&lt;td&gt;Turn on the firewall, then Options &amp;gt; set the app to Block incoming connections&lt;/td&gt;
&lt;td&gt;Turn off the matching item in System Settings &amp;gt; General &amp;gt; Sharing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu/Debian (ufw)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo ufw status verbose&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo ufw deny 5432/tcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo systemctl disable --now name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fedora/RHEL (firewalld)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo firewall-cmd --list-all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sudo firewall-cmd --permanent --remove-port=5432/tcp&lt;/code&gt; then &lt;code&gt;sudo firewall-cmd --reload&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo systemctl disable --now name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On a Mac, the built-in firewall works per app rather than per port, so you allow or block a program, not a number. Apple explains the options in its guide to &lt;a href="https://support.apple.com/guide/mac-help/block-connections-to-your-mac-with-a-firewall-mh34041/mac" rel="noopener noreferrer"&gt;blocking connections with the Mac firewall&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A warning for Linux servers: if you're connected over SSH, run &lt;code&gt;sudo ufw allow OpenSSH&lt;/code&gt; (or &lt;code&gt;sudo ufw allow 22/tcp&lt;/code&gt;) &lt;em&gt;before&lt;/em&gt; &lt;code&gt;sudo ufw enable&lt;/code&gt;, or you can lock yourself out. On firewalld, removing a port only closes it if it was opened with &lt;code&gt;--add-port&lt;/code&gt;; services opened with &lt;code&gt;--add-service&lt;/code&gt; are removed with &lt;code&gt;--remove-service&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-minute monthly port check
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Run the listening-ports command for your system and save the output to a text file.&lt;/li&gt;
&lt;li&gt;Compare it with last month's list. New entries deserve a look.&lt;/li&gt;
&lt;li&gt;Anything on &lt;code&gt;0.0.0.0&lt;/code&gt; or &lt;code&gt;[::]&lt;/code&gt; that you can't explain: identify it, then decide whether to stop it.&lt;/li&gt;
&lt;li&gt;Check that your firewall is switched on for public networks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Saving the output makes comparison easy. On Windows, &lt;code&gt;netstat -ano &amp;gt; ports.txt&lt;/code&gt; does it. On Mac and Linux, add &lt;code&gt;&amp;gt; ports.txt&lt;/code&gt; to the end of the command. An unexplained new listener is a useful early warning sign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I see which program is using a port?
&lt;/h3&gt;

&lt;p&gt;On Windows, run netstat -ano and look up the PID with tasklist, or use Get-NetTCPConnection with Get-Process. On a Mac, run sudo lsof -i :PORT -n -P. On Linux, run sudo ss -tulpn or sudo lsof -i :PORT and read the process column.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does 0.0.0.0 mean in netstat?
&lt;/h3&gt;

&lt;p&gt;It means the program is listening on every network interface on the computer, so other devices that can reach it may be able to connect. 127.0.0.1 means only programs on the same computer can connect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it bad to have open ports?
&lt;/h3&gt;

&lt;p&gt;Not necessarily. Your operating system and many legitimate apps listen on ports. The risk comes from services you don't need, services exposed to networks you don't trust, and software that's out of date. Close what you don't use and keep the rest updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does netstat show so many ports on Windows?
&lt;/h3&gt;

&lt;p&gt;Windows runs many services that use the network, including RPC on port 135, file sharing on 445 and several dynamic ports between 49152 and 65535. Most of these are normal. Focus on listeners you can't match to a program you recognise.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I close port 445 on Windows?
&lt;/h3&gt;

&lt;p&gt;If you don't share files or printers, turn off file and printer sharing for your network profile and make sure Windows Firewall is on, especially on public networks. Block it with a firewall rule rather than disabling core services you aren't sure about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does closing a port in the firewall stop the program?
&lt;/h3&gt;

&lt;p&gt;No. A firewall rule blocks connections to the port, but the program keeps running and listening locally. To fully close the port, stop or uninstall the program or turn off the feature that opened it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need admin rights to check open ports?
&lt;/h3&gt;

&lt;p&gt;You can list ports without admin rights on all three systems, but you won't see the program name for services run by other users or the system. Run the command as administrator on Windows or with sudo on Mac and Linux for the full picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a website tell me which ports are open on my computer?
&lt;/h3&gt;

&lt;p&gt;An online port checker can only test your public IP address, which at home is usually your router, not your computer. To check your computer itself, use the built-in commands in this guide or test from another device you own on the same network.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last checked: 22 October 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/how-to-check-open-ports" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>networking</category>
      <category>windows</category>
    </item>
    <item>
      <title>How to Encrypt Your Laptop: BitLocker, Device Encryption and FileVault Step by Step</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Thu, 22 Oct 2026 04:07:17 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/how-to-encrypt-your-laptop-bitlocker-device-encryption-and-filevault-step-by-step-j3m</link>
      <guid>https://dev.to/mfaysaldev/how-to-encrypt-your-laptop-bitlocker-device-encryption-and-filevault-step-by-step-j3m</guid>
      <description>&lt;p&gt;&lt;strong&gt;To encrypt a Windows laptop, open Settings &amp;gt; Privacy &amp;amp; security &amp;gt; Device encryption (Windows Home) or Manage BitLocker in Control Panel (Pro, Enterprise and Education) and turn it on. On a Mac, open System Settings &amp;gt; Privacy &amp;amp; Security &amp;gt; FileVault and click Turn On. Before you do either, decide where your recovery key will live, because without it a locked drive can't be opened.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your laptop is lost or stolen, a password on your account doesn't protect the files on its drive. Someone can take the drive out, or boot from a USB stick, and read everything. Full-disk encryption scrambles the whole drive, so the data is useless without your login or recovery key.&lt;/p&gt;

&lt;p&gt;The good news is that many laptops sold in the last few years are already encrypted, or nearly so. This guide shows you how to check, how to finish the job, and how to avoid the one mistake that causes real trouble: losing the recovery key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this guide:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which encryption does your laptop have?&lt;/li&gt;
&lt;li&gt;Step 1: back up before you change anything&lt;/li&gt;
&lt;li&gt;Step 2: check whether Windows is already encrypted&lt;/li&gt;
&lt;li&gt;Step 3: turn on Device Encryption (Windows Home)&lt;/li&gt;
&lt;li&gt;Step 4: turn on BitLocker (Windows Pro, Enterprise or Education)&lt;/li&gt;
&lt;li&gt;Step 5: find and save your BitLocker recovery key&lt;/li&gt;
&lt;li&gt;Step 6: turn on FileVault on a Mac&lt;/li&gt;
&lt;li&gt;Step 7: check FileVault from Terminal&lt;/li&gt;
&lt;li&gt;Encrypting USB sticks and external drives&lt;/li&gt;
&lt;li&gt;Mistakes that lock people out&lt;/li&gt;
&lt;li&gt;What encryption protects, and what it doesn't&lt;/li&gt;
&lt;li&gt;Frequently asked questions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Which encryption does your laptop have?
&lt;/h2&gt;

&lt;p&gt;The tool you use depends on your operating system and, on Windows, your edition. Microsoft explains the difference between the two Windows options on its &lt;a href="https://support.microsoft.com/en-us/windows/security/encryption/device-encryption-in-windows" rel="noopener noreferrer"&gt;Device Encryption support page&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Computer&lt;/th&gt;
&lt;th&gt;Built-in encryption&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows 11 or 10 Home&lt;/td&gt;
&lt;td&gt;Device Encryption&lt;/td&gt;
&lt;td&gt;Uses BitLocker underneath, with a simple on/off switch, on supported hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows 11 or 10 Pro, Enterprise, Education&lt;/td&gt;
&lt;td&gt;BitLocker Drive Encryption&lt;/td&gt;
&lt;td&gt;Full controls, including external drives (BitLocker To Go)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mac with Apple silicon or a T2 chip&lt;/td&gt;
&lt;td&gt;FileVault&lt;/td&gt;
&lt;td&gt;Data is encrypted automatically; FileVault makes your password required to unlock it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Older Intel Mac without T2&lt;/td&gt;
&lt;td&gt;FileVault&lt;/td&gt;
&lt;td&gt;Must be turned on to encrypt the drive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;LUKS (usually chosen at install)&lt;/td&gt;
&lt;td&gt;Most installers offer an "encrypt the disk" option&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not sure which Windows edition you have? Open Settings &amp;gt; System &amp;gt; About and read &lt;strong&gt;Edition&lt;/strong&gt; under Windows specifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: back up before you change anything
&lt;/h2&gt;

&lt;p&gt;Encryption rarely goes wrong, but turning it on rewrites every block of your drive, so a fresh backup is cheap insurance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy important files to an external drive or a cloud service such as OneDrive or iCloud Drive.&lt;/li&gt;
&lt;li&gt;Plug a laptop into mains power. Encryption runs in the background and uses battery.&lt;/li&gt;
&lt;li&gt;Make sure you know your account password, and on Windows, which Microsoft account you sign in with.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can keep working while the drive encrypts. Time depends on the size of the drive and how much is on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: check whether Windows is already encrypted
&lt;/h2&gt;

&lt;p&gt;There are three quick ways to check. Use whichever you're comfortable with.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Settings:&lt;/strong&gt; go to Settings &amp;gt; Privacy &amp;amp; security &amp;gt; Device encryption (on Windows 10, Settings &amp;gt; Update &amp;amp; Security &amp;gt; Device encryption). If the switch is on, the drive is encrypted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control Panel:&lt;/strong&gt; search for &lt;strong&gt;Manage BitLocker&lt;/strong&gt;. Each drive shows a status such as BitLocker on or BitLocker off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command line:&lt;/strong&gt; open Terminal or Command Prompt as administrator and run &lt;code&gt;manage-bde -status&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the command output, look at &lt;strong&gt;Conversion Status&lt;/strong&gt; and &lt;strong&gt;Protection Status&lt;/strong&gt; for drive C:. Microsoft's &lt;a href="https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/operations-guide" rel="noopener noreferrer"&gt;BitLocker operations guide&lt;/a&gt; lists the statuses you might see:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;On / Protection On&lt;/td&gt;
&lt;td&gt;The drive is encrypted and protected&lt;/td&gt;
&lt;td&gt;Check your recovery key is saved (Step 5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;The drive isn't encrypted&lt;/td&gt;
&lt;td&gt;Turn on Device Encryption or BitLocker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suspended&lt;/td&gt;
&lt;td&gt;Encrypted, but protection is paused&lt;/td&gt;
&lt;td&gt;Resume protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Waiting for Activation&lt;/td&gt;
&lt;td&gt;Encrypted with a clear key, so not yet protected&lt;/td&gt;
&lt;td&gt;Finish setup by turning protection on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Waiting for Activation" catches people out. It can appear on new laptops set up with a local account, and it means the data is not yet protected even though encryption has started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: turn on Device Encryption (Windows Home)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Sign in with an administrator account. Signing in with a Microsoft account is the easiest route, because Windows backs up the recovery key to it automatically.&lt;/li&gt;
&lt;li&gt;Open Settings &amp;gt; Privacy &amp;amp; security &amp;gt; Device encryption.&lt;/li&gt;
&lt;li&gt;Turn the switch on and let it finish in the background.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you don't see Device encryption at all, your hardware may not support it or you may be on a standard account. To find out why, search for &lt;strong&gt;System Information&lt;/strong&gt;, right-click it, choose Run as administrator, and read &lt;strong&gt;Device Encryption Support&lt;/strong&gt; at the bottom of System Summary. Common reasons include no usable TPM chip, Secure Boot switched off in the firmware settings, or the Windows Recovery Environment not being set up.&lt;/p&gt;

&lt;p&gt;Windows Home doesn't include the full BitLocker controls. If your hardware doesn't support Device Encryption, the options are upgrading to Windows Pro or using third-party encryption software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: turn on BitLocker (Windows Pro, Enterprise or Education)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Search for &lt;strong&gt;Manage BitLocker&lt;/strong&gt; and open it.&lt;/li&gt;
&lt;li&gt;Next to the operating system drive (C:), select &lt;strong&gt;Turn on BitLocker&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose how to back up your recovery key: your Microsoft account, a file, or a printout. You can pick more than one.&lt;/li&gt;
&lt;li&gt;Choose how much to encrypt. &lt;strong&gt;Used disk space only&lt;/strong&gt; is quicker and suits a new laptop. &lt;strong&gt;Entire drive&lt;/strong&gt; suits one you've used for a while, because it also covers deleted files still sitting in free space.&lt;/li&gt;
&lt;li&gt;Choose the encryption mode. &lt;strong&gt;New encryption mode&lt;/strong&gt; is right for the laptop's internal drive. &lt;strong&gt;Compatible mode&lt;/strong&gt; is for drives that might move to an older version of Windows.&lt;/li&gt;
&lt;li&gt;Leave &lt;strong&gt;Run BitLocker system check&lt;/strong&gt; ticked, continue, and restart when asked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After the restart, encryption carries on in the background. You can check progress at any time with &lt;code&gt;manage-bde -status&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: find and save your BitLocker recovery key
&lt;/h2&gt;

&lt;p&gt;The recovery key is a 48-digit number. Windows asks for it when it can't unlock the drive automatically, for example after some firmware updates, hardware changes or too many wrong PIN attempts. Microsoft's guide to &lt;a href="https://support.microsoft.com/en-us/windows/security/encryption/find-your-bitlocker-recovery-key" rel="noopener noreferrer"&gt;finding your BitLocker recovery key&lt;/a&gt; is clear that its support team can't recover a lost key for you.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you use a Microsoft account, open &lt;code&gt;aka.ms/myrecoverykey&lt;/code&gt; on another device and sign in. Check that your laptop's key is listed now, while everything works.&lt;/li&gt;
&lt;li&gt;If the laptop was ever joined to a work or school account, the key may be stored with that organisation instead.&lt;/li&gt;
&lt;li&gt;Keep a second copy somewhere separate: a printout with your important papers, or an entry in your password manager.&lt;/li&gt;
&lt;li&gt;Never store the only copy on the encrypted laptop itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the laptop was set up by someone else, such as a family member or a shop, the key may be in their Microsoft account. Sort that out now rather than at the recovery screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: turn on FileVault on a Mac
&lt;/h2&gt;

&lt;p&gt;On macOS Ventura and later, FileVault lives in System Settings. On older versions, look in System Preferences &amp;gt; Security &amp;amp; Privacy &amp;gt; FileVault. Apple's guide to &lt;a href="https://support.apple.com/guide/mac-help/protect-data-on-your-mac-with-filevault-mh11785/mac" rel="noopener noreferrer"&gt;protecting data on your Mac with FileVault&lt;/a&gt; covers each version.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose Apple menu &amp;gt; System Settings &amp;gt; Privacy &amp;amp; Security, then scroll to &lt;strong&gt;FileVault&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Turn On&lt;/strong&gt; and enter your password if asked. You need an administrator account.&lt;/li&gt;
&lt;li&gt;Choose a recovery method: let your iCloud account unlock the disk, or create a recovery key and don't use iCloud.&lt;/li&gt;
&lt;li&gt;If you create a recovery key, write it down exactly and store it away from the Mac.&lt;/li&gt;
&lt;li&gt;If other people use the Mac, enable each user so they can unlock the disk with their own password.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apple is blunt about the risk: if you forget your login password and also lose the recovery key, your files are gone for good. Once FileVault is on, automatic login is disabled and a password is always needed at startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: check FileVault from Terminal
&lt;/h2&gt;

&lt;p&gt;System Settings shows whether FileVault is on, but Terminal gives a quick, scriptable answer. Open Terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fdesetup status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see &lt;strong&gt;FileVault is On&lt;/strong&gt;, &lt;strong&gt;FileVault is Off&lt;/strong&gt;, or a progress message while encryption is running. For a simple yes or no, &lt;code&gt;fdesetup isactive&lt;/code&gt; prints &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;. Both are handy when you look after several family Macs and want to confirm each one is protected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encrypting USB sticks and external drives
&lt;/h2&gt;

&lt;p&gt;Full-disk encryption only covers the internal drive. Files you copy to a USB stick or external SSD are unprotected unless you encrypt that drive too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows Pro and above:&lt;/strong&gt; right-click the drive in File Explorer, choose &lt;strong&gt;Turn on BitLocker&lt;/strong&gt; (or use Manage BitLocker) and set a password. This is BitLocker To Go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac:&lt;/strong&gt; Control-click a Mac-formatted (APFS or Mac OS Extended) drive in Finder and choose &lt;strong&gt;Encrypt&lt;/strong&gt;, or erase it in Disk Utility with an encrypted format. Erasing wipes the drive, so copy its files off first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick a long password you can type from memory, or generate one with our &lt;a href="https://mfaysal.com/free-tools/password-generator/" rel="noopener noreferrer"&gt;password generator&lt;/a&gt; and save it in your password manager. A drive encrypted on Windows with BitLocker isn't natively readable on a Mac, and vice versa, so plan for where you'll open it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes that lock people out
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not knowing where the recovery key is.&lt;/strong&gt; This causes almost every BitLocker horror story. Check it before you need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updating BIOS or UEFI firmware without suspending BitLocker.&lt;/strong&gt; Some firmware updates trigger recovery mode. Choose &lt;strong&gt;Suspend protection&lt;/strong&gt; in Manage BitLocker first; it resumes after the restart. Some manufacturer updaters do this for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting up with a local account.&lt;/strong&gt; On Windows Home, Device Encryption isn't switched on automatically with a local account, and there's no Microsoft account to hold the key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selling or recycling a laptop with someone else's key.&lt;/strong&gt; Reset the laptop properly before it leaves your hands.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What encryption protects, and what it doesn't
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Does full-disk encryption help?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Laptop stolen while switched off&lt;/td&gt;
&lt;td&gt;Yes, the data is unreadable without your key or password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drive removed and plugged into another computer&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laptop stolen while unlocked&lt;/td&gt;
&lt;td&gt;No, so lock your screen when you walk away&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Malware or phishing while you're signed in&lt;/td&gt;
&lt;td&gt;No, you need updates, antivirus and care with links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files copied to a USB stick&lt;/td&gt;
&lt;td&gt;No, unless that drive is encrypted too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files in cloud storage&lt;/td&gt;
&lt;td&gt;No, that depends on your cloud account's security&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;File system choice matters here too. Copy a file from your encrypted laptop to a plain exFAT stick and the protection stays behind, which I explain in my guide to &lt;a href="https://mfaysal.com/blog/ntfs-vs-fat32-vs-exfat" rel="noopener noreferrer"&gt;NTFS vs FAT32 vs exFAT&lt;/a&gt;. Encryption works best alongside a strong account password, a screen that locks quickly, and two-factor authentication on your cloud accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is my laptop already encrypted?
&lt;/h3&gt;

&lt;p&gt;Possibly. Many Windows 11 laptops set up with a Microsoft account turn on Device Encryption automatically, and Macs with Apple silicon or a T2 chip encrypt their data by default. Check in Settings, Manage BitLocker, manage-bde -status on Windows, or fdesetup status on a Mac.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does encryption slow down my laptop?
&lt;/h3&gt;

&lt;p&gt;On modern laptops you're unlikely to notice a difference in everyday use, because current processors and drives handle encryption efficiently. The first encryption pass runs in the background and can make the machine feel slightly busier until it finishes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between BitLocker and Device Encryption?
&lt;/h3&gt;

&lt;p&gt;Device Encryption is a simplified version of BitLocker for Windows Home and supported hardware, with a single on/off switch. BitLocker on Pro, Enterprise and Education adds full controls, including startup PINs and encryption for external drives.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if I lose my BitLocker recovery key?
&lt;/h3&gt;

&lt;p&gt;If Windows asks for the key and you can't find it in your Microsoft account, a work account, a printout or a USB drive, the data can't be unlocked. Microsoft can't recreate it, so the only option is to reset the laptop and lose the files on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I store my FileVault key in iCloud?
&lt;/h3&gt;

&lt;p&gt;For most personal Macs, letting iCloud unlock the disk is convenient and reduces the risk of losing a written key. If you'd rather not link the two, create a recovery key and keep it somewhere safe away from the Mac.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I encrypt a laptop running Windows 11 Home without Device Encryption?
&lt;/h3&gt;

&lt;p&gt;Not with built-in tools. If System Information shows your hardware doesn't meet the prerequisites, you can enable a missing setting such as Secure Boot, upgrade to Windows Pro for BitLocker, or use reputable third-party encryption software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to turn off encryption before a Windows update?
&lt;/h3&gt;

&lt;p&gt;No, normal Windows updates work with encryption on. For BIOS or UEFI firmware updates, suspend BitLocker protection first unless the manufacturer's updater says it handles this for you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last checked: 21 October 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/how-to-encrypt-your-laptop" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>windows</category>
      <category>macos</category>
    </item>
    <item>
      <title>How to Create a Strong Password: Passphrases, Managers and Passkeys</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Wed, 21 Oct 2026 04:08:29 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/how-to-create-a-strong-password-passphrases-managers-and-passkeys-2dg0</link>
      <guid>https://dev.to/mfaysaldev/how-to-create-a-strong-password-passphrases-managers-and-passkeys-2dg0</guid>
      <description>&lt;p&gt;&lt;strong&gt;A strong password is long, unique to one account, and hard to guess. The practical way to get there is a password manager that creates and stores a different secret for every site, plus a memorable passphrase for the manager itself and for email.&lt;/strong&gt; The UK National Cyber Security Centre (NCSC) urges people to use password managers and to keep a strong, separate password for email. Many sites now also offer passkeys, which the NCSC recommends as a first choice where available.&lt;/p&gt;

&lt;p&gt;This guide is for readers in the UK and US who want clear steps without myth-busting theatre. You can generate candidate passwords with the &lt;a href="https://mfaysal.com/free-tools/password-generator/" rel="noopener noreferrer"&gt;free password generator&lt;/a&gt; on mfaysal.com, then store the winners in a manager rather than a sticky note.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes a password strong
&lt;/h2&gt;

&lt;p&gt;Length and uniqueness matter more than sprinkling punctuation into a short word. Attackers try leaked passwords, common words and automated guesses. If you reuse one password and it appears in a breach, every other account that shares it is at risk.&lt;/p&gt;

&lt;p&gt;NIST digital identity guidelines (SP 800-63B) have pushed organisations toward longer secrets, checks against known breached passwords, and away from arbitrary composition rules and forced periodic resets when nothing has gone wrong. You do not need to memorise standard numbers from NIST to benefit: favour length, uniqueness and breach screening over complex rituals.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Watch-outs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Short complex string&lt;/td&gt;
&lt;td&gt;Looks complex&lt;/td&gt;
&lt;td&gt;Easy to reuse; hard to remember; often guessed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long random string from a manager&lt;/td&gt;
&lt;td&gt;High entropy; unique per site&lt;/td&gt;
&lt;td&gt;Needs a manager and a strong primary password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diceware-style passphrase&lt;/td&gt;
&lt;td&gt;Memorable; long&lt;/td&gt;
&lt;td&gt;Must be unique; avoid famous quotes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passkey&lt;/td&gt;
&lt;td&gt;Phishing resistant; no typing&lt;/td&gt;
&lt;td&gt;Not every site supports them yet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Protect email first. If someone controls your inbox, they can reset passwords elsewhere. The NCSC tip on using a &lt;a href="https://www.ncsc.gov.uk/collection/top-tips-for-staying-secure-online/use-a-strong-and-separate-password-for-email" rel="noopener noreferrer"&gt;strong and separate password for email&lt;/a&gt; is the right starting point.&lt;/p&gt;

&lt;p&gt;US and UK readers face the same core threats: credential stuffing from leaked databases, phishing pages that copy bank logins, and malware that steals browser-saved passwords. A unique password stops stuffing from spreading. A manager auto-fill that only runs on the real domain reduces casual phishing success. Passkeys and 2SV close more of the remaining gaps.&lt;/p&gt;

&lt;p&gt;Think in accounts, not in clever spellings. One long unique secret per account beats one ornate password shared everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passphrases and Diceware-style methods
&lt;/h2&gt;

&lt;p&gt;A passphrase strings several unrelated words together so the secret is long enough to resist guessing but still memorable. Diceware-style methods pick words with dice and a word list so the choice is random rather than a favourite lyric or film title. Randomness is the point: famous example phrases from articles should never be reused as your real password.&lt;/p&gt;

&lt;p&gt;Write a process, not a pet name. For example, roll or generate four or five uncommon words, separate them with spaces or a character you will remember, and only then store the result. Avoid birthdays, team names, street addresses and keyboard walks such as qwerty.&lt;/p&gt;

&lt;p&gt;Passphrases shine as the primary password for a password manager or for the few secrets you must type often. For dozens of shopping and social accounts, let a manager generate long random strings instead of inventing a new passphrase each time.&lt;/p&gt;

&lt;p&gt;Never place your only copy of a passphrase in an unencrypted notes app that syncs everywhere without a lock. If you write it on paper, store the paper like you would store bank documents, as the NCSC discusses when it covers writing passwords down carefully.&lt;/p&gt;

&lt;p&gt;Combine words that do not form a common sentence about your life. Attackers know people pick hobby phrases. Random word lists beat autobiographical phrases every time. If you must invent without dice, close your eyes and pick from a large printed word list rather than from memory of your favourite books.&lt;/p&gt;

&lt;p&gt;Spaces inside a passphrase are fine on modern sites. If a site rejects spaces, join words with hyphens. If it rejects long secrets entirely, that site is a good candidate for a manager-generated string and for enabling 2SV immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a password manager
&lt;/h2&gt;

&lt;p&gt;Password managers create strong unique passwords, fill them on the right site, sync across your devices and warn you about known breaches. The NCSC guidance on &lt;a href="https://www.ncsc.gov.uk/collection/top-tips-for-staying-secure-online/password-managers" rel="noopener noreferrer"&gt;managing your passwords&lt;/a&gt; explains why managers beat memory for everyday accounts.&lt;/p&gt;

&lt;p&gt;You can start with the manager built into your browser or operating system, or install a third-party manager if you need cross-browser sync and richer features. On shared or public computers, never allow the browser to save passwords. On a household PC, decide whether other users should be able to unlock the same profile.&lt;/p&gt;

&lt;p&gt;Choose a primary password you can remember under stress: a long passphrase is ideal. Turn on two-step verification for the manager account itself so a stolen primary password is not enough. Update your devices and browsers so auto-fill and vault encryption stay current.&lt;/p&gt;

&lt;p&gt;When you migrate, change the most important accounts first: email, banking, work single sign-on, Apple or Google account, and the manager. Use a trusted generator if you need a one-off secret before the vault is ready, then save it into the manager immediately.&lt;/p&gt;

&lt;p&gt;Third-party managers often add breach alerts and secure sharing for families. Pick features you will actually use. The best manager is the one you keep open every day. Export a sealed backup only if you understand how to store that file safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn on two-step verification
&lt;/h2&gt;

&lt;p&gt;Two-step verification adds a second check after the password, such as an authenticator app code, a hardware security key or a prompt on a trusted device. The NCSC treats turning on 2SV as the first and most important step when you clean up password habits, because it blocks many attacks even after a phishing steal.&lt;/p&gt;

&lt;p&gt;Prefer authenticator apps or security keys over SMS where you can. SMS still helps if nothing else is offered, but SIM swaps and message interception are known risks. Store backup codes offline in a safe place when a site provides them.&lt;/p&gt;

&lt;p&gt;Enable 2SV on email before you reset other accounts. Then enable it on banking, cloud storage and social media. Work accounts may mandate a particular method; follow your organisation standard rather than inventing a parallel setup.&lt;/p&gt;

&lt;p&gt;If you lose the phone that holds your authenticator, those backup codes or a registered hardware key are what get you back in. Test recovery once while you still have access. Print codes and put them with other important papers, or store them in an encrypted backup approved by your workplace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passkeys: the next step beyond passwords
&lt;/h2&gt;

&lt;p&gt;Passkeys let you sign in with a cryptographic key unlocked by your device PIN, fingerprint or face check. You do not invent a new string, and a lookalike phishing site cannot easily collect a passkey the way it collects a typed password.&lt;/p&gt;

&lt;p&gt;The NCSC recommends making passkeys your first choice where a site offers them, and keeping strong unique passwords plus 2SV everywhere else meanwhile. We explain the security model in plain language in &lt;a href="https://mfaysal.com/blog/are-passkeys-safe" rel="noopener noreferrer"&gt;Are passkeys safe?&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Register passkeys on more than one device or use a syncing platform you trust so you are not locked out when a phone is lost. Keep a recovery path documented. Passkeys reduce password burden; they do not remove the need for device security and account recovery planning.&lt;/p&gt;

&lt;p&gt;Until every site supports passkeys, you will still need a manager. Treat passkeys as the preferred door on modern services and passwords as the fallback on older ones. When a site offers both, register the passkey and keep the password entry in your vault as a backup until you are confident recovery works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to stop doing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reusing passwords&lt;/strong&gt; across email, shopping and social media.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changing passwords on a fixed calendar&lt;/strong&gt; when nothing suggests a compromise, which modern guidance such as NIST SP 800-63B has discouraged in favour of change-after-breach.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saving passwords on public or shared machines.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using common substitutions&lt;/strong&gt; that attackers lists already include.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring breach emails.&lt;/strong&gt; If a provider says your password leaked, change that password and any reuse elsewhere immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security questions are often weak because answers are public or shared within families. Where a site forces them, treat answers like additional passwords: long, unique and stored in your manager rather than truthful trivia.&lt;/p&gt;

&lt;p&gt;Do not email passwords to yourself. Do not paste them into chat apps. Do not screenshot your vault. Do not share primary passwords with support callers who contact you first; real support will not need your full password in clear text.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple setup plan for this week
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install or enable a password manager and create a long primary passphrase.&lt;/li&gt;
&lt;li&gt;Turn on 2SV for the manager and for email.&lt;/li&gt;
&lt;li&gt;Change the email password to a unique strong secret and store it only in the manager.&lt;/li&gt;
&lt;li&gt;Replace reused passwords on banking, work and social accounts, one batch per evening.&lt;/li&gt;
&lt;li&gt;Register a passkey on sites that offer it, starting with major platforms or your bank if supported.&lt;/li&gt;
&lt;li&gt;Generate any remaining one-off secrets with a trusted generator and save them to the vault at once.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You do not need a perfect score on day one. Unique email and banking credentials plus a manager already remove most of the everyday risk that comes from password reuse.&lt;/p&gt;

&lt;p&gt;Families can share a short household checklist: who owns the manager subscription, where backup codes live, and which devices hold passkeys. Small businesses should separate personal and work vaults and follow any supplier requirements for multi-factor authentication.&lt;/p&gt;

&lt;p&gt;If you already suffered account takeover, start with email recovery through the provider official help pages, then rotate passwords on every important site while logged in from a clean device. Scan for malware if the password was stolen from your computer rather than from a remote breach.&lt;/p&gt;

&lt;p&gt;Teaching a relative? Sit with them while you install a manager and save the email password together. A thirty-minute setup beats another year of sticky notes on the monitor.&lt;/p&gt;

&lt;p&gt;This article draws on NCSC top tips and widely discussed NIST SP 800-63B ideas without inventing numeric thresholds. Product interfaces change, so follow each provider current enrolment screens when you enable 2SV or passkeys.&lt;/p&gt;

&lt;p&gt;Revisit the plan every few months when you get a new phone or laptop, and confirm passkeys and authenticator apps moved with you before you wipe the old device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I create a strong password?
&lt;/h3&gt;

&lt;p&gt;Make it long and unique. Use a password manager to generate a random password for each site, and a memorable multi-word passphrase for the manager and email.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are passphrases better than complex short passwords?
&lt;/h3&gt;

&lt;p&gt;A long random or Diceware-style passphrase is usually easier to remember and harder to guess than a short string with a few symbols.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use a password manager?
&lt;/h3&gt;

&lt;p&gt;Yes. The NCSC recommends password managers so you can use a different strong password on every account without memorising each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  What password should I protect first?
&lt;/h3&gt;

&lt;p&gt;Your email password, because inbox access lets attackers reset other accounts. Keep it strong, unique and covered by 2SV.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are passkeys safer than passwords?
&lt;/h3&gt;

&lt;p&gt;Passkeys are designed to resist phishing and reuse problems that plague passwords. The NCSC recommends using them where they are offered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to write passwords down?
&lt;/h3&gt;

&lt;p&gt;It can be, if the paper is stored securely and you still use strong unique secrets. The NCSC notes this option for some situations, with clear cautions about access and phishing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should I change passwords?
&lt;/h3&gt;

&lt;p&gt;Change a password when there is a breach, reuse problem or suspected compromise. Forced regular changes without cause are no longer best practice in modern guidance such as NIST SP 800-63B.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where can I generate a strong password?
&lt;/h3&gt;

&lt;p&gt;Use a reputable password manager generator, or a trusted tool such as the password generator on mfaysal.com, then store the result in your manager.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last checked: 18 October 2026. Rates and rules can change, so confirm anything important with the official GOV.UK or NCSC source linked above.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/how-to-create-a-strong-password" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>privacy</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Python Lazy Imports: How the New lazy Keyword Works in Python 3.15</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Tue, 20 Oct 2026 04:05:44 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/python-lazy-imports-how-the-new-lazy-keyword-works-in-python-315-4poh</link>
      <guid>https://dev.to/mfaysaldev/python-lazy-imports-how-the-new-lazy-keyword-works-in-python-315-4poh</guid>
      <description>&lt;p&gt;&lt;strong&gt;Python 3.15 adds explicit lazy imports: write &lt;code&gt;lazy import json&lt;/code&gt; (or &lt;code&gt;lazy from pathlib import Path&lt;/code&gt;) and Python won’t load the module until your code first uses that name.&lt;/strong&gt; You keep all your imports neatly at the top of the file, but you only pay the loading cost for modules a particular run actually needs. In my test, a script that imported a slow module started in 0.50 seconds with a normal import and 0.00 seconds with &lt;code&gt;lazy import&lt;/code&gt;, and the module loaded later, only when it was called.&lt;/p&gt;

&lt;p&gt;Lazy imports come from PEP 810 and are one of the biggest changes in Python 3.15.0, released on 9 October 2026. I tested everything below on Python 3.15.0rc3 (installed with &lt;code&gt;uv python install 3.15&lt;/code&gt;), and pasted the real output, including the error messages. The behaviour matches the final release notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why imports slow Python down
&lt;/h2&gt;

&lt;p&gt;When Python imports a module, the &lt;a href="https://docs.python.org/3/whatsnew/3.15.html" rel="noopener noreferrer"&gt;What’s New in Python 3.15&lt;/a&gt; guide explains, it must locate the file, read it from disk, compile it to bytecode and execute all its top-level code. For applications with deep dependency trees, that can take seconds, “even when most of the imported code is never actually used during a particular run.”&lt;/p&gt;

&lt;p&gt;Command-line tools feel this most. Typing &lt;code&gt;mytool --help&lt;/code&gt; shouldn’t load your database driver, plotting library and HTTP client, but with normal imports at the top of the file, it does.&lt;/p&gt;

&lt;p&gt;Developers have worked around this for years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Moving &lt;code&gt;import&lt;/code&gt; statements inside functions.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;importlib.import_module()&lt;/code&gt; on demand.&lt;/li&gt;
&lt;li&gt;Restructuring code to avoid importing heavy packages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They all work, but they scatter imports through the code and make it harder to see what a module depends on. The &lt;code&gt;lazy&lt;/code&gt; keyword fixes that without moving anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The syntax: lazy import and lazy from … import
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;lazy&lt;/code&gt; goes in front of an ordinary import statement. Both forms work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lazy&lt;/code&gt; is a &lt;strong&gt;soft keyword&lt;/strong&gt;, which means it’s only special at the start of an import statement. Existing code that uses &lt;code&gt;lazy&lt;/code&gt; as a variable name keeps working; &lt;code&gt;lazy = 5; print(lazy)&lt;/code&gt; printed &lt;code&gt;5&lt;/code&gt; on 3.15.&lt;/p&gt;

&lt;p&gt;Older versions don’t understand the syntax at all. Running the same file on Python 3.14 gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  File "./app_lazy.py", line 2
    lazy import heavy
         ^^^^^^
SyntaxError: invalid syntax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the keyword form is only for code that will run on 3.15 or later. There’s a compatible alternative later in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring the startup difference
&lt;/h2&gt;

&lt;p&gt;To see the effect clearly, I made a fake “heavy” module that sleeps for half a second when it loads, standing in for a big library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# heavy.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [heavy] loading...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, a normal import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app_eager.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;started in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [heavy] loading...
started in 0.50s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the lazy version, which also checks &lt;code&gt;sys.modules&lt;/code&gt; (Python’s registry of loaded modules) before and after first use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app_lazy.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;started in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in sys.modules?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heavy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in sys.modules?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;started in 0.00s
in sys.modules? False
  [heavy] loading...
report ready
in sys.modules? True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The import line cost nothing. The module only loaded at &lt;code&gt;heavy.report()&lt;/code&gt;, and from then on it’s a completely normal module. If a run never touches &lt;code&gt;heavy&lt;/code&gt;, it never loads at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a lazy import actually creates
&lt;/h2&gt;

&lt;p&gt;Behind the scenes, a lazy import binds the name to a lightweight &lt;strong&gt;proxy object&lt;/strong&gt;. The proxy type is available as &lt;code&gt;types.LazyImportType&lt;/code&gt;, and &lt;code&gt;sys.get_lazy_imports()&lt;/code&gt; tells you the current global mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;
&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dumps&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LazyImportType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_lazy_imports&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dumps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lazy_import True
normal
{"ok": true}
function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I peeked through &lt;code&gt;globals()&lt;/code&gt; on purpose, because simply writing &lt;code&gt;heavy&lt;/code&gt; would trigger the load. After &lt;code&gt;dumps()&lt;/code&gt; was first called, the name was replaced by the real function. The mode &lt;code&gt;normal&lt;/code&gt; is the default: only imports marked &lt;code&gt;lazy&lt;/code&gt; are lazy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where lazy imports aren’t allowed
&lt;/h2&gt;

&lt;p&gt;The 3.15 documentation lists three restrictions, and each one gives a clear &lt;code&gt;SyntaxError&lt;/code&gt;. I tried all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inside a function&lt;/strong&gt; (or class body):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SyntaxError: lazy import not allowed inside functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Inside try/except&lt;/strong&gt; (or &lt;code&gt;finally&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SyntaxError: lazy import not allowed inside try/except blocks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Star imports&lt;/strong&gt; (and &lt;code&gt;from __future__&lt;/code&gt; imports):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SyntaxError: lazy from ... import * is not allowed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The try/except rule makes sense once you think about it: a lazy import doesn’t run at that line, so an &lt;code&gt;except ImportError&lt;/code&gt; around it could never catch anything. If you need an optional dependency with a fallback, keep using a normal import inside &lt;code&gt;try&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How errors show up with lazy imports
&lt;/h2&gt;

&lt;p&gt;Because nothing is loaded at the import line, a missing module isn’t reported there. The documentation says the exception is raised at the point of first use, with a traceback that includes both places. Here’s what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;does_not_exist&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;import line passed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;does_not_exist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import line passed
Traceback (most recent call last):
  File "./app_missing.py", line 1, in &amp;lt;module&amp;gt;
    lazy import does_not_exist
ImportError: lazy import of 'does_not_exist' raised an exception during resolution

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "./app_missing.py", line 3, in &amp;lt;module&amp;gt;
    does_not_exist.run()
    ^^^^^^^^^^^^^^
ModuleNotFoundError: No module named 'does_not_exist'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program printed “import line passed” before failing. That’s the main trade-off of lazy imports: typos in module names and missing dependencies surface later, possibly in a code path your tests don’t cover. Run your test suite with good coverage, and consider a quick smoke test that imports everything eagerly (see the global mode below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Making every import lazy: -X lazy_imports and PYTHON_LAZY_IMPORTS
&lt;/h2&gt;

&lt;p&gt;You can switch the behaviour on for a whole program without editing any source, using either the &lt;code&gt;-X lazy_imports&lt;/code&gt; command-line option or the &lt;code&gt;PYTHON_LAZY_IMPORTS&lt;/code&gt; environment variable. Both accept &lt;code&gt;all&lt;/code&gt; (make all imports lazy by default) or &lt;code&gt;normal&lt;/code&gt; (the default, respecting only the keyword).&lt;/p&gt;

&lt;p&gt;With this plain file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app_all.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all-mode: started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heavy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3.15 -X lazy_imports=all app_all.py
all-mode: started
  [heavy] loading...
report ready

$ PYTHON_LAZY_IMPORTS=all python3.15 app_all.py
all-mode: started
  [heavy] loading...
report ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ordinary &lt;code&gt;import heavy&lt;/code&gt; became lazy: “started” printed before the module loaded. This is a quick way to measure how much your app would gain before you change any code. Some libraries rely on side effects at import time (registering plugins, patching other modules), so test carefully before turning &lt;code&gt;all&lt;/code&gt; on in production.&lt;/p&gt;

&lt;p&gt;You can also change the mode at runtime with &lt;code&gt;sys.set_lazy_imports()&lt;/code&gt; and read it with &lt;code&gt;sys.get_lazy_imports()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing which modules are lazy with a filter
&lt;/h2&gt;

&lt;p&gt;For finer control, &lt;code&gt;sys.set_lazy_imports_filter()&lt;/code&gt; takes a function that decides per import. It receives the importing module’s name, the imported module’s name and the &lt;code&gt;fromlist&lt;/code&gt;, and returns &lt;code&gt;True&lt;/code&gt; to allow lazy loading. Here only &lt;code&gt;heavy&lt;/code&gt; is allowed to be lazy, and &lt;code&gt;json&lt;/code&gt; is forced to load eagerly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;only_heavy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;importing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fromlist&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;imported&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_lazy_imports_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;only_heavy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_lazy_imports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy loaded?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;| json loaded?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;heavy loaded? False | json loaded? True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The documentation suggests a similar pattern for making only your own application’s modules lazy (for example, everything starting with &lt;code&gt;myapp.&lt;/code&gt;) while keeping third-party libraries eager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supporting Python 3.14 and older: &lt;strong&gt;lazy_modules&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If your code must still run on older versions, you can’t use the keyword, because it’s a syntax error there. Instead, define &lt;code&gt;__lazy_modules__&lt;/code&gt; at the top of the module: a list of fully qualified module names. On 3.15, normal imports of those modules become lazy; on older versions the variable is just an unused list and the imports run eagerly as before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;__lazy_modules__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;compat: started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heavy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;compat: started
  [heavy] loading...
report ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s the best of both worlds for libraries: faster startup on 3.15, no breakage on 3.14.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real-world example: a command-line tool
&lt;/h2&gt;

&lt;p&gt;Here’s the situation lazy imports were designed for. This small tool has two sub-commands. Only &lt;code&gt;report&lt;/code&gt; needs the slow module, but with a normal import, &lt;code&gt;version&lt;/code&gt; would pay for it too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tool.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool 1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heavy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage: tool.py [version|report]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I timed both commands from the shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ time python3.15 tool.py version
tool 1.0
real 0.01s
$ time python3.15 tool.py report
  [heavy] loading...
report ready
real 0.51s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;version&lt;/code&gt; command never loads &lt;code&gt;heavy&lt;/code&gt;, so it returns almost instantly. The &lt;code&gt;report&lt;/code&gt; command loads it when it’s needed. The import still sits at the top of the file, where anyone reading the code can see every dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lazy imports vs importing inside a function
&lt;/h2&gt;

&lt;p&gt;The traditional workaround is to put the import inside the function that needs it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_report&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;          &lt;span class="c1"&gt;# old trick: import inside the function
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;make_report&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;started 0.00s False
  [heavy] loading...
report ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works on every Python version and gives the same startup saving, so it’s still a good choice for code that must support 3.14 and earlier. The downsides are readability and repetition: dependencies are hidden inside functions, and if three functions need the module, you write the import three times. With &lt;code&gt;lazy import&lt;/code&gt; the dependency is declared once, at the top, and loaded once, on first use.&lt;/p&gt;

&lt;p&gt;There’s also &lt;code&gt;importlib.util.LazyLoader&lt;/code&gt;, which has existed for years, but it needs several lines of setup code per module. The new keyword replaces that boilerplate for most everyday cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to measure your own import time
&lt;/h2&gt;

&lt;p&gt;Before optimising, find out which imports are actually slow. Python has had a built-in import profiler for years: &lt;code&gt;python -X importtime your_script.py&lt;/code&gt; prints how long each module took to import (in microseconds), with nested imports indented. Sort by the cumulative column and the expensive ones stand out. Those are your best candidates for &lt;code&gt;lazy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A rough checklist I’d follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;python3.15 -X importtime app.py 2&amp;gt; imports.txt&lt;/code&gt; and look for the biggest cumulative times.&lt;/li&gt;
&lt;li&gt;Try &lt;code&gt;-X lazy_imports=all&lt;/code&gt; and time the startup again to see the upper limit of what you can gain.&lt;/li&gt;
&lt;li&gt;Mark the slow, rarely used imports with &lt;code&gt;lazy&lt;/code&gt;, starting with ones only needed in one command or code path.&lt;/li&gt;
&lt;li&gt;Run your tests, and check that import-time side effects (such as plugin registration) still happen when needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When lazy imports help (and when they don’t)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;They help most&lt;/strong&gt; in command-line tools with many sub-commands, web apps with rarely used admin features, test suites where each test touches a few modules, and serverless functions where cold-start time costs money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They don’t help&lt;/strong&gt; when every run uses every import anyway; you only move the cost later. They also won’t speed up code inside a hot loop, because after first use a lazily imported module is a normal module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They can surprise you&lt;/strong&gt; when a module does something important at import time. If &lt;code&gt;import myapp.plugins&lt;/code&gt; registers handlers, making it lazy means those handlers don’t exist until something touches &lt;code&gt;myapp.plugins&lt;/code&gt;. Keep such imports eager.&lt;/p&gt;

&lt;p&gt;For file and path checks at startup, which often go hand in hand with deciding what to load, my guide to &lt;a href="https://mfaysal.com/blog/python-check-if-file-exists" rel="noopener noreferrer"&gt;checking if a file exists in Python&lt;/a&gt; covers &lt;code&gt;pathlib&lt;/code&gt;, which itself is a good candidate for &lt;code&gt;lazy from pathlib import Path&lt;/code&gt; in small scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a lazy import in Python?
&lt;/h3&gt;

&lt;p&gt;A lazy import delays loading a module until the imported name is first used. In Python 3.15 you write &lt;code&gt;lazy import module&lt;/code&gt; or &lt;code&gt;lazy from module import name&lt;/code&gt;, and Python binds a lightweight proxy that loads the real module on first access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Python version supports the lazy keyword?
&lt;/h3&gt;

&lt;p&gt;Python 3.15 and later, released on 9 October 2026 (PEP 810). On Python 3.14 and earlier, &lt;code&gt;lazy import&lt;/code&gt; is a &lt;code&gt;SyntaxError&lt;/code&gt;. Use &lt;code&gt;__lazy_modules__&lt;/code&gt; if your code must run on both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use lazy import inside a function?
&lt;/h3&gt;

&lt;p&gt;No. Lazy imports are only allowed at module level. Using &lt;code&gt;lazy&lt;/code&gt; inside a function, class body or &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt;/&lt;code&gt;finally&lt;/code&gt; block raises a &lt;code&gt;SyntaxError&lt;/code&gt;. A normal import inside a function is already effectively lazy.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if a lazily imported module doesn’t exist?
&lt;/h3&gt;

&lt;p&gt;The import line succeeds, and the error appears when you first use the name. Python raises an &lt;code&gt;ImportError&lt;/code&gt; chained to the original &lt;code&gt;ModuleNotFoundError&lt;/code&gt;, with a traceback showing both the import line and the line where you used it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I make all imports lazy?
&lt;/h3&gt;

&lt;p&gt;Run Python with &lt;code&gt;-X lazy_imports=all&lt;/code&gt; or set the environment variable &lt;code&gt;PYTHON_LAZY_IMPORTS=all&lt;/code&gt;. You can also call &lt;code&gt;sys.set_lazy_imports("all")&lt;/code&gt; at runtime, and use &lt;code&gt;sys.set_lazy_imports_filter()&lt;/code&gt; to exclude specific modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do lazy imports make my code faster?
&lt;/h3&gt;

&lt;p&gt;They make startup faster when some imports aren’t needed on every run. They don’t speed up code that runs after the module is loaded, and if every import is used on every run, the total time stays about the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is lazy a reserved word now?
&lt;/h3&gt;

&lt;p&gt;No. It’s a soft keyword, only special at the start of an import statement. Variables named &lt;code&gt;lazy&lt;/code&gt; still work.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/python-lazy-imports" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>performance</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>JavaScript Temporal API Tutorial: Replace Date with Tested Examples (2026)</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:05:32 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/javascript-temporal-api-tutorial-replace-date-with-tested-examples-2026-ijf</link>
      <guid>https://dev.to/mfaysaldev/javascript-temporal-api-tutorial-replace-date-with-tested-examples-2026-ijf</guid>
      <description>&lt;p&gt;&lt;strong&gt;Temporal is JavaScript’s new built-in date and time API, designed to replace the old &lt;code&gt;Date&lt;/code&gt; object.&lt;/strong&gt; Instead of one confusing type, it gives you separate, immutable types for each job: &lt;code&gt;Temporal.PlainDate&lt;/code&gt; for a calendar date such as a birthday, &lt;code&gt;Temporal.ZonedDateTime&lt;/code&gt; for an exact moment in a named time zone, &lt;code&gt;Temporal.Duration&lt;/code&gt; for lengths of time, and &lt;code&gt;Temporal.Instant&lt;/code&gt; for a timestamp. Months are numbered 1 to 12, adding a month to 31 January gives 28 February instead of 2 March, and time zones are handled properly.&lt;/p&gt;

&lt;p&gt;Temporal is trending because it has finally shipped in mainstream runtimes. &lt;a href="https://nodejs.org/en/blog/release/v26.0.0/" rel="noopener noreferrer"&gt;Node.js 26.0.0&lt;/a&gt;, released on 5 May 2026, turned it on by default, and Node 26 is due to become the long-term support (LTS) release this October. Chrome 144 (13 January 2026) and Firefox 139 (27 May 2025) also support it. I ran every example below on &lt;strong&gt;Node.js 26.11.1&lt;/strong&gt; on 12 October 2026 and pasted the real output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why replace Date?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Date&lt;/code&gt; has been in JavaScript since 1995 and has well-known problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Months start at 0.&lt;/strong&gt; &lt;code&gt;new Date(2026, 0, 31)&lt;/code&gt; is 31 January, not a mistake you’d guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It’s mutable.&lt;/strong&gt; &lt;code&gt;setMonth()&lt;/code&gt; changes the object in place, so passing a date to a function can silently change it for the caller.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only two time zones.&lt;/strong&gt; A &lt;code&gt;Date&lt;/code&gt; only knows UTC and the computer’s local zone. Showing a meeting time in London and Dhaka needs extra libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Surprising arithmetic.&lt;/strong&gt; Here’s what happens when you add a month to 31 January with &lt;code&gt;Date&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&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;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-03-02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s no 31 February, so &lt;code&gt;Date&lt;/code&gt; rolls over into March. Temporal’s answer to the same question is coming up below. If you just need to format an existing &lt;code&gt;Date&lt;/code&gt;, my guide to &lt;a href="https://mfaysal.com/blog/javascript-format-date-dd-mm-yyyy" rel="noopener noreferrer"&gt;formatting a date as dd/mm/yyyy in JavaScript&lt;/a&gt; still works; Temporal is for when you need real date maths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check that Temporal is available
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Temporal&lt;/code&gt; is a global object, like &lt;code&gt;Math&lt;/code&gt;. Feature-detect it before relying on it:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Node.js 26 this printed &lt;code&gt;object&lt;/code&gt;. On the box’s older Node.js 20 it printed &lt;code&gt;undefined&lt;/code&gt;. Browser support, from the &lt;a href="https://web-platform-dx.github.io/web-features-explorer/features/temporal/" rel="noopener noreferrer"&gt;web-features explorer&lt;/a&gt; on 12 October 2026, is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chrome and Edge:&lt;/strong&gt; supported from version 144 (January 2026).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firefox:&lt;/strong&gt; supported from version 139 (May 2025).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safari (macOS and iOS):&lt;/strong&gt; not supported yet, which is why Temporal is still marked “limited availability” rather than Baseline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For public websites, that Safari gap matters. Use a polyfill such as &lt;code&gt;@js-temporal/polyfill&lt;/code&gt; or &lt;code&gt;temporal-polyfill&lt;/code&gt; when &lt;code&gt;globalThis.Temporal&lt;/code&gt; is missing, or keep Temporal to server-side code on Node 26 for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Temporal types at a glance
&lt;/h2&gt;

&lt;p&gt;Picking the right type is most of the work. Here’s the short version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Temporal.PlainDate&lt;/code&gt;:&lt;/strong&gt; a calendar date with no time or zone. Birthdays, deadlines, bank holidays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Temporal.PlainTime&lt;/code&gt;:&lt;/strong&gt; a wall-clock time with no date. Opening hours, alarm times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Temporal.PlainDateTime&lt;/code&gt;:&lt;/strong&gt; a date and time with no zone. Rarely the right choice for real events, but useful for form inputs before you know the zone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Temporal.ZonedDateTime&lt;/code&gt;:&lt;/strong&gt; an exact moment plus a time zone such as &lt;code&gt;Europe/London&lt;/code&gt;. Meetings, flights, anything that happens at a real moment for people in a place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Temporal.Instant&lt;/code&gt;:&lt;/strong&gt; an exact moment with no zone, like a timestamp in a database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Temporal.Duration&lt;/code&gt;:&lt;/strong&gt; a length of time, like “2 months and 2 days”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Temporal.PlainYearMonth&lt;/code&gt; and &lt;code&gt;Temporal.PlainMonthDay&lt;/code&gt;:&lt;/strong&gt; “February 2026” or “25 December” on their own.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  PlainDate: working with calendar dates
&lt;/h2&gt;

&lt;p&gt;Create a date from an ISO string (&lt;code&gt;YYYY-MM-DD&lt;/code&gt;) and read its parts:&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-12&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfWeek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daysInMonth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inLeapYear&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-10-12 2026 10 12 1 31 false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;October is month &lt;strong&gt;10&lt;/strong&gt;, not 9. &lt;code&gt;dayOfWeek&lt;/code&gt; runs from 1 (Monday) to 7 (Sunday), so 12 October 2026 is a Monday. Useful properties like &lt;code&gt;daysInMonth&lt;/code&gt; and &lt;code&gt;inLeapYear&lt;/code&gt; are built in.&lt;/p&gt;

&lt;p&gt;To get today’s date in the computer’s time zone, use &lt;code&gt;Temporal.Now.plainDateISO()&lt;/code&gt;. For the current exact moment, use &lt;code&gt;Temporal.Now.instant()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start and end of the month
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;with()&lt;/code&gt; returns a copy with some fields replaced:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daysInMonth&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-10-01 2026-10-31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding days and months (and the 31 January problem)
&lt;/h2&gt;

&lt;p&gt;Temporal objects are &lt;strong&gt;immutable&lt;/strong&gt;: &lt;code&gt;add()&lt;/code&gt; and &lt;code&gt;subtract()&lt;/code&gt; return a new object and leave the original alone.&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;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-12&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;later&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;later&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-10-12 2026-10-19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the same month problem that tripped up &lt;code&gt;Date&lt;/code&gt;:&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;jan31&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-01-31&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jan31&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jan31&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-02-28
RangeError: Temporal error: not a valid ISO date.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default Temporal &lt;strong&gt;constrains&lt;/strong&gt; the result to the last valid day, 28 February, which is what most people expect for “one month later”. If you’d rather catch the problem, pass &lt;code&gt;{ overflow: "reject" }&lt;/code&gt; and you get a &lt;code&gt;RangeError&lt;/code&gt; instead. The exact error wording comes from Node’s engine and may differ in browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differences between dates: until() and since()
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;until()&lt;/code&gt; returns a &lt;code&gt;Temporal.Duration&lt;/code&gt;. By default the largest unit for dates is days, but you can ask for months or years:&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;exam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-12-14&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exam&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;largestUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;months&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exam&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P63D P2M2D 63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;P63D&lt;/code&gt; is the ISO 8601 way of writing “63 days”, and &lt;code&gt;P2M2D&lt;/code&gt; is “2 months, 2 days”. Read the parts with properties such as &lt;code&gt;.days&lt;/code&gt; and &lt;code&gt;.months&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working out someone’s age
&lt;/h3&gt;

&lt;p&gt;Age calculations are notoriously fiddly with &lt;code&gt;Date&lt;/code&gt;. With Temporal, ask for years:&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;birth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2004-02-29&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-12&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;largestUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;years&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;years&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even a leap-day birthday works without special-casing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing and sorting dates
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;Temporal.PlainDate.compare&lt;/code&gt; as a sort function and &lt;code&gt;equals()&lt;/code&gt; for equality. Don’t use &lt;code&gt;===&lt;/code&gt;, which compares object identity, not dates:&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;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-12-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-01-15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-06-30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-12&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-01-15, 2026-06-30, 2026-12-01 true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For skipping weekends and bank holidays, the logic in my post on &lt;a href="https://mfaysal.com/blog/javascript-working-days-uk-bank-holidays" rel="noopener noreferrer"&gt;counting UK working days in JavaScript&lt;/a&gt; becomes simpler with &lt;code&gt;dayOfWeek&lt;/code&gt; and &lt;code&gt;add({ days: 1 })&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ZonedDateTime: time zones done properly
&lt;/h2&gt;

&lt;p&gt;This is where Temporal really shines. Create a moment in a named IANA time zone, then view it in other zones:&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;meet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-12T15:00[Europe/London]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;meet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;meet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Asia/Dhaka&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;meet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toPlainTime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-10-12T15:00:00+01:00[Europe/London]
2026-10-12T20:00:00+06:00[Asia/Dhaka]
10:00:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 3 pm meeting in London (on British Summer Time, +01:00) is 8 pm in Dhaka and 10 am in New York. The string keeps both the offset and the zone name, so it can be stored and read back without losing information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Daylight saving time: days vs hours
&lt;/h3&gt;

&lt;p&gt;UK clocks go back on Sunday 25 October 2026. Watch what happens when you add “1 day” versus “24 hours” across that change:&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;beforeDst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-24T12:00[Europe/London]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beforeDst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;beforeDst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-10-25T12:00:00+00:00[Europe/London] 2026-10-25T11:00:00+00:00[Europe/London]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a &lt;strong&gt;day&lt;/strong&gt; keeps the same wall-clock time (noon). Adding &lt;strong&gt;24 hours&lt;/strong&gt; lands at 11:00, because that particular day was 25 hours long. Temporal makes you choose which one you mean, which is exactly the kind of bug &lt;code&gt;Date&lt;/code&gt; hides. The same issue exists on the server side; I covered it for PHP in &lt;a href="https://mfaysal.com/blog/php-timezone-europe-london" rel="noopener noreferrer"&gt;setting the timezone to Europe/London in PHP&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Durations
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Temporal.Duration&lt;/code&gt; represents a length of time. It doesn’t automatically “balance” units, but &lt;code&gt;round()&lt;/code&gt; and &lt;code&gt;total()&lt;/code&gt; will:&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;dur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;largestUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hours&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minutes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PT1H90M PT2H30M 150
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PT1H90M&lt;/code&gt; is kept exactly as you wrote it, rounding gives &lt;code&gt;PT2H30M&lt;/code&gt; (2 hours 30 minutes), and &lt;code&gt;total()&lt;/code&gt; converts it to a single number of minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plain times, date-times and month-days
&lt;/h2&gt;

&lt;p&gt;The other Plain types follow the same pattern:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;09:15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-12T23:30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainYearMonth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-02&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;daysInMonth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainMonthDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12-25&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toPlainDate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2026&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;dayOfWeek&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10:05:00
2026-10-13T00:30:00
28 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 50-minute lesson starting at 09:15 ends at 10:05. Adding an hour to 23:30 rolls into the next day. February 2026 has 28 days, and Christmas Day 2026 is a Friday (day 5).&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting between Temporal and Date
&lt;/h2&gt;

&lt;p&gt;You’ll be mixing old and new code for a while, so conversions matter. An &lt;code&gt;Instant&lt;/code&gt; maps cleanly to and from epoch milliseconds, and &lt;code&gt;Date&lt;/code&gt; has a new &lt;code&gt;toTemporalInstant()&lt;/code&gt; method:&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;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEpochMilliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;epochMilliseconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;epochMilliseconds&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-12T09:30:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toTemporalInstant&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-10-12T09:30:00Z 1791797400000 2026-10-12T09:30:00.000Z
2026-10-12T09:30:00Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the old &lt;code&gt;Date.UTC(2026, 9, ...)&lt;/code&gt; needs month 9 for October. That’s the zero-based month problem again, and one more reason to move to Temporal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting dates for people
&lt;/h2&gt;

&lt;p&gt;Every Temporal type has &lt;code&gt;toLocaleString()&lt;/code&gt;, which uses the same &lt;code&gt;Intl&lt;/code&gt; options you may know from &lt;code&gt;Date&lt;/code&gt;:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-GB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dateStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;meet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-GB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dateStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timeStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monday, 12 October 2026
12 Oct 2026, 15:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One gotcha I hit: combining &lt;code&gt;dateStyle&lt;/code&gt; or &lt;code&gt;timeStyle&lt;/code&gt; with individual options such as &lt;code&gt;timeZoneName&lt;/code&gt; throws &lt;code&gt;TypeError: Invalid option&lt;/code&gt;. That’s an &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; rule, not a Temporal bug. Use either the style shortcuts or individual fields like &lt;code&gt;{ day: "numeric", month: "long", timeZoneName: "short" }&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation: rejecting bad dates
&lt;/h2&gt;

&lt;p&gt;Temporal refuses to parse impossible ISO strings and non-ISO formats:&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-02-30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12/10/2026&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RangeError: Temporal error: Parsed day value not in a valid range.
RangeError: Temporal error: Invalid character while parsing year value.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my tests, &lt;code&gt;"2026-02-30"&lt;/code&gt; was rejected even without the &lt;code&gt;overflow&lt;/code&gt; option, because ISO strings are validated strictly. If you build a date from separate fields, though, the default &lt;code&gt;overflow: "constrain"&lt;/code&gt; clamps it: &lt;code&gt;Temporal.PlainDate.from({ year: 2026, month: 2, day: 30 })&lt;/code&gt; gave &lt;code&gt;2026-02-28&lt;/code&gt;. For user input from a form, parse the parts yourself and pass &lt;code&gt;{ overflow: "reject" }&lt;/code&gt; so mistakes are caught rather than silently adjusted.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"12/10/2026"&lt;/code&gt; fails outright. That’s a feature: it’s ambiguous (12 October or 10 December?), and Temporal won’t guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two practical examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adding working days
&lt;/h3&gt;

&lt;p&gt;Skipping weekends is a loop over &lt;code&gt;dayOfWeek&lt;/code&gt; (Saturday is 6, Sunday is 7):&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;function&lt;/span&gt; &lt;span class="nf"&gt;addWorkingDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfWeek&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;friday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;friday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfWeek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;addWorkingDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;friday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 2026-10-21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three working days after Friday 16 October is Wednesday 21 October. Because &lt;code&gt;add()&lt;/code&gt; never mutates, there’s no risk of changing the caller’s &lt;code&gt;friday&lt;/code&gt; by accident.&lt;/p&gt;

&lt;h3&gt;
  
  
  A countdown across time zones
&lt;/h3&gt;

&lt;p&gt;Here’s a countdown from 09:15 in Dhaka to a launch at 18:00 in London. My first attempt threw an error worth knowing about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RangeError: Temporal error: Timezones must be the same if unit is a day unit.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Days can be 23 or 25 hours long depending on the zone, so Temporal refuses to count days between two different zones. Convert one side first:&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;function&lt;/span&gt; &lt;span class="nf"&gt;countdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeZoneId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;largestUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;days&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;smallestUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minutes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;d &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;h &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;m`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-12T09:15[Asia/Dhaka]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;launch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-20T18:00[Europe/London]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;countdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8d 13h 45m
{"launch":"2026-10-20T18:00:00+01:00[Europe/London]"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;JSON.stringify&lt;/code&gt; uses the full string with offset and zone, and &lt;code&gt;Temporal.ZonedDateTime.from()&lt;/code&gt; reads it back to an equal value, so storing Temporal values in JSON or a text column is lossless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Temporal mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Using PlainDateTime for real events.&lt;/strong&gt; Without a zone, “2026-10-25T01:30” is ambiguous on the night the clocks change. Use &lt;code&gt;ZonedDateTime&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparing with &lt;code&gt;===&lt;/code&gt; or &lt;code&gt;&amp;lt;&lt;/code&gt;.&lt;/strong&gt; Use &lt;code&gt;equals()&lt;/code&gt; and &lt;code&gt;compare()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expecting Duration to balance itself.&lt;/strong&gt; Call &lt;code&gt;round()&lt;/code&gt; or &lt;code&gt;total()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting Safari.&lt;/strong&gt; Feature-detect and polyfill for browser code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming error messages are identical everywhere.&lt;/strong&gt; Catch &lt;code&gt;RangeError&lt;/code&gt; by type, not by message text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counting days between two different time zones.&lt;/strong&gt; Convert one value with &lt;code&gt;withTimeZone()&lt;/code&gt; first, as in the countdown example.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storing a PlainDateTime when you meant an Instant.&lt;/strong&gt; For logs and database timestamps, store an &lt;code&gt;Instant&lt;/code&gt; (or a &lt;code&gt;ZonedDateTime&lt;/code&gt; string) so the exact moment is never lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the Temporal API in JavaScript?
&lt;/h3&gt;

&lt;p&gt;Temporal is a built-in global object that provides modern, immutable date and time types, including &lt;code&gt;PlainDate&lt;/code&gt;, &lt;code&gt;ZonedDateTime&lt;/code&gt;, &lt;code&gt;Instant&lt;/code&gt; and &lt;code&gt;Duration&lt;/code&gt;. It’s designed to replace the older &lt;code&gt;Date&lt;/code&gt; object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Temporal supported in Node.js?
&lt;/h3&gt;

&lt;p&gt;Yes. Node.js 26.0.0, released on 5 May 2026, enables Temporal by default with no flag. Node.js 20 and older don’t have it, so &lt;code&gt;typeof Temporal&lt;/code&gt; returns &lt;code&gt;"undefined"&lt;/code&gt; there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which browsers support Temporal?
&lt;/h3&gt;

&lt;p&gt;As of 12 October 2026, Chrome and Edge 144+ and Firefox 139+ support it. Safari on macOS and iOS doesn’t yet, so use a polyfill for public websites.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between PlainDate and ZonedDateTime?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PlainDate&lt;/code&gt; is just a calendar date with no time or time zone, like a birthday. &lt;code&gt;ZonedDateTime&lt;/code&gt; is an exact moment in a specific time zone, such as a meeting at 15:00 in Europe/London.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I add months to a date in JavaScript without bugs?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;Temporal.PlainDate.from("2026-01-31").add({ months: 1 })&lt;/code&gt;, which returns 28 February 2026. Pass &lt;code&gt;{ overflow: "reject" }&lt;/code&gt; if you’d rather get an error than an adjusted date.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I convert a Date to Temporal?
&lt;/h3&gt;

&lt;p&gt;Call &lt;code&gt;date.toTemporalInstant()&lt;/code&gt; to get a &lt;code&gt;Temporal.Instant&lt;/code&gt;, then &lt;code&gt;instant.toZonedDateTimeISO("Europe/London")&lt;/code&gt; if you need a time zone. To go back, use &lt;code&gt;new Date(instant.epochMilliseconds)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are Temporal objects mutable?
&lt;/h3&gt;

&lt;p&gt;No. Every Temporal object is immutable. Methods such as &lt;code&gt;add()&lt;/code&gt;, &lt;code&gt;subtract()&lt;/code&gt; and &lt;code&gt;with()&lt;/code&gt; return a new object and leave the original unchanged.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/javascript-temporal-api-tutorial" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why Is My Hard Drive Smaller Than Advertised? GB vs GiB Explained</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Sun, 18 Oct 2026 04:05:14 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/why-is-my-hard-drive-smaller-than-advertised-gb-vs-gib-explained-f7a</link>
      <guid>https://dev.to/mfaysaldev/why-is-my-hard-drive-smaller-than-advertised-gb-vs-gib-explained-f7a</guid>
      <description>&lt;p&gt;&lt;strong&gt;A drive sold as 1TB shows as about 931GB in Windows because the two use different units. Drive makers count 1TB as 1,000,000,000,000 bytes (powers of 1,000). Windows divides by powers of 1,024 and still labels the result "GB", when it is really gibibytes (GiB).&lt;/strong&gt; No space has gone missing; the same number of bytes is being described two ways. A small extra amount also goes to partitions and the file system.&lt;/p&gt;

&lt;p&gt;Below you will find the maths, a table for common drive sizes, and how to check your own drive. For quick conversions, use the &lt;a href="https://mfaysal.com/free-tools/gb-to-tb-converter/" rel="noopener noreferrer"&gt;GB to TB converter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decimal vs binary units explained
&lt;/h2&gt;

&lt;p&gt;Computers work in binary, so early software measured memory in powers of 2. A "kilobyte" came to mean 1,024 bytes because 2 to the power of 10 is 1,024, which is close to 1,000. The gap is small at that size but grows with each step.&lt;/p&gt;

&lt;p&gt;To end the confusion, the International Electrotechnical Commission created binary prefixes in 1998: kibibyte (KiB), mebibyte (MiB), gibibyte (GiB) and tebibyte (TiB). The &lt;a href="https://physics.nist.gov/cuu/Units/binary.html" rel="noopener noreferrer"&gt;US National Institute of Standards and Technology explains these prefixes&lt;/a&gt;. Drive makers use the standard SI meaning, so 1GB is 1,000,000,000 bytes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decimal (SI)&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Binary (IEC)&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 KB&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1 KiB&lt;/td&gt;
&lt;td&gt;1,024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 MB&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;1 MiB&lt;/td&gt;
&lt;td&gt;1,048,576&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;1,000,000,000&lt;/td&gt;
&lt;td&gt;1 GiB&lt;/td&gt;
&lt;td&gt;1,073,741,824&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 TB&lt;/td&gt;
&lt;td&gt;1,000,000,000,000&lt;/td&gt;
&lt;td&gt;1 TiB&lt;/td&gt;
&lt;td&gt;1,099,511,627,776&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The maths: why 1TB becomes 931GB
&lt;/h2&gt;

&lt;p&gt;Take a 1TB drive with 1,000,000,000,000 bytes. Windows divides by 1,024 three times to get "GB":&lt;/p&gt;

&lt;p&gt;1,000,000,000,000 ÷ 1,073,741,824 = 931.32&lt;/p&gt;

&lt;p&gt;So Windows reports about 931GB. The difference is roughly 6.9% at the terabyte level, and it grows to about 9.1% at the tebibyte level for larger drives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advertised vs shown size for common drives
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Advertised&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Shown in Windows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;128GB&lt;/td&gt;
&lt;td&gt;128,000,000,000&lt;/td&gt;
&lt;td&gt;119.2 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256GB&lt;/td&gt;
&lt;td&gt;256,000,000,000&lt;/td&gt;
&lt;td&gt;238.4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;512GB&lt;/td&gt;
&lt;td&gt;512,000,000,000&lt;/td&gt;
&lt;td&gt;476.8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1TB&lt;/td&gt;
&lt;td&gt;1,000,000,000,000&lt;/td&gt;
&lt;td&gt;931.3 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2TB&lt;/td&gt;
&lt;td&gt;2,000,000,000,000&lt;/td&gt;
&lt;td&gt;1.82 TB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4TB&lt;/td&gt;
&lt;td&gt;4,000,000,000,000&lt;/td&gt;
&lt;td&gt;3.64 TB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8TB&lt;/td&gt;
&lt;td&gt;8,000,000,000,000&lt;/td&gt;
&lt;td&gt;7.28 TB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Real drives often hold slightly more bytes than the round number, so your figure may be a little higher than this table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other reasons you see less free space
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recovery and system partitions:&lt;/strong&gt; Windows and laptop makers often create hidden partitions of a few hundred megabytes up to several gigabytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File system overhead:&lt;/strong&gt; NTFS, exFAT and other formats need space for their own tables. Our guide to &lt;a href="https://mfaysal.com/blog/ntfs-vs-fat32-vs-exfat" rel="noopener noreferrer"&gt;NTFS vs FAT32 vs exFAT&lt;/a&gt; covers the trade-offs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The operating system:&lt;/strong&gt; a fresh Windows install, updates, the page file and hibernation file can take tens of gigabytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSD over-provisioning:&lt;/strong&gt; some SSDs reserve spare space to manage wear, and the labelled capacity already excludes it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do Mac and phones show the same thing?
&lt;/h2&gt;

&lt;p&gt;No. Since Mac OS X 10.6 Snow Leopard, macOS reports storage in decimal units, so a 1TB drive shows as about 1TB. Windows is the main system that still divides by 1,024 while labelling the result GB. Phones vary by maker, and the operating system itself takes a large share of internal storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check your drive's real size
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open File Explorer, right-click the drive and choose &lt;strong&gt;Properties&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Look at &lt;strong&gt;Capacity&lt;/strong&gt;. Windows shows the exact number of bytes next to the rounded GB figure.&lt;/li&gt;
&lt;li&gt;Divide the bytes by 1,000,000,000 to get the decimal GB figure the maker used.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the byte count is far below the label, for example a "1TB" USB stick showing 32GB worth of bytes, the drive may be fake. Test it before you trust it with data. For other conversions, the &lt;a href="https://mfaysal.com/free-tools/bytes-to-mb-converter/" rel="noopener noreferrer"&gt;bytes to MB converter&lt;/a&gt; gives exact values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why does my 1TB drive show 931GB?
&lt;/h3&gt;

&lt;p&gt;Drive makers count 1TB as one trillion bytes. Windows divides by 1,024 instead of 1,000 and labels the result GB, so the same space appears as about 931GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is GB 1,000 or 1,024 megabytes?
&lt;/h3&gt;

&lt;p&gt;Under the SI standard, 1GB is 1,000MB. The binary unit, 1GiB, is 1,024MiB. Windows uses the binary value but writes GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a GiB?
&lt;/h3&gt;

&lt;p&gt;A gibibyte is 1,073,741,824 bytes (1,024 to the power of 3). It was defined by the IEC to separate binary sizes from decimal ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Am I being cheated by the drive maker?
&lt;/h3&gt;

&lt;p&gt;No. The maker labels capacity in standard decimal units, and the bytes are all there. The difference comes from how Windows displays the number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my 256GB phone show so little free space?
&lt;/h3&gt;

&lt;p&gt;The decimal-to-binary gap is part of it, but the operating system and preinstalled apps take most of the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does macOS show the full capacity?
&lt;/h3&gt;

&lt;p&gt;Yes. macOS has used decimal units since version 10.6, so a 1TB drive shows as roughly 1TB.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last checked: 16 October 2026. Rules and dates can change, so confirm anything important with the official source linked above.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/why-is-my-hard-drive-smaller-than-advertised" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>beginners</category>
      <category>hardware</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Python frozendict: The New Immutable Dictionary in Python 3.15 (Tested)</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Sat, 17 Oct 2026 04:02:06 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/python-frozendict-the-new-immutable-dictionary-in-python-315-tested-22fo</link>
      <guid>https://dev.to/mfaysaldev/python-frozendict-the-new-immutable-dictionary-in-python-315-tested-22fo</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;code&gt;frozendict&lt;/code&gt; is a new built-in type in Python 3.15: a dictionary that can’t be changed after you create it.&lt;/strong&gt; You make one just like a dict, for example &lt;code&gt;frozendict(host="localhost", port=5432)&lt;/code&gt;, and read from it the same way, but any attempt to add, change or delete a key raises an error. Because it can’t change, a &lt;code&gt;frozendict&lt;/code&gt; is &lt;strong&gt;hashable&lt;/strong&gt; (as long as its keys and values are), so you can use it as a dictionary key or put it in a set, which a normal dict can never do.&lt;/p&gt;

&lt;p&gt;Python 3.15.0 was released on 9 October 2026, and &lt;code&gt;frozendict&lt;/code&gt; (PEP 814) is one of its headline features alongside lazy imports. I ran every example below on Python 3.15.0rc3, the final release candidate, installed with &lt;code&gt;uv python install 3.15&lt;/code&gt;, and pasted the real output under each one. The behaviour matches the &lt;a href="https://docs.python.org/3/whatsnew/3.15.html" rel="noopener noreferrer"&gt;What’s New in Python 3.15&lt;/a&gt; documentation for the final release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Python added frozendict
&lt;/h2&gt;

&lt;p&gt;Python has always had an immutable version of &lt;code&gt;list&lt;/code&gt; (&lt;code&gt;tuple&lt;/code&gt;) and of &lt;code&gt;set&lt;/code&gt; (&lt;code&gt;frozenset&lt;/code&gt;), but not of &lt;code&gt;dict&lt;/code&gt;. Developers worked around that with &lt;code&gt;types.MappingProxyType&lt;/code&gt;, with third-party packages, or by converting dicts to sorted tuples of pairs whenever they needed something hashable. None of those options were built in and obvious.&lt;/p&gt;

&lt;p&gt;An immutable dictionary is useful whenever data should not change after it’s created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration&lt;/strong&gt; that’s loaded once and read everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default arguments&lt;/strong&gt; for functions, where a mutable &lt;code&gt;{}&lt;/code&gt; default is a classic bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache keys&lt;/strong&gt; made of several named values, such as &lt;code&gt;(city, unit)&lt;/code&gt; lookups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constants&lt;/strong&gt; shared between modules, where an accidental write would cause a hard-to-find bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thread-safe sharing&lt;/strong&gt;, because nobody can mutate an object that can’t be mutated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to create a frozendict
&lt;/h2&gt;

&lt;p&gt;You don’t import anything; &lt;code&gt;frozendict&lt;/code&gt; is a built-in, just like &lt;code&gt;dict&lt;/code&gt;. It accepts the same kinds of input as &lt;code&gt;dict()&lt;/code&gt;: keyword arguments, an existing mapping, or an iterable of key–value pairs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frozendict({'x': 1, 'y': 2})
frozendict({'host': 'localhost', 'port': 5432})
frozendict({'a': 1, 'b': 2})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;frozendict.fromkeys()&lt;/code&gt; works too, which is handy for building a fixed set of counters or flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromkeys&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frozendict({'a': 0, 'b': 0})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s no literal syntax like &lt;code&gt;{...}&lt;/code&gt; for a frozendict. You always call &lt;code&gt;frozendict(...)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can’t do: frozendict is read-only
&lt;/h2&gt;

&lt;p&gt;Every operation that would change the mapping fails. I tried the usual suspects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;] = 3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;del a[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a.update(z=3)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a.pop(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a.clear()&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a['z'] = 3 -&amp;gt; TypeError: 'frozendict' object does not support item assignment
del a['x'] -&amp;gt; TypeError: 'frozendict' object does not support item deletion
a.update(z=3) -&amp;gt; AttributeError: 'frozendict' object has no attribute 'update'
a.pop('x') -&amp;gt; AttributeError: 'frozendict' object has no attribute 'pop'
a.clear() -&amp;gt; AttributeError: 'frozendict' object has no attribute 'clear'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the two kinds of error. Assigning or deleting with square brackets raises &lt;code&gt;TypeError&lt;/code&gt;. Mutating methods such as &lt;code&gt;update()&lt;/code&gt;, &lt;code&gt;pop()&lt;/code&gt;, &lt;code&gt;clear()&lt;/code&gt; and &lt;code&gt;setdefault()&lt;/code&gt; simply don’t exist, so you get &lt;code&gt;AttributeError&lt;/code&gt;. In fact, the public methods on &lt;code&gt;frozendict&lt;/code&gt; in my 3.15 build were just these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;['copy', 'fromkeys', 'get', 'items', 'keys', 'values']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What you can do: reading works like a dict
&lt;/h2&gt;

&lt;p&gt;All the read operations you already know from dictionaries work the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 missing ['x', 'y'] [('x', 1), ('y', 2)] 2 True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can loop over it, unpack it with &lt;code&gt;**&lt;/code&gt;, pass it to &lt;code&gt;dict()&lt;/code&gt; and use comprehensions on it. If you’re used to looping with &lt;code&gt;.items()&lt;/code&gt;, my &lt;a href="https://mfaysal.com/blog/python-list-comprehension-examples" rel="noopener noreferrer"&gt;Python list comprehension examples&lt;/a&gt; all work on a &lt;code&gt;frozendict&lt;/code&gt; as the source, as long as you’re building a new object rather than editing the old one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why frozendict is hashable (and why that matters)
&lt;/h2&gt;

&lt;p&gt;A value is &lt;strong&gt;hashable&lt;/strong&gt; if Python can compute a fixed number for it with &lt;code&gt;hash()&lt;/code&gt;. Only hashable values can be dictionary keys or set members. A normal dict isn’t hashable because it can change; if it changed after being used as a key, the lookup would break.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;frozendict&lt;/code&gt; can’t change, so Python lets you hash it. The documentation also notes that a frozendict &lt;strong&gt;preserves insertion order, but comparison doesn’t take order into account&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;True True True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So two frozendicts with the same pairs in a different order are equal and hash the same, and a frozendict compares equal to a normal dict with the same contents. The order is still kept for display and iteration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frozendict({'b': 1, 'a': 2}) ['b', 'a']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using a frozendict as a dictionary key
&lt;/h3&gt;

&lt;p&gt;This is the feature that makes it genuinely new. Here’s a tiny cache keyed by a set of named options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Leeds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Leeds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lookup key is written in a different order, but it still finds the entry. Before 3.15, you’d typically write &lt;code&gt;tuple(sorted(options.items()))&lt;/code&gt; to get the same effect, which is harder to read and easy to get wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing duplicate dicts with a set
&lt;/h3&gt;

&lt;p&gt;Because frozendicts can live in a set, you can de-duplicate records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are equal, so the set keeps just one. For de-duplicating lists of simple values, the techniques in my guide to &lt;a href="https://mfaysal.com/blog/python-remove-duplicates-from-list" rel="noopener noreferrer"&gt;removing duplicates from a list in Python&lt;/a&gt; still apply; &lt;code&gt;frozendict&lt;/code&gt; simply extends the same idea to records.&lt;/p&gt;

&lt;h3&gt;
  
  
  The catch: values must be hashable too
&lt;/h3&gt;

&lt;p&gt;A frozendict is only hashable if &lt;strong&gt;all&lt;/strong&gt; its keys and values are. A list inside it breaks hashing, even though the frozendict itself still can’t be modified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;php&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sql&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TypeError:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: unhashable type: 'list'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a tuple (&lt;code&gt;tags=("php", "sql")&lt;/code&gt;) or a &lt;code&gt;frozenset&lt;/code&gt; if you need the whole thing to be hashable.&lt;/p&gt;

&lt;h2&gt;
  
  
  frozendict is not a subclass of dict
&lt;/h2&gt;

&lt;p&gt;This is the gotcha most likely to bite existing code. The documentation says a frozendict “is not a subclass of &lt;code&gt;dict&lt;/code&gt;; it inherits directly from &lt;code&gt;object&lt;/code&gt;.” I checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections.abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__mro__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;False True (&amp;lt;class 'frozendict'&amp;gt;, &amp;lt;class 'object'&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any function that checks &lt;code&gt;isinstance(arg, dict)&lt;/code&gt; will reject a frozendict. The documentation recommends two fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isinstance(arg, (dict, frozendict))&lt;/code&gt; if you want exactly those two types.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isinstance(arg, collections.abc.Mapping)&lt;/code&gt; if you want to accept any read-only mapping, including &lt;code&gt;MappingProxyType&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second option is usually the better design: most functions only need to read keys and values, so they should ask for a &lt;code&gt;Mapping&lt;/code&gt;, not a &lt;code&gt;dict&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating changed copies: | and dict()
&lt;/h2&gt;

&lt;p&gt;Since you can’t edit a frozendict, you create a new one with the changes. The merge operator &lt;code&gt;|&lt;/code&gt; works and returns a frozendict when the left side is one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frozendict({'x': 1, 'y': 2, 'z': 3}) frozendict
dict
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result type follows the left-hand operand: &lt;code&gt;frozendict | dict&lt;/code&gt; gives a frozendict, while &lt;code&gt;dict | frozendict&lt;/code&gt; gives a normal dict. To change a value, build a dict, adjust it and freeze it again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{'x': 1, 'y': 2} frozendict({'x': 10, 'y': 2})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;copy()&lt;/code&gt; is cheap: because the object can never change, &lt;code&gt;a.copy() is a&lt;/code&gt; returned &lt;code&gt;True&lt;/code&gt; in my test, and &lt;code&gt;copy.copy(a) is a&lt;/code&gt; did too. There’s no point duplicating something immutable.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON, pickle and the standard library
&lt;/h2&gt;

&lt;p&gt;According to the 3.15 documentation, these standard library modules were updated to accept &lt;code&gt;frozendict&lt;/code&gt;: &lt;code&gt;copy&lt;/code&gt;, &lt;code&gt;decimal&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;marshal&lt;/code&gt;, &lt;code&gt;plistlib&lt;/code&gt; (serialisation only), &lt;code&gt;pickle&lt;/code&gt;, &lt;code&gt;pprint&lt;/code&gt; and &lt;code&gt;xml.etree.ElementTree&lt;/code&gt;. JSON and pickle round-trips worked in my test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"x": 1, "y": 2} True True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;json.loads()&lt;/code&gt; still returns ordinary dicts when you read JSON back. If you want a frozen config from a JSON file, wrap the result: &lt;code&gt;frozendict(json.load(f))&lt;/code&gt;. For writing JSON nicely formatted, the options in my post on &lt;a href="https://mfaysal.com/blog/python-write-json-to-file-pretty-print" rel="noopener noreferrer"&gt;writing JSON to a file with pretty print&lt;/a&gt; work with frozendicts unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested data: frozendict is shallow
&lt;/h2&gt;

&lt;p&gt;Freezing the outer mapping doesn’t freeze what’s inside. If you want nested config to be read-only, freeze each level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TypeError:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: 'frozendict' object does not support item assignment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;db&lt;/code&gt; had been a normal dict, &lt;code&gt;cfg["db"]["host"] = "x"&lt;/code&gt; would have worked silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  A recursive freeze() helper for JSON config
&lt;/h3&gt;

&lt;p&gt;Real config is usually loaded from JSON and nested several levels deep. This small helper converts every dict into a frozendict and every list into a tuple, so the whole structure becomes read-only and hashable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mfaysal.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: {&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 3306}, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admins&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: [&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mahir&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TypeError:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frozendict({'site': 'mfaysal.com', 'db': frozendict({'host': 'localhost', 'port': 3306}), 'admins': ('mahir',)})
3306 True
TypeError: 'frozendict' object does not support item assignment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load the file once at startup, freeze it, and import &lt;code&gt;CONFIG&lt;/code&gt; wherever you need it. Any code that tries to change a setting at runtime fails loudly instead of quietly affecting the rest of the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safe default argument
&lt;/h2&gt;

&lt;p&gt;A mutable default argument such as &lt;code&gt;def connect(options={})&lt;/code&gt; is one of Python’s best-known traps, because the same dict is shared between every call. A frozendict default is safe because nothing can change it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  frozendict vs MappingProxyType vs dict
&lt;/h2&gt;

&lt;p&gt;Before 3.15, &lt;code&gt;types.MappingProxyType&lt;/code&gt; was the built-in way to get a read-only view. It’s still useful, but it’s different in an important way: it’s a &lt;strong&gt;view&lt;/strong&gt; of an underlying dict, not a frozen copy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;
&lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MappingProxyType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;99
99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy showed the change to &lt;code&gt;inner&lt;/code&gt;, because it only wraps it. The frozendict kept the value it had when it was created (99), even after &lt;code&gt;inner&lt;/code&gt; changed back to 1. A &lt;code&gt;MappingProxyType&lt;/code&gt; also isn’t hashable. Here’s how the three compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dict&lt;/code&gt;:&lt;/strong&gt; mutable, not hashable, available in every version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;types.MappingProxyType&lt;/code&gt;:&lt;/strong&gt; read-only &lt;em&gt;view&lt;/em&gt;; changes to the underlying dict show through; not hashable; available since Python 3.3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;frozendict&lt;/code&gt;:&lt;/strong&gt; a frozen copy; nothing can change it; hashable when its contents are; new in Python 3.15.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Supporting older Python versions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;frozendict&lt;/code&gt; doesn’t exist before 3.15, so code that must also run on 3.14 or earlier needs a fallback. A simple approach is to fall back to a read-only proxy, accepting that you lose hashability on older versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;frozendict&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;NameError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;frozendict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MappingProxyType&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_info&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On 3.15 the &lt;code&gt;try&lt;/code&gt; succeeds and you get the real built-in. On older versions you get a read-only wrapper. Don’t rely on hashing in that case, and remember that &lt;code&gt;MappingProxyType&lt;/code&gt; needs a dict argument rather than keyword arguments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you switch your dicts to frozendict?
&lt;/h2&gt;

&lt;p&gt;Not everywhere. Normal dicts are still the right choice for data you build up and change. Reach for &lt;code&gt;frozendict&lt;/code&gt; when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A value is meant to be constant, and an accidental write would be a bug.&lt;/li&gt;
&lt;li&gt;You need a mapping as a dict key or set member.&lt;/li&gt;
&lt;li&gt;You want a safe default argument or a shared object between threads.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep in mind the deployment side too: Python 3.15 is brand new, so check that your hosting platform and libraries support it before you rely on new built-ins in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is frozendict in Python?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;frozendict&lt;/code&gt; is a built-in immutable dictionary type added in Python 3.15 (PEP 814). It behaves like a read-only dict: you can read keys and values, but you can’t add, change or delete entries after creating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Python version has frozendict?
&lt;/h3&gt;

&lt;p&gt;Python 3.15 and later. It was released on 9 October 2026. On Python 3.14 and earlier, the name &lt;code&gt;frozendict&lt;/code&gt; doesn’t exist unless you install a third-party package or define a fallback.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is frozendict hashable?
&lt;/h3&gt;

&lt;p&gt;Yes, as long as all its keys and values are hashable. That means you can use it as a dictionary key or put it in a set. A frozendict containing a list raises &lt;code&gt;TypeError: unhashable type: 'list'&lt;/code&gt; when hashed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is frozendict a subclass of dict?
&lt;/h3&gt;

&lt;p&gt;No. It inherits directly from &lt;code&gt;object&lt;/code&gt;, so &lt;code&gt;isinstance(x, dict)&lt;/code&gt; returns &lt;code&gt;False&lt;/code&gt;. Check for &lt;code&gt;collections.abc.Mapping&lt;/code&gt; or &lt;code&gt;(dict, frozendict)&lt;/code&gt; instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I add a key to a frozendict?
&lt;/h3&gt;

&lt;p&gt;You can’t change it in place, but you can create a new one with the merge operator: &lt;code&gt;new = old | {"key": "value"}&lt;/code&gt;. When the left side is a frozendict, the result is also a frozendict.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between frozendict and MappingProxyType?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;MappingProxyType&lt;/code&gt; is a read-only view of an existing dict, so changes to that dict show through, and it isn’t hashable. A &lt;code&gt;frozendict&lt;/code&gt; is an independent frozen copy that never changes and is hashable when its contents are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does frozendict keep insertion order?
&lt;/h3&gt;

&lt;p&gt;Yes. It preserves insertion order for display and iteration, but equality ignores order, so &lt;code&gt;frozendict(x=1, y=2) == frozendict(y=2, x=1)&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/python-frozendict" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Python enumerate(): How to Start at 1 (and 12 Tested Examples)</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Fri, 16 Oct 2026 03:59:20 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/python-enumerate-how-to-start-at-1-and-12-tested-examples-54h3</link>
      <guid>https://dev.to/mfaysaldev/python-enumerate-how-to-start-at-1-and-12-tested-examples-54h3</guid>
      <description>&lt;p&gt;To loop over a list in Python and get a counter that starts at 1, write &lt;code&gt;for n, item in enumerate(items, start=1):&lt;/code&gt;. &lt;code&gt;enumerate()&lt;/code&gt; returns pairs of &lt;code&gt;(count, item)&lt;/code&gt;, and the optional &lt;code&gt;start&lt;/code&gt; argument sets the first number; it defaults to 0. You can also pass it positionally, as &lt;code&gt;enumerate(items, 1)&lt;/code&gt;. Changing &lt;code&gt;start&lt;/code&gt; only changes the number you see, not which item comes first, so if you use that number to index the list, subtract one.&lt;/p&gt;

&lt;p&gt;I ran every example in this post on &lt;strong&gt;Python 3.13.5&lt;/strong&gt; on 11 October 2026 and pasted the real output under each one. If you’re working through Python basics, this pairs well with my &lt;a href="https://mfaysal.com/blog/python-list-comprehension-examples" rel="noopener noreferrer"&gt;Python list comprehension examples&lt;/a&gt;, because &lt;code&gt;enumerate()&lt;/code&gt; and comprehensions turn up together all the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What enumerate() does
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.python.org/3/library/functions.html#enumerate" rel="noopener noreferrer"&gt;Python documentation for enumerate()&lt;/a&gt; gives the signature as &lt;code&gt;enumerate(iterable, start=0)&lt;/code&gt;. It returns an &lt;strong&gt;enumerate object&lt;/strong&gt;, an iterator that yields tuples containing a count (starting from &lt;code&gt;start&lt;/code&gt;) and the values from the iterable.&lt;/p&gt;

&lt;p&gt;Here’s the most basic use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fruits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cherry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 apple
1 banana
2 cherry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;i, fruit&lt;/code&gt; part is &lt;strong&gt;tuple unpacking&lt;/strong&gt;. Each loop gets a tuple like &lt;code&gt;(0, 'apple')&lt;/code&gt;, and Python splits it into two variables. You can name them anything; &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;index&lt;/code&gt; are common.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to make enumerate start at 1
&lt;/h2&gt;

&lt;p&gt;Pass &lt;code&gt;start=1&lt;/code&gt;. This is perfect for numbered lists that humans will read, where “item 0” would look odd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fruit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. apple
2. banana
3. cherry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The f-string here builds each line; my &lt;a href="https://mfaysal.com/blog/python-f-string-formatting-examples" rel="noopener noreferrer"&gt;Python f-string formatting examples&lt;/a&gt; cover padding and alignment if you want the numbers to line up in columns.&lt;/p&gt;

&lt;p&gt;Two things to keep straight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt; changes the &lt;strong&gt;counter&lt;/strong&gt;, not the position. The first item is still &lt;code&gt;fruits[0]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you use the counter to index or change the list, use &lt;code&gt;fruits[n - 1]&lt;/code&gt;, or simply keep the default &lt;code&gt;start=0&lt;/code&gt; for that loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What enumerate() actually returns
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;enumerate()&lt;/code&gt; doesn’t build a list. It returns a lazy iterator that produces one pair at a time, which is why it’s memory-friendly on large data. You can step through it by hand with &lt;code&gt;next()&lt;/code&gt; or turn the rest into a list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enumerate (0, 'apple') [(1, 'banana'), (2, 'cherry')]
[(1, 'a'), (2, 'b'), (3, 'c')]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details from that output. Once &lt;code&gt;next(e)&lt;/code&gt; has taken the first pair, &lt;code&gt;list(e)&lt;/code&gt; only gets what’s left: iterators are used up as you go. And &lt;code&gt;enumerate()&lt;/code&gt; works on any iterable, including a string, where each “item” is a character.&lt;/p&gt;

&lt;h2&gt;
  
  
  How enumerate() works under the hood
&lt;/h2&gt;

&lt;p&gt;There’s no magic inside &lt;code&gt;enumerate()&lt;/code&gt;. The Python documentation shows that it’s equivalent to this small generator function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading it line by line explains every behaviour in this post. The counter begins at whatever you pass as &lt;code&gt;start&lt;/code&gt;. Each step hands back a tuple of the current number and the next element, then adds one. Because it uses &lt;code&gt;yield&lt;/code&gt;, nothing is calculated until the loop asks for the next pair, which is why &lt;code&gt;enumerate()&lt;/code&gt; works on huge files and endless generators without loading them into memory. And because it simply loops over &lt;code&gt;iterable&lt;/code&gt;, it accepts anything a &lt;code&gt;for&lt;/code&gt; loop accepts.&lt;/p&gt;

&lt;p&gt;You’d never redefine the built-in like this in real code, since it would hide the faster C version, but writing it out once is a good way to understand generators.&lt;/p&gt;

&lt;h2&gt;
  
  
  enumerate() vs range(len())
&lt;/h2&gt;

&lt;p&gt;Many beginners, me included, first write index loops like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 apple
1 banana
2 cherry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, but &lt;code&gt;enumerate()&lt;/code&gt; is usually better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gives you the item directly&lt;/strong&gt;: &lt;code&gt;range(len(items))&lt;/code&gt;: No, you write &lt;code&gt;items[i]&lt;/code&gt;; &lt;code&gt;enumerate(items)&lt;/code&gt;: Yes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works with any iterable (files, generators, sets)&lt;/strong&gt;: &lt;code&gt;range(len(items))&lt;/code&gt;: No, needs &lt;code&gt;len()&lt;/code&gt; and indexing; &lt;code&gt;enumerate(items)&lt;/code&gt;: Yes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to start counting at 1&lt;/strong&gt;: &lt;code&gt;range(len(items))&lt;/code&gt;: &lt;code&gt;range(1, len(items) + 1)&lt;/code&gt; and then &lt;code&gt;items[i - 1]&lt;/code&gt;; &lt;code&gt;enumerate(items)&lt;/code&gt;: &lt;code&gt;start=1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability&lt;/strong&gt;: &lt;code&gt;range(len(items))&lt;/code&gt;: More noise; &lt;code&gt;enumerate(items)&lt;/code&gt;: Says what you mean.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;range(len())&lt;/code&gt; still makes sense when you genuinely only need the numbers, or when you’re comparing an item with its neighbour (&lt;code&gt;items[i]&lt;/code&gt; and &lt;code&gt;items[i + 1]&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing list items in place with enumerate
&lt;/h2&gt;

&lt;p&gt;You can’t change a list by assigning to the loop variable, because that only rebinds the name. You need the index, and &lt;code&gt;enumerate()&lt;/code&gt; gives you both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;12.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[12.0, 15.0, 9.6]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds 20% to each price. Notice I kept the default &lt;code&gt;start=0&lt;/code&gt; here because I’m using &lt;code&gt;i&lt;/code&gt; as a real index.&lt;/p&gt;

&lt;h2&gt;
  
  
  enumerate() with a dictionary
&lt;/h2&gt;

&lt;p&gt;Looping over a dict gives you its keys, so to get keys, values and a counter, call &lt;code&gt;.items()&lt;/code&gt; and unpack the inner tuple with brackets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ayesha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ben&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 Ayesha 91
2 Ben 73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dictionaries keep insertion order (guaranteed since Python 3.7), so the ranks follow the order you added the keys. To rank by score instead, sort the items first. With Chloe added on 88:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ayesha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ben&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chloe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 Ayesha 91
2 Chloe 88
3 Ben 73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  enumerate() with zip(): index plus two lists
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;zip()&lt;/code&gt; walks two lists side by side. Wrap it in &lt;code&gt;enumerate()&lt;/code&gt; to number the pairs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ayesha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ben&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chloe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;marks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;marks&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 Ayesha 91
2 Ben 73
3 Chloe 88
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order matters: &lt;code&gt;enumerate(zip(...))&lt;/code&gt; gives &lt;code&gt;(i, (name, mark))&lt;/code&gt;, so you need the brackets around &lt;code&gt;(name, mark)&lt;/code&gt; when unpacking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looping backwards with reversed and enumerate
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;reversed()&lt;/code&gt; needs a sequence it can walk backwards, and an enumerate object isn’t one. Calling &lt;code&gt;reversed(enumerate(fruits))&lt;/code&gt; directly raised &lt;code&gt;TypeError: 'enumerate' object is not reversible&lt;/code&gt; in my test. Convert it to a list first, which keeps the original numbers attached to each item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 cherry
2 banana
1 apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want the count to go 1, 2, 3 while the items go backwards, do it the other way round:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 cherry
2 banana
3 apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This version works without building a list, because &lt;code&gt;reversed()&lt;/code&gt; runs on the original list, and &lt;code&gt;enumerate()&lt;/code&gt; happily wraps any iterator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Line numbers when reading a file
&lt;/h2&gt;

&lt;p&gt;Files are iterables of lines, so &lt;code&gt;enumerate(f, start=1)&lt;/code&gt; gives you human-friendly line numbers for error messages. I’ve used an in-memory file here so the example runs anywhere, but a real &lt;code&gt;open("scores.csv")&lt;/code&gt; works the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name,score&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Ayesha,91&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Ben,seventy&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line_no&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line_no&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;  &lt;span class="c1"&gt;# skip the header
&lt;/span&gt;    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;line_no&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: bad score &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;line 3: bad score 'seventy'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reporting “line 3” rather than “row 1 after the header” makes it easy to find the problem in a text editor. For proper CSV parsing with quoted fields, use the &lt;code&gt;csv&lt;/code&gt; module, as in my guide to &lt;a href="https://mfaysal.com/blog/python-read-csv-without-pandas" rel="noopener noreferrer"&gt;reading a CSV in Python without pandas&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  enumerate() in list and dict comprehensions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;enumerate()&lt;/code&gt; fits neatly into comprehensions. Two patterns I use a lot: building a lookup from item to position, and finding the positions of items that match a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;fruit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{'apple': 0, 'banana': 1, 'cherry': 2}
[1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one returns &lt;code&gt;[1]&lt;/code&gt; because only “banana” contains “an”. It finds &lt;strong&gt;every&lt;/strong&gt; match, unlike &lt;code&gt;list.index()&lt;/code&gt;, which stops at the first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every second item, and other counter tricks
&lt;/h2&gt;

&lt;p&gt;Because you have the counter, you can act on every nth item with the modulo operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 apple
2 cherry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For simply taking every second item, slicing (&lt;code&gt;fruits[::2]&lt;/code&gt;) is shorter. Use &lt;code&gt;enumerate()&lt;/code&gt; when you also need the position, for example to print “row 3 of 10” or to add a separator before every item except the first (&lt;code&gt;if i &amp;gt; 0:&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbering results on page 2 and beyond
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;start&lt;/code&gt; is great for paginated lists. If page 2 shows items 11 to 20, you want the numbers to carry on from page 1 rather than restart at 1. Work out the offset once and pass &lt;code&gt;offset + 1&lt;/code&gt; as &lt;code&gt;start&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;per_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;per_page&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;per_page&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11 item-11
12 item-12
...
20 item-20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve trimmed the middle lines of the output; the full run printed 11 to 20 in order. The same offset is what you’d pass to &lt;code&gt;LIMIT ... OFFSET ...&lt;/code&gt; in SQL when fetching that page from a database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested loops: rows and columns
&lt;/h2&gt;

&lt;p&gt;For a grid (a list of lists), use one &lt;code&gt;enumerate()&lt;/code&gt; per level to get row and column numbers. Here I look for every &lt;code&gt;O&lt;/code&gt; on a small noughts-and-crosses board:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;O&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;O&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;O&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;O&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;O at row &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, col &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O at row 0, col 1
O at row 1, col 0
O at row 1, col 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the default &lt;code&gt;start=0&lt;/code&gt; here, so the numbers match &lt;code&gt;grid[r][c]&lt;/code&gt; exactly. That makes it easy to update a cell later with &lt;code&gt;grid[r][c] = "X"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Negative and non-integer start values
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;start&lt;/code&gt; can be any integer, including a negative one. It can’t be a float:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TypeError:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[(-1, 'apple'), (0, 'banana'), (1, 'cherry')]
TypeError: 'float' object cannot be interpreted as an integer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Negative starts are rare, but they’re handy when your numbering needs an offset, such as counting relative to a reference row.&lt;/p&gt;

&lt;h2&gt;
  
  
  The list.index() trap
&lt;/h2&gt;

&lt;p&gt;A common beginner bug is to call &lt;code&gt;.index()&lt;/code&gt; inside a loop to find the current position. It returns the position of the &lt;strong&gt;first&lt;/strong&gt; matching value, so it goes wrong as soon as the list has duplicates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dupes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;dupes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dupes&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dupes&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0, 1, 0] [0, 1, 2]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third item is at position 2, but &lt;code&gt;.index()&lt;/code&gt; says 0. It’s also slow on big lists, because it searches from the beginning every time. &lt;code&gt;enumerate()&lt;/code&gt; gives the correct position for free. The &lt;code&gt;_&lt;/code&gt; is a convention for “I don’t need this value”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common enumerate() mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Swapping the order:&lt;/strong&gt; it’s &lt;code&gt;for i, item in enumerate(items)&lt;/code&gt;, count first. &lt;code&gt;for item, i in ...&lt;/code&gt; runs without error but gives you confusing names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting to unpack:&lt;/strong&gt; &lt;code&gt;for pair in enumerate(items)&lt;/code&gt; gives you tuples; use &lt;code&gt;pair[0]&lt;/code&gt; and &lt;code&gt;pair[1]&lt;/code&gt; or unpack straight away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using the start=1 counter as an index:&lt;/strong&gt; &lt;code&gt;items[n]&lt;/code&gt; with &lt;code&gt;start=1&lt;/code&gt; skips the first item and raises &lt;code&gt;IndexError&lt;/code&gt; on the last.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusing an exhausted enumerate object:&lt;/strong&gt; like any iterator, it’s empty after one full pass. Call &lt;code&gt;enumerate()&lt;/code&gt; again for a second loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changing the list while enumerating it:&lt;/strong&gt; appending to or removing from a list inside its own &lt;code&gt;for&lt;/code&gt; loop shifts the positions under you, so items get skipped or processed twice. Loop over a copy (&lt;code&gt;for i, x in enumerate(items[:]):&lt;/code&gt;) or build a new list instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Needing a running total, not a counter:&lt;/strong&gt; &lt;code&gt;enumerate()&lt;/code&gt; only counts items. For sums, use a separate variable or &lt;code&gt;itertools.accumulate()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I make Python enumerate start at 1?
&lt;/h3&gt;

&lt;p&gt;Pass the &lt;code&gt;start&lt;/code&gt; argument: &lt;code&gt;for n, item in enumerate(items, start=1):&lt;/code&gt;. You can also write &lt;code&gt;enumerate(items, 1)&lt;/code&gt;. The first item is still &lt;code&gt;items[0]&lt;/code&gt;; only the counter changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does enumerate() return in Python?
&lt;/h3&gt;

&lt;p&gt;It returns an enumerate object, a lazy iterator that yields &lt;code&gt;(count, item)&lt;/code&gt; tuples. Wrap it in &lt;code&gt;list()&lt;/code&gt; if you want to see all the pairs at once, for example &lt;code&gt;list(enumerate(["a", "b"]))&lt;/code&gt; gives &lt;code&gt;[(0, 'a'), (1, 'b')]&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is enumerate faster than range(len())?
&lt;/h3&gt;

&lt;p&gt;For everyday loops the speed difference is small, but &lt;code&gt;enumerate()&lt;/code&gt; is cleaner, works with any iterable (including files and generators) and avoids indexing mistakes. Choose it for readability first.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I use enumerate with a dictionary?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;.items()&lt;/code&gt; and unpack the inner pair: &lt;code&gt;for i, (key, value) in enumerate(my_dict.items(), start=1):&lt;/code&gt;. Looping over the dict directly gives you only the keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can enumerate start at a negative number?
&lt;/h3&gt;

&lt;p&gt;Yes. Any integer works, so &lt;code&gt;enumerate(items, start=-1)&lt;/code&gt; numbers the items -1, 0, 1 and so on. A float such as &lt;code&gt;1.5&lt;/code&gt; raises a &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I enumerate in reverse in Python?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;reversed(list(enumerate(items)))&lt;/code&gt; to keep each item’s original number while looping backwards, or &lt;code&gt;enumerate(reversed(items), start=1)&lt;/code&gt; to count upwards over the reversed items.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does enumerate work with strings and files?
&lt;/h3&gt;

&lt;p&gt;Yes. &lt;code&gt;enumerate()&lt;/code&gt; accepts any iterable. A string yields one character at a time, and an open file yields one line at a time, which makes &lt;code&gt;enumerate(f, start=1)&lt;/code&gt; a neat way to get line numbers.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/python-enumerate-start-at-1" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>MySQL GROUP_CONCAT with ORDER BY, SEPARATOR and DISTINCT (Tested on 8.4)</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Thu, 15 Oct 2026 04:07:24 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/mysql-groupconcat-with-order-by-separator-and-distinct-tested-on-84-5dke</link>
      <guid>https://dev.to/mfaysaldev/mysql-groupconcat-with-order-by-separator-and-distinct-tested-on-84-5dke</guid>
      <description>&lt;p&gt;To join several rows into one string per group in MySQL, use &lt;code&gt;GROUP_CONCAT()&lt;/code&gt; with &lt;code&gt;GROUP BY&lt;/code&gt;. The order and the separator go &lt;strong&gt;inside&lt;/strong&gt; the brackets: &lt;code&gt;GROUP_CONCAT(subject ORDER BY subject SEPARATOR ', ')&lt;/code&gt;. Add &lt;code&gt;DISTINCT&lt;/code&gt; straight after the opening bracket to drop duplicates. Two things catch people out: NULL values are skipped silently, and the result is cut off at &lt;code&gt;group_concat_max_len&lt;/code&gt;, which is 1,024 bytes by default, with only a warning to tell you.&lt;/p&gt;

&lt;p&gt;I ran every query in this post on &lt;strong&gt;MySQL 8.4.6&lt;/strong&gt; on 11 October 2026, with the default &lt;code&gt;sql_mode&lt;/code&gt; (which includes &lt;code&gt;ONLY_FULL_GROUP_BY&lt;/code&gt;), and pasted the real output under each one. Everything below is copy-and-paste ready: create the two tables, insert the rows and run each query in order. If you run your queries from PHP, my guide to &lt;a href="https://mfaysal.com/blog/php-pdo-fetch-assoc-fetchall" rel="noopener noreferrer"&gt;PDO fetch() and fetchAll()&lt;/a&gt; shows how to read the results back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sample tables
&lt;/h2&gt;

&lt;p&gt;The examples use two small tables: students and the subjects they’ve taken, with an optional score. I’ve deliberately included a duplicate (Chloe took Biology twice), a NULL score and a row with a NULL subject, because that’s where &lt;code&gt;GROUP_CONCAT&lt;/code&gt; behaves in surprising ways.&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Ayesha'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'10A'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Ben'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'10A'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Chloe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'10B'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Dev'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'10B'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Maths'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Physics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'English'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'History'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Maths'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Biology'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Maths'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Biology'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic GROUP_CONCAT: one row per group
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GROUP_CONCAT()&lt;/code&gt; is an aggregate function, like &lt;code&gt;COUNT()&lt;/code&gt; or &lt;code&gt;SUM()&lt;/code&gt;. The &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/aggregate-functions.html" rel="noopener noreferrer"&gt;MySQL aggregate function reference&lt;/a&gt; describes it simply as “return a concatenated string”. You group the rows, and it glues each group’s values together.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------+-----------------------+
| name   | subjects              |
+--------+-----------------------+
| Ayesha | Maths,Physics,English |
| Ben    | History,Maths         |
| Chloe  | Biology,Maths,Biology |
| Dev    | NULL                  |
+--------+-----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice three things. The default separator is a comma with &lt;strong&gt;no space&lt;/strong&gt;. The order inside each list isn’t guaranteed; here it happens to follow insertion order, but you shouldn’t rely on that. And Dev’s only subject is NULL, so his whole result is NULL.&lt;/p&gt;

&lt;h2&gt;
  
  
  GROUP_CONCAT with ORDER BY and SEPARATOR
&lt;/h2&gt;

&lt;p&gt;To control the order of values inside each string, put &lt;code&gt;ORDER BY&lt;/code&gt; &lt;strong&gt;inside&lt;/strong&gt; the function call. To change the separator, add &lt;code&gt;SEPARATOR&lt;/code&gt; after it. Both are part of &lt;code&gt;GROUP_CONCAT&lt;/code&gt;’s own syntax, separate from the query’s outer &lt;code&gt;ORDER BY&lt;/code&gt;.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------+-------------------------+
| name   | subjects                |
+--------+-------------------------+
| Ayesha | English, Maths, Physics |
| Ben    | History, Maths          |
| Chloe  | Biology, Biology, Maths |
| Dev    | NULL                    |
+--------+-------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The outer &lt;code&gt;ORDER BY s.id&lt;/code&gt; sorts the &lt;strong&gt;rows&lt;/strong&gt; of the result. The inner &lt;code&gt;ORDER BY sub.subject&lt;/code&gt; sorts the &lt;strong&gt;values within each string&lt;/strong&gt;. You’ll often want both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ordering by a different column
&lt;/h3&gt;

&lt;p&gt;The inner &lt;code&gt;ORDER BY&lt;/code&gt; doesn’t have to use the column you’re concatenating. Here I list each student’s subjects best score first, with the score in brackets and a pipe as the separator:&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' ('&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;' | '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;best_first&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------+------------------------------------------+
| name   | best_first                               |
+--------+------------------------------------------+
| Ayesha | Maths (91) | Physics (84) | English (77) |
| Ben    | Maths (73) | History (68)                |
| Chloe  | Biology (90) | Biology (88)              |
| Dev    | NULL                                     |
+--------+------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look closely at Chloe. Her Maths entry has disappeared. Its score is NULL, &lt;code&gt;CONCAT()&lt;/code&gt; returns NULL if any argument is NULL, and &lt;code&gt;GROUP_CONCAT&lt;/code&gt; skips NULL values. That leads us to the biggest gotcha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why NULL values disappear from GROUP_CONCAT
&lt;/h2&gt;

&lt;p&gt;The MySQL manual states that, unless otherwise stated, aggregate functions ignore NULL values, and &lt;code&gt;GROUP_CONCAT&lt;/code&gt; follows that rule. If every value in a group is NULL, the result is NULL rather than an empty string.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rows_in_group&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------+----------+---------------+
| name | subjects | rows_in_group |
+------+----------+---------------+
| Dev  | NULL     |             1 |
+------+----------+---------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s one row in the group, but its subject is NULL, so the concatenated result is NULL. Two fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default for the whole result:&lt;/strong&gt; wrap it in &lt;code&gt;COALESCE(GROUP_CONCAT(sub.subject), 'none')&lt;/code&gt;, which returned &lt;code&gt;none&lt;/code&gt; for Dev in my test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep NULL items visible:&lt;/strong&gt; use &lt;code&gt;COALESCE()&lt;/code&gt; or &lt;code&gt;IFNULL()&lt;/code&gt; on the value inside, for example &lt;code&gt;GROUP_CONCAT(CONCAT(sub.subject, ' (', COALESCE(sub.score, '-'), ')'))&lt;/code&gt;, so Chloe’s Maths stays in the list instead of vanishing. In my test that returned &lt;code&gt;Biology (90) | Biology (88) | Maths (-)&lt;/code&gt;. Note that NULL scores sort last with &lt;code&gt;DESC&lt;/code&gt;, which is usually what you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GROUP_CONCAT DISTINCT: removing duplicates
&lt;/h2&gt;

&lt;p&gt;Put &lt;code&gt;DISTINCT&lt;/code&gt; first inside the brackets to keep only unique values. You can combine it with &lt;code&gt;ORDER BY&lt;/code&gt; and &lt;code&gt;SEPARATOR&lt;/code&gt;:&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------+----------------+
| name  | subjects       |
+-------+----------------+
| Chloe | Biology, Maths |
+-------+----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Biology now appears once. If duplicate rows are a symptom of bad data rather than a legitimate repeat, fix the table instead; my post on &lt;a href="https://mfaysal.com/blog/mysql-delete-duplicate-rows" rel="noopener noreferrer"&gt;deleting duplicate rows in MySQL&lt;/a&gt; covers safe ways to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom separators: no separator, new lines and more
&lt;/h2&gt;

&lt;p&gt;Any string literal works as a separator, including an empty one and escape sequences:&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;SELECT&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;squashed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;one_per_line&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------+-----------+--------------+
| class | squashed  | one_per_line |
+-------+-----------+--------------+
| 10A   | AyeshaBen | Ayesha
Ben   |
| 10B   | ChloeDev  | Chloe
Dev    |
+-------+-----------+--------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new-line version breaks the text table, which is exactly what you want when you’re building a plain-text email or a CSV cell. Other separators I find handy are &lt;code&gt;' | '&lt;/code&gt; for readable reports and &lt;code&gt;';'&lt;/code&gt; for exporting to spreadsheets that treat commas as column breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 1024 limit: group_concat_max_len and truncated results
&lt;/h2&gt;

&lt;p&gt;This one causes real bugs in production. &lt;code&gt;GROUP_CONCAT&lt;/code&gt; results are capped by the &lt;code&gt;group_concat_max_len&lt;/code&gt; system variable. On my MySQL 8.4.6 server, &lt;code&gt;SELECT @@group_concat_max_len;&lt;/code&gt; returned &lt;strong&gt;1024&lt;/strong&gt;. When a result is longer, MySQL cuts it off and raises a warning, but the query still succeeds, so your application never notices.&lt;/p&gt;

&lt;p&gt;To demonstrate, I set a tiny limit of 20 bytes:&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;SET&lt;/span&gt; &lt;span class="k"&gt;SESSION&lt;/span&gt; &lt;span class="n"&gt;group_concat_max_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&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;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;names&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;WARNINGS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
| names                |
+----------------------+
| Ayesha, Ben, Chloe,  |
+----------------------+
+---------+------+---------------------------------+
| Level   | Code | Message                         |
+---------+------+---------------------------------+
| Warning | 1260 | Row 4 was cut by GROUP_CONCAT() |
+---------+------+---------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dev was silently dropped, and the string even ends with a stray separator. Raising the limit for the session fixes it:&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;SET&lt;/span&gt; &lt;span class="k"&gt;SESSION&lt;/span&gt; &lt;span class="n"&gt;group_concat_max_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000000&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;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;names&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------+
| names                   |
+-------------------------+
| Ayesha, Ben, Chloe, Dev |
+-------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Practical advice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;SET SESSION group_concat_max_len = ...&lt;/code&gt; right after connecting, in the same connection that runs the query. It only lasts for that session.&lt;/li&gt;
&lt;li&gt;If you can’t predict the size, check warnings (warning code 1260) or compare the result length with the limit.&lt;/li&gt;
&lt;li&gt;If the list could be huge, ask whether you need one string at all. Fetching the rows and joining them in PHP or Python is often simpler and has no cut-off.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Top N per group with GROUP_CONCAT
&lt;/h2&gt;

&lt;p&gt;MySQL’s &lt;code&gt;GROUP_CONCAT&lt;/code&gt; has no &lt;code&gt;LIMIT&lt;/code&gt; clause inside the brackets (MariaDB added one, MySQL hasn’t), so a common trick is to order the list and then keep the first N items with &lt;code&gt;SUBSTRING_INDEX()&lt;/code&gt;:&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;SUBSTRING_INDEX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;top2&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------+---------------+
| name   | top2          |
+--------+---------------+
| Ayesha | Maths,Physics |
+--------+---------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SUBSTRING_INDEX(str, ',', 2)&lt;/code&gt; returns everything before the second comma. It breaks if the values themselves contain your separator, so pick one that can’t appear in the data. For anything more complex, a window function such as &lt;code&gt;ROW_NUMBER()&lt;/code&gt; in a subquery is more robust.&lt;/p&gt;

&lt;h2&gt;
  
  
  GROUP_CONCAT vs JSON_ARRAYAGG
&lt;/h2&gt;

&lt;p&gt;If the result is going to code rather than a human, consider &lt;code&gt;JSON_ARRAYAGG()&lt;/code&gt;, which the MySQL manual lists as returning the result set “as a single JSON array”. You get proper quoting for free, so commas inside values can’t break your parsing:&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JSON_ARRAYAGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;subjects_json&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------+---------------------------------+
| name   | subjects_json                   |
+--------+---------------------------------+
| Ayesha | ["Maths", "Physics", "English"] |
| Ben    | ["History", "Maths"]            |
+--------+---------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In PHP you can &lt;code&gt;json_decode()&lt;/code&gt; that column straight into an array. The trade-off: &lt;code&gt;JSON_ARRAYAGG&lt;/code&gt; doesn’t accept an &lt;code&gt;ORDER BY&lt;/code&gt; inside the brackets. When I tried &lt;code&gt;JSON_ARRAYAGG(name ORDER BY name)&lt;/code&gt; on MySQL 8.4.6, it failed with syntax error 1064. Use &lt;code&gt;GROUP_CONCAT&lt;/code&gt; when the order of items matters, or sort the array in your application code after decoding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error 1055 and ONLY_FULL_GROUP_BY
&lt;/h2&gt;

&lt;p&gt;Because MySQL 8.4 enables &lt;code&gt;ONLY_FULL_GROUP_BY&lt;/code&gt; by default, every non-aggregated column in your &lt;code&gt;SELECT&lt;/code&gt; must be in the &lt;code&gt;GROUP BY&lt;/code&gt; or depend on it. Grouping by the primary key &lt;code&gt;s.id&lt;/code&gt; while selecting &lt;code&gt;s.name&lt;/code&gt; is fine, since the name depends on the id. But this query fails:&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subjects&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains
nonaggregated column 'gc_demo.s.name' which is not functionally dependent on columns in
GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each class has several names, so MySQL can’t pick one. Either add &lt;code&gt;s.name&lt;/code&gt; to the &lt;code&gt;GROUP BY&lt;/code&gt; or aggregate it too, for example &lt;code&gt;GROUP_CONCAT(DISTINCT s.name)&lt;/code&gt;. Don’t switch the SQL mode off to make the error go away; it’s protecting you from random results.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to use GROUP_CONCAT
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GROUP_CONCAT&lt;/code&gt; is brilliant for display: a tag list next to a blog post, a comma-separated list of products in an order summary, or a quick report you paste into a spreadsheet. It’s a poor fit when you need to filter, count or join on the individual values later. Searching inside a concatenated string with &lt;code&gt;LIKE '%Maths%'&lt;/code&gt; is slow and matches partial words, and storing comma-separated lists in a column breaks the basic rule of one value per field. Keep the data in proper rows and only concatenate at the very end, in the query that feeds the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using GROUP_CONCAT safely from PHP
&lt;/h2&gt;

&lt;p&gt;If any part of the query comes from user input, such as a class name from a dropdown, bind it with a prepared statement rather than gluing it into the SQL string. My guide to &lt;a href="https://mfaysal.com/blog/php-pdo-prepared-statements" rel="noopener noreferrer"&gt;PHP PDO prepared statements&lt;/a&gt; shows the pattern. &lt;code&gt;GROUP_CONCAT&lt;/code&gt; itself is just another column in the result; you read it with &lt;code&gt;fetch()&lt;/code&gt; like any other value and &lt;code&gt;explode(', ', $row['subjects'])&lt;/code&gt; if you need an array back.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I use ORDER BY inside GROUP_CONCAT?
&lt;/h3&gt;

&lt;p&gt;Put it inside the brackets, after the column: &lt;code&gt;GROUP_CONCAT(subject ORDER BY subject ASC)&lt;/code&gt;. It sorts the values within each concatenated string. The query’s outer &lt;code&gt;ORDER BY&lt;/code&gt; sorts the result rows separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I change the GROUP_CONCAT separator?
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;SEPARATOR&lt;/code&gt; as the last part inside the brackets: &lt;code&gt;GROUP_CONCAT(subject SEPARATOR ', ')&lt;/code&gt;. The default is a comma with no space. Use &lt;code&gt;SEPARATOR ''&lt;/code&gt; for no separator or &lt;code&gt;SEPARATOR '\n'&lt;/code&gt; for one value per line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is my GROUP_CONCAT result cut off?
&lt;/h3&gt;

&lt;p&gt;It has hit &lt;code&gt;group_concat_max_len&lt;/code&gt;, which was 1,024 bytes by default on my MySQL 8.4 server. MySQL truncates the string and raises warning 1260. Run &lt;code&gt;SET SESSION group_concat_max_len = 1000000;&lt;/code&gt; before the query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does GROUP_CONCAT return NULL?
&lt;/h3&gt;

&lt;p&gt;Every value in that group was NULL, and aggregate functions ignore NULLs. Wrap the call in &lt;code&gt;COALESCE(GROUP_CONCAT(col), '')&lt;/code&gt; to get an empty string or a default label instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I remove duplicates in GROUP_CONCAT?
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;DISTINCT&lt;/code&gt; straight after the opening bracket: &lt;code&gt;GROUP_CONCAT(DISTINCT subject ORDER BY subject SEPARATOR ', ')&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I limit the number of values in GROUP_CONCAT?
&lt;/h3&gt;

&lt;p&gt;Not with a &lt;code&gt;LIMIT&lt;/code&gt; inside the function in MySQL, but you can order the values and keep the first N with &lt;code&gt;SUBSTRING_INDEX(GROUP_CONCAT(col ORDER BY score DESC), ',', N)&lt;/code&gt;. For complex cases, use &lt;code&gt;ROW_NUMBER()&lt;/code&gt; in a subquery.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between GROUP_CONCAT and JSON_ARRAYAGG?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GROUP_CONCAT&lt;/code&gt; returns a plain string with your chosen separator and supports &lt;code&gt;ORDER BY&lt;/code&gt; and &lt;code&gt;DISTINCT&lt;/code&gt; inside the call. &lt;code&gt;JSON_ARRAYAGG&lt;/code&gt; returns a JSON array with proper quoting, which is safer to parse in code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/mysql-group-concat-order-by-separator" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
    </item>
    <item>
      <title>What Is %windir%? Windows Environment Variables Explained (With a List)</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Wed, 14 Oct 2026 04:02:12 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/what-is-windir-windows-environment-variables-explained-with-a-list-49m8</link>
      <guid>https://dev.to/mfaysaldev/what-is-windir-windows-environment-variables-explained-with-a-list-49m8</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;code&gt;%windir%&lt;/code&gt; is a Windows environment variable that holds the path of the Windows folder on the system drive, which is usually &lt;code&gt;C:\Windows&lt;/code&gt;.&lt;/strong&gt; The percent signs tell Windows to swap the name for its value, so typing &lt;code&gt;%windir%\System32&lt;/code&gt; in the Run box opens &lt;code&gt;C:\Windows\System32&lt;/code&gt;. &lt;code&gt;%SystemRoot%&lt;/code&gt; points to the same folder. Environment variables like these let scripts, installers and tutorials refer to folders without hard-coding a drive letter or user name.&lt;/p&gt;

&lt;p&gt;I kept seeing &lt;code&gt;%windir%&lt;/code&gt;, &lt;code&gt;%appdata%&lt;/code&gt; and &lt;code&gt;%temp%&lt;/code&gt; in Windows security labs and setup guides without really knowing what they were, so I worked through Microsoft’s documentation (checked on 11 October 2026) and wrote down what actually matters. This post covers what the common variables mean, where they come from and how to view or change them safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an environment variable?
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;environment variable&lt;/strong&gt; is a named piece of text that Windows hands to every program it starts. Each process gets an &lt;strong&gt;environment block&lt;/strong&gt;, a list of &lt;code&gt;NAME=value&lt;/code&gt; pairs. Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/windows/win32/procthread/environment-variables" rel="noopener noreferrer"&gt;environment variables documentation&lt;/a&gt; explains that there are two types, &lt;strong&gt;user&lt;/strong&gt; variables (set for each user) and &lt;strong&gt;system&lt;/strong&gt; variables (set for everyone), and that a child process inherits the environment of its parent by default.&lt;/p&gt;

&lt;p&gt;That inheritance is the key idea. When you open Command Prompt from the Start menu, it inherits the environment from Explorer. Any program you start from that Command Prompt inherits it from there. That’s why changing a variable in one window doesn’t magically change it in programs that are already running.&lt;/p&gt;

&lt;p&gt;A few more rules from the same page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A variable name can’t contain an equals sign (&lt;code&gt;=&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A user-defined variable can be up to 32,767 characters long.&lt;/li&gt;
&lt;li&gt;Names aren’t case-sensitive on Windows, so &lt;code&gt;%WINDIR%&lt;/code&gt;, &lt;code&gt;%windir%&lt;/code&gt; and &lt;code&gt;%WinDir%&lt;/code&gt; are the same thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What %windir% and %SystemRoot% mean
&lt;/h2&gt;

&lt;p&gt;Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables" rel="noopener noreferrer"&gt;list of recognised environment variables&lt;/a&gt; describes &lt;code&gt;WINDIR&lt;/code&gt; as the variable that “refers to the Windows folder located on the system drive” and lists &lt;code&gt;SYSTEMROOT&lt;/code&gt; as “same as WINDIR”. Its typical value is &lt;code&gt;C:\Windows&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So why do both exist? Historically, &lt;code&gt;%SystemRoot%&lt;/code&gt; belongs to the Windows NT family, while &lt;code&gt;%windir%&lt;/code&gt; was already used by the older DOS-based versions of Windows, and both were kept for compatibility. Today they hold the same path on a normal installation, and you’ll see both in Microsoft’s own documentation and registry values. In my experience, &lt;code&gt;%SystemRoot%&lt;/code&gt; turns up more in registry paths and service settings, and &lt;code&gt;%windir%&lt;/code&gt; more in scripts and Run-box shortcuts.&lt;/p&gt;

&lt;p&gt;Useful folders under &lt;code&gt;%windir%&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%windir%\System32&lt;/code&gt;&lt;/strong&gt;: What’s there: Core 64-bit system programs and libraries (&lt;code&gt;cmd.exe&lt;/code&gt;, &lt;code&gt;notepad.exe&lt;/code&gt;, most &lt;code&gt;.msc&lt;/code&gt; consoles).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%windir%\SysWOW64&lt;/code&gt;&lt;/strong&gt;: What’s there: 32-bit system files on 64-bit Windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%windir%\System32\drivers\etc&lt;/code&gt;&lt;/strong&gt;: What’s there: The &lt;code&gt;hosts&lt;/code&gt; file, which maps names to IP addresses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%windir%\Temp&lt;/code&gt;&lt;/strong&gt;: What’s there: Temporary files used by the system and services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%windir%\Fonts&lt;/code&gt;&lt;/strong&gt;: What’s there: Installed fonts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%windir%\Logs&lt;/code&gt;&lt;/strong&gt;: What’s there: Various Windows log files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That &lt;code&gt;hosts&lt;/code&gt; file comes up a lot in security courses, because malware has historically edited it to redirect websites. It’s protected: you need admin rights to change anything under &lt;code&gt;%windir%&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Windows environment variables (list)
&lt;/h2&gt;

&lt;p&gt;These are the variables you’ll meet most often. Typical values are from Microsoft’s recognised variables list and assume Windows is on &lt;code&gt;C:&lt;/code&gt; and the user is called &lt;code&gt;mahir&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%windir%&lt;/code&gt; / &lt;code&gt;%SystemRoot%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\Windows&lt;/code&gt; (system variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%SystemDrive%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:&lt;/code&gt; (a drive, not a folder: no trailing backslash) (system variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%ProgramFiles%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\Program Files&lt;/code&gt; (system variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%ProgramFiles(x86)%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\Program Files (x86)&lt;/code&gt; on 64-bit Windows (system variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%ProgramData%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\ProgramData&lt;/code&gt; (application data for all users) (system variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%ALLUSERSPROFILE%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\ProgramData&lt;/code&gt; (system variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%USERPROFILE%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\Users\mahir&lt;/code&gt; (user variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%APPDATA%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\Users\mahir\AppData\Roaming&lt;/code&gt; (user variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%LOCALAPPDATA%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\Users\mahir\AppData\Local&lt;/code&gt; (user variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%TEMP%&lt;/code&gt; / &lt;code&gt;%TMP%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\Users\mahir\AppData\Local\Temp&lt;/code&gt; (user variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%USERNAME%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;mahir&lt;/code&gt; (user variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%COMPUTERNAME%&lt;/code&gt;&lt;/strong&gt;: The PC’s name (system variable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%PATH%&lt;/code&gt;&lt;/strong&gt;: A semicolon-separated list of folders (user and system values combined).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;%COMSPEC%&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;C:\Windows\System32\cmd.exe&lt;/code&gt; (system variable).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Roaming vs Local AppData:&lt;/strong&gt; Microsoft describes &lt;code&gt;%APPDATA%&lt;/code&gt; (Roaming) as a common repository for application-specific data, which can roam with a user profile on managed networks, and &lt;code&gt;%LOCALAPPDATA%&lt;/code&gt; as the home for local, non-roaming application data. Browser caches and large files usually sit in Local; small settings files often sit in Roaming.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use environment variables in Explorer and the Run box
&lt;/h2&gt;

&lt;p&gt;You can type a variable anywhere Windows accepts a path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;Win + R&lt;/strong&gt; to open Run.&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;%temp%&lt;/code&gt; and press &lt;strong&gt;Enter&lt;/strong&gt;. Your temporary files folder opens.&lt;/li&gt;
&lt;li&gt;Try &lt;code&gt;%appdata%&lt;/code&gt;, &lt;code&gt;%userprofile%\Downloads&lt;/code&gt; or &lt;code&gt;%windir%\System32\drivers\etc&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Explorer’s address bar works the same way. This is handy when a guide says “delete the app’s folder in AppData”: you don’t have to un-hide anything or know the user name, you just type &lt;code&gt;%appdata%&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Clearing &lt;code&gt;%temp%&lt;/code&gt; is a classic safe tidy-up: close your programs, open &lt;code&gt;%temp%&lt;/code&gt;, select everything and delete it. Windows will skip any file that’s still in use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing environment variables in Command Prompt
&lt;/h2&gt;

&lt;p&gt;In Command Prompt (&lt;code&gt;cmd.exe&lt;/code&gt;) you read a variable by wrapping its name in percent signs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\&amp;gt; echo %windir%
C:\Windows

C:\&amp;gt; echo %USERPROFILE%
C:\Users\mahir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see everything, use the built-in &lt;a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/set_1" rel="noopener noreferrer"&gt;set command&lt;/a&gt;. Microsoft notes that with no parameters it “displays the current environment variable settings”, and that &lt;code&gt;set&lt;/code&gt; with part of a name lists every variable that begins with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\&amp;gt; set
C:\&amp;gt; set prog
ProgramData=C:\ProgramData
ProgramFiles=C:\Program Files
ProgramFiles(x86)=C:\Program Files (x86)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output above is the typical shape; exact values depend on your PC.&lt;/p&gt;

&lt;p&gt;One quirk worth knowing: if a variable doesn’t exist, Command Prompt doesn’t show an error. &lt;code&gt;echo %NOSUCHVAR%&lt;/code&gt; simply prints &lt;code&gt;%NOSUCHVAR%&lt;/code&gt; back at you, percent signs and all. So if you see the raw name in a script’s output, that’s your clue the variable is misspelt or was never set in that window. In a batch file, &lt;code&gt;if defined NOSUCHVAR echo yes&lt;/code&gt; is the tidy way to test for it before you use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Temporary vs permanent variables: set vs setx
&lt;/h2&gt;

&lt;p&gt;This is where most beginners get caught out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;set&lt;/code&gt; is temporary.&lt;/strong&gt; &lt;code&gt;set MYVAR=hello&lt;/code&gt; creates or changes a variable for the current Command Prompt window and the programs started from it. Close the window and it’s gone. Microsoft’s user environment variables page says the same thing: variables created with &lt;code&gt;set&lt;/code&gt; “apply only to the command window in which they are set, and to its child processes”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;setx&lt;/code&gt; is permanent.&lt;/strong&gt; Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setx" rel="noopener noreferrer"&gt;setx documentation&lt;/a&gt; says it writes variables “to the master environment in the registry”. By default it sets a user variable; add &lt;code&gt;/m&lt;/code&gt; to set a system variable (which needs an elevated prompt).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setx MYVAR "hello"
setx MYVAR "hello" /m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three &lt;code&gt;setx&lt;/code&gt; gotchas, all straight from the docs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It doesn’t affect the current window.&lt;/strong&gt; “Variables set with setx variables are available in future command windows only, not in the current command window.” Open a new window to see the change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It truncates at 1,024 characters.&lt;/strong&gt; If you assign more, the content is cropped, and if you were overwriting an existing variable you can lose data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It expands references.&lt;/strong&gt; If &lt;code&gt;%PATH%&lt;/code&gt; contains &lt;code&gt;%JAVADIR%&lt;/code&gt; and you rewrite &lt;code&gt;PATH&lt;/code&gt; with &lt;code&gt;setx&lt;/code&gt;, the reference is replaced with its current value, so later changes to &lt;code&gt;JAVADIR&lt;/code&gt; won’t flow through.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Point 2 and point 3 are why you should &lt;strong&gt;never&lt;/strong&gt; run &lt;code&gt;setx PATH "%PATH%;C:\something"&lt;/code&gt;. On many PCs, &lt;code&gt;PATH&lt;/code&gt; is already near or over 1,024 characters, and that command merges the user and system PATH into your user variable while cropping it. Use the graphical editor below instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment variables in PowerShell: $Env:windir
&lt;/h2&gt;

&lt;p&gt;PowerShell doesn’t use percent signs. According to Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables" rel="noopener noreferrer"&gt;about_Environment_Variables&lt;/a&gt; page, you use the &lt;code&gt;$Env:&lt;/code&gt; prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;windir&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# C:\Windows&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;Foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'An example'&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# current session only&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Env:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# list every variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting &lt;code&gt;$Env:Foo&lt;/code&gt; only changes the current PowerShell session, just like &lt;code&gt;set&lt;/code&gt; in Command Prompt. The same page notes that environment variable names are case-sensitive on macOS and Linux, unlike on Windows, so &lt;code&gt;$Env:Path&lt;/code&gt; and &lt;code&gt;$Env:PATH&lt;/code&gt; are different there. That difference bites people who write cross-platform scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to edit environment variables in Windows 11 and 10
&lt;/h2&gt;

&lt;p&gt;For permanent changes, the graphical editor is the safest route:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;Win&lt;/strong&gt;, type &lt;strong&gt;environment variables&lt;/strong&gt; and choose &lt;strong&gt;Edit the system environment variables&lt;/strong&gt; (or &lt;strong&gt;Edit environment variables for your account&lt;/strong&gt; for user-only changes).&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Environment Variables…&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The top list holds your &lt;strong&gt;user&lt;/strong&gt; variables; the bottom list holds &lt;strong&gt;system&lt;/strong&gt; variables.&lt;/li&gt;
&lt;li&gt;Select a variable and click &lt;strong&gt;Edit&lt;/strong&gt;. For &lt;code&gt;Path&lt;/code&gt;, Windows shows one folder per line, so you can add, remove or reorder entries without editing one long string.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt; in every window, then restart any program that needs the new value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Behind the scenes, Microsoft documents that system variables live in the registry under &lt;code&gt;HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment&lt;/code&gt;, and that programs which change them should broadcast a &lt;code&gt;WM_SETTINGCHANGE&lt;/code&gt; message so the shell picks up the update. User variables live under &lt;code&gt;HKEY_CURRENT_USER\Environment&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How PATH works (and why order matters)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PATH&lt;/code&gt; is the most important variable for anyone who codes. When you type &lt;code&gt;python&lt;/code&gt; or &lt;code&gt;git&lt;/code&gt; without a full path, Windows searches the current folder and then each folder listed in &lt;code&gt;PATH&lt;/code&gt;, in order, for a matching program. The first match wins.&lt;/p&gt;

&lt;p&gt;That has two consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Not recognised as an internal or external command”&lt;/strong&gt; usually means the program’s folder isn’t in &lt;code&gt;PATH&lt;/code&gt;, or you haven’t opened a new terminal since adding it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order matters for security.&lt;/strong&gt; If an attacker can write to a folder that appears early in &lt;code&gt;PATH&lt;/code&gt;, they can drop a fake &lt;code&gt;python.exe&lt;/code&gt; or &lt;code&gt;git.exe&lt;/code&gt; there and it will run instead of the real one. Keep user-writable folders out of the system &lt;code&gt;PATH&lt;/code&gt;, and check new entries before adding them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see which copy of a program wins with &lt;code&gt;where&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\&amp;gt; where python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Environment variables and security: what to look for
&lt;/h2&gt;

&lt;p&gt;Once you know these variables, a lot of security advice makes more sense. Here’s why they keep appearing in labs and incident write-ups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User-writable folders are where unwanted programs hide.&lt;/strong&gt; &lt;code&gt;%TEMP%&lt;/code&gt;, &lt;code&gt;%APPDATA%&lt;/code&gt; and &lt;code&gt;%LOCALAPPDATA%&lt;/code&gt; all sit inside your user profile, so any program you run can write there without an admin prompt. That makes them convenient for legitimate apps and for malware alike. When you’re checking a PC for something suspicious, look for unfamiliar &lt;code&gt;.exe&lt;/code&gt; files in these folders, especially ones set to start automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protected folders need elevation.&lt;/strong&gt; &lt;code&gt;%windir%&lt;/code&gt; and &lt;code&gt;%ProgramFiles%&lt;/code&gt; are writable only with administrator rights. A program that installs itself there had to get past a UAC prompt (I explain those in &lt;a href="https://mfaysal.com/blog/what-is-uac-in-windows" rel="noopener noreferrer"&gt;what UAC in Windows is&lt;/a&gt;), which is a useful clue when you’re working out how something arrived.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Variables are visible to every process you run.&lt;/strong&gt; Any program started under your account can read your user and system environment variables. They’re fine for configuration, and better than hard-coding secrets in source files, but they aren’t a vault. On a shared PC, don’t store passwords in system variables, and on a server, give each app only the keys it needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scripts should use variables, not hard-coded paths.&lt;/strong&gt; A script that writes to &lt;code&gt;C:\Users\mahir\Desktop&lt;/code&gt; breaks for every other user and on any PC where Windows isn’t on &lt;code&gt;C:&lt;/code&gt;. Using &lt;code&gt;%USERPROFILE%\Desktop&lt;/code&gt; or &lt;code&gt;%SystemDrive%&lt;/code&gt; keeps it portable, and it’s easier to review, because the intent is obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading environment variables in code
&lt;/h2&gt;

&lt;p&gt;Environment variables are also how developers keep configuration and secrets out of source code. The same idea runs from a Windows PC up to a cloud host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WINDIR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;# C:\Windows on Windows, None on Linux
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APPDATA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'windir'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'not on Windows'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;.get()&lt;/code&gt; with a default in Python, or checking &lt;code&gt;getenv()&lt;/code&gt;’s &lt;code&gt;false&lt;/code&gt; return in PHP, means your script won’t crash on a machine where the variable doesn’t exist. Hosting platforms use the same mechanism for API keys; I used it when &lt;a href="https://mfaysal.com/blog/gemini-api-key-nextjs-vercel" rel="noopener noreferrer"&gt;setting a Gemini API key on Vercel&lt;/a&gt;, so the key never lands in the code repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is %windir% in Windows?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;%windir%&lt;/code&gt; is an environment variable that stores the path of the Windows folder on the system drive, usually &lt;code&gt;C:\Windows&lt;/code&gt;. Windows replaces the variable with that path when you use it in the Run box, Explorer, a script or a shortcut.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between %windir% and %SystemRoot%?
&lt;/h3&gt;

&lt;p&gt;On a normal installation there’s no difference in value: Microsoft lists &lt;code&gt;SYSTEMROOT&lt;/code&gt; as “same as WINDIR”, and both point to the Windows folder. &lt;code&gt;%SystemRoot%&lt;/code&gt; comes from the Windows NT line and is common in registry values, while &lt;code&gt;%windir%&lt;/code&gt; is older and common in scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where is %appdata%?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;%appdata%&lt;/code&gt; points to the Roaming application data folder, typically &lt;code&gt;C:\Users\&amp;lt;username&amp;gt;\AppData\Roaming&lt;/code&gt;. &lt;code&gt;%localappdata%&lt;/code&gt; points to &lt;code&gt;C:\Users\&amp;lt;username&amp;gt;\AppData\Local&lt;/code&gt;. Type either into the Run box to open it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to delete files in %temp%?
&lt;/h3&gt;

&lt;p&gt;Generally yes. &lt;code&gt;%temp%&lt;/code&gt; holds temporary files, and Windows won’t let you delete files that are still in use. Close your programs first, then delete what’s left. Don’t delete the Temp folder itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between set and setx?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;set&lt;/code&gt; changes a variable only in the current Command Prompt window and its child processes. &lt;code&gt;setx&lt;/code&gt; saves the variable permanently in the registry, but the change only appears in new windows, and values longer than 1,024 characters are cropped.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I see all environment variables in Windows?
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;set&lt;/code&gt; in Command Prompt or &lt;code&gt;Get-ChildItem Env:&lt;/code&gt; in PowerShell. For permanent user and system variables, open &lt;strong&gt;Edit the system environment variables&lt;/strong&gt; and click &lt;strong&gt;Environment Variables…&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my new PATH entry not work?
&lt;/h3&gt;

&lt;p&gt;Programs read their environment when they start, so close and reopen your terminal or editor after editing &lt;code&gt;PATH&lt;/code&gt;. Also check the folder you added actually contains the program, and that there isn’t a typo or a stray quote in the entry.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/windows-environment-variables-windir" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>cybersecurity</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NTFS vs FAT32 vs exFAT: Which Format Should You Use? (Windows 2026)</title>
      <dc:creator>Mahir Faysal</dc:creator>
      <pubDate>Tue, 13 Oct 2026 03:59:14 +0000</pubDate>
      <link>https://dev.to/mfaysaldev/ntfs-vs-fat32-vs-exfat-which-format-should-you-use-windows-2026-36nm</link>
      <guid>https://dev.to/mfaysaldev/ntfs-vs-fat32-vs-exfat-which-format-should-you-use-windows-2026-36nm</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer: use NTFS for Windows system drives and internal disks, exFAT for USB sticks, external drives and SD cards that move between Windows and Mac, and FAT32 only when an older device (a car stereo, a games console, some cameras) refuses anything else.&lt;/strong&gt; FAT32 can’t store a single file bigger than 4 GB, and Windows’ own tools won’t create a new FAT32 volume larger than 32 GB. exFAT removes those limits but has no permissions or journaling. NTFS has both, but Macs can’t format it.&lt;/p&gt;

&lt;p&gt;I wrote this while studying Windows fundamentals for cybersecurity, because the “which format?” question keeps coming up in labs and at home. Every limit below comes from Microsoft’s documentation or Apple’s Disk Utility guide, checked on 11 October 2026. I’ve kept the maths light and the commands safe to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a file system actually is
&lt;/h2&gt;

&lt;p&gt;A drive is just billions of numbered blocks. A &lt;strong&gt;file system&lt;/strong&gt; is the set of rules that turns those blocks into files and folders: where each file starts, how big it is, when it changed and (on some file systems) who’s allowed to open it. When you “format” a drive, you’re writing a fresh, empty copy of those rules onto it, which is why formatting wipes what was there.&lt;/p&gt;

&lt;p&gt;Windows supports several file systems. The three you’ll meet every week are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAT32&lt;/strong&gt;, from the 1990s. Simple, and understood by almost every device ever made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NTFS&lt;/strong&gt; (New Technology File System), the native file system of Windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;exFAT&lt;/strong&gt; (Extended File Allocation Table), designed by Microsoft for flash storage such as USB sticks and SD cards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  NTFS vs FAT32 vs exFAT at a glance
&lt;/h2&gt;

&lt;p&gt;This comparison pulls together Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison" rel="noopener noreferrer"&gt;file system functionality comparison&lt;/a&gt; and the other sources linked below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maximum file size&lt;/strong&gt;: NTFS: 2^64 − 1 bytes in the format (practically limited by cluster size, 16 TB at the default 4 KB); exFAT: 2^64 − 1 bytes; FAT32: 4 GiB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Largest new volume Windows will format&lt;/strong&gt;: NTFS: 8 PB on recent Windows (with 2 MB clusters); exFAT: Very large (2^32 − 1 clusters); FAT32: 32 GB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File name length&lt;/strong&gt;: NTFS: 255 Unicode characters; exFAT: 255 Unicode characters; FAT32: 255 Unicode characters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions (access control lists)&lt;/strong&gt;: NTFS: Yes; exFAT: No; FAT32: No.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracks file owner&lt;/strong&gt;: NTFS: Yes; exFAT: No; FAT32: No.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in encryption and compression&lt;/strong&gt;: NTFS: Yes; exFAT: No; FAT32: No.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Journaling&lt;/strong&gt;: NTFS: Yes (metadata); exFAT: No; FAT32: No.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk quotas&lt;/strong&gt;: NTFS: Yes; exFAT: No; FAT32: No.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard links and symbolic links&lt;/strong&gt;: NTFS: Yes; exFAT: No; FAT32: No.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac Disk Utility can format it&lt;/strong&gt;: NTFS: No; exFAT: Yes; FAT32: Yes (as MS-DOS FAT).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is clear. NTFS is the feature-rich one, FAT32 is the most compatible, and exFAT sits in the middle: big files, broad compatibility, very few features.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAT32: the old, universal option
&lt;/h2&gt;

&lt;p&gt;FAT stands for &lt;strong&gt;File Allocation Table&lt;/strong&gt;, the table near the start of the drive that records which blocks belong to which file. FAT32 is the version introduced with Windows 95 OSR2 and Windows 98, using 32-bit entries (28 bits of which are actually used for cluster numbers).&lt;/p&gt;

&lt;p&gt;FAT32’s two famous limits trip people up constantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 4 GB file limit
&lt;/h3&gt;

&lt;p&gt;FAT32 stores each file’s size as a 32-bit number, so no single file can be 4 GB or larger. Microsoft’s comparison table lists the maximum file size as 4 GiB. Try to copy a 6 GB video or a Windows ISO to a FAT32 stick and you’ll get an error saying the file is too large for the destination file system, even if the stick has 50 GB free.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 32 GB format limit
&lt;/h3&gt;

&lt;p&gt;FAT32 could theoretically address volumes of several terabytes. But, as Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/archive/technet-wiki/5375.windows-file-systems" rel="noopener noreferrer"&gt;Windows file systems overview&lt;/a&gt; explains, Windows “works with existing FAT32 volumes of larger sizes (created in other operating systems)” while it “limits new FAT32 volumes to a maximum of 32 GB”. That’s why the FAT32 option disappears from Windows’ format dialog for a 64 GB stick. Third-party tools can make bigger FAT32 volumes, but at that size exFAT is the better answer anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick FAT32 when&lt;/strong&gt; a device’s manual says it only reads FAT32, the drive is 32 GB or smaller, and you won’t store any file of 4 GB or more.&lt;/p&gt;

&lt;h2&gt;
  
  
  exFAT: built for flash drives
&lt;/h2&gt;

&lt;p&gt;Microsoft designed exFAT to fix FAT32’s limits without adding NTFS’s overhead, which matters on cheap flash memory. The Windows file systems overview notes that it raises the file size limit to 2^64 bytes, adds a bitmap that tracks free clusters (making allocation and deletion faster) and allows more than 1,000 files in a single directory.&lt;/p&gt;

&lt;p&gt;exFAT is also widely supported outside Windows. Apple’s &lt;a href="https://support.apple.com/guide/disk-utility/file-system-formats-dsku19ed921c/mac" rel="noopener noreferrer"&gt;Disk Utility guide&lt;/a&gt; lists both MS-DOS (FAT) and ExFAT as the file systems “compatible with Windows” that a Mac can create. Microsoft also publishes the full exFAT specification on its developer documentation site.&lt;/p&gt;

&lt;p&gt;The trade-off is that exFAT is deliberately simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No permissions.&lt;/strong&gt; Anyone who plugs the drive in can read every file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No journaling.&lt;/strong&gt; If you pull the drive out mid-copy, the file system can be left inconsistent. Always use &lt;strong&gt;Safely Remove Hardware&lt;/strong&gt; or &lt;strong&gt;Eject&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No encryption built into the file system.&lt;/strong&gt; BitLocker To Go can still encrypt an exFAT drive on supported Windows editions, but exFAT itself has no encryption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pick exFAT when&lt;/strong&gt; you’re formatting a USB stick, SD card or external SSD bigger than 32 GB, especially if it moves between Windows and macOS, or you need to store files larger than 4 GB.&lt;/p&gt;

&lt;h2&gt;
  
  
  NTFS: the Windows default
&lt;/h2&gt;

&lt;p&gt;NTFS is what your &lt;code&gt;C:&lt;/code&gt; drive uses, and for good reasons. It keeps a journal of changes to its own structures, so after a crash or power cut it can repair itself to a consistent state. The Windows file systems overview also notes that the NTFS driver can make many minor repairs while Windows is running, without a reboot.&lt;/p&gt;

&lt;p&gt;NTFS is also the only one of the three with &lt;strong&gt;security built in&lt;/strong&gt;. Each file and folder has an &lt;strong&gt;access control list (ACL)&lt;/strong&gt;: a list of users and groups and what each may do (read, write, modify, full control). That’s what the &lt;strong&gt;Security&lt;/strong&gt; tab in a file’s Properties shows. Without ACLs, Windows couldn’t stop one user reading another user’s Documents folder, or stop a program overwriting system files.&lt;/p&gt;

&lt;p&gt;Other NTFS features you’ll see in Windows and security labs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encrypting File System (EFS)&lt;/strong&gt; and compression per file or folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk quotas&lt;/strong&gt; per user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard links and symbolic links&lt;/strong&gt;, used heavily by Windows itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alternate data streams (ADS)&lt;/strong&gt;, extra hidden “streams” of data attached to a file. Windows uses one to mark files downloaded from the internet, and attackers have historically used ADS to hide data, which is why it comes up in security courses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  NTFS size limits in 2026
&lt;/h3&gt;

&lt;p&gt;The maximum NTFS volume depends on the cluster size. According to Microsoft’s &lt;a href="https://learn.microsoft.com/en-gb/windows-server/storage/file-server/ntfs-overview" rel="noopener noreferrer"&gt;NTFS overview&lt;/a&gt;, Windows 10 version 1709 and later, and Windows Server 2019 and later, support NTFS volumes up to 8 PB using 2 MB clusters. Earlier versions top out at 256 TB. At the default 4 KB cluster size, the largest volume and file is 16 TB, which is still far bigger than any single file you’re likely to handle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick NTFS when&lt;/strong&gt; the drive stays inside or attached to Windows PCs: the system disk, a second internal SSD, or an external backup drive used only with Windows. Also pick it when you need permissions or encryption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one should I use? A quick decision guide
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows system drive or internal disk&lt;/strong&gt;: NTFS. Permissions, journaling, Windows features need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;USB stick or SD card used on Windows and Mac&lt;/strong&gt;: exFAT. Both can read and write; no 4 GB limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External backup drive for Windows only&lt;/strong&gt;: NTFS. Journaling protects against corruption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old car stereo, console or camera&lt;/strong&gt;: FAT32. Many older devices only read FAT32.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drive of 32 GB or less for maximum compatibility&lt;/strong&gt;: FAT32. Readable almost everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storing a single file over 4 GB&lt;/strong&gt;: exFAT or NTFS. FAT32 physically can’t.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive files on a removable drive&lt;/strong&gt;: NTFS with BitLocker, or exFAT with BitLocker To Go. Neither FAT format has built-in protection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re unsure about a camera or TV, check its manual. Many modern cameras format SD cards themselves, and that is usually the safest option because the camera picks the format it expects.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check which file system a drive uses
&lt;/h2&gt;

&lt;p&gt;You don’t need to format anything to find out. Any of these works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;File Explorer:&lt;/strong&gt; right-click the drive, choose &lt;strong&gt;Properties&lt;/strong&gt;, and read &lt;strong&gt;File system&lt;/strong&gt; on the General tab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command Prompt:&lt;/strong&gt; run &lt;code&gt;fsutil fsinfo volumeinfo E:&lt;/code&gt; (replace &lt;code&gt;E:&lt;/code&gt; with your drive letter) and look for &lt;strong&gt;File System Name&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PowerShell:&lt;/strong&gt; run &lt;code&gt;Get-Volume&lt;/code&gt; to list every volume with its file system type, size and free space.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-Volume&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DriveLetter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FileSystemLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FileSystemType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SizeRemaining&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to format a drive as exFAT, NTFS or FAT32
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Warning: formatting erases everything on the drive. Copy anything you need first and triple-check the drive letter.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The easy route is File Explorer: right-click the drive, choose &lt;strong&gt;Format&lt;/strong&gt;, pick the file system and click &lt;strong&gt;Start&lt;/strong&gt;. Leave &lt;strong&gt;Allocation unit size&lt;/strong&gt; on the default; Microsoft says default settings are recommended for general use.&lt;/p&gt;

&lt;p&gt;From an administrator command prompt, the built-in &lt;a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/format" rel="noopener noreferrer"&gt;format command&lt;/a&gt; does the same job. The &lt;code&gt;/FS:&lt;/code&gt; switch accepts FAT, FAT32, NTFS, exFAT, ReFS or UDF, &lt;code&gt;/V:&lt;/code&gt; sets the label and &lt;code&gt;/Q&lt;/code&gt; does a quick format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;format E: /FS:exFAT /V:USBSTICK /Q
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows will ask you to confirm before it wipes the drive. You must be a member of the Administrators group to format a hard drive, so expect a UAC prompt if you start from a normal window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you convert FAT32 to NTFS without losing data?
&lt;/h2&gt;

&lt;p&gt;Yes, in one direction only. Windows includes the &lt;code&gt;convert&lt;/code&gt; tool, which Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/cc730863%28v=ws.11%29" rel="noopener noreferrer"&gt;convert documentation&lt;/a&gt; says “converts file allocation table (FAT) and FAT32 volumes to the NTFS file system, leaving existing files and directories intact”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;convert E: /fs:ntfs /v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things to know before you run it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It’s one-way.&lt;/strong&gt; Volumes converted to NTFS can’t be converted back to FAT or FAT32. Going back means backing up, reformatting and copying the files back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Back up first anyway.&lt;/strong&gt; Data loss is unlikely, but a power cut mid-conversion isn’t a risk worth taking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can’t convert to exFAT.&lt;/strong&gt; There’s no built-in in-place conversion to or from exFAT. For exFAT, you copy the files off, format and copy them back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Converted volumes can be slightly less efficient&lt;/strong&gt; than ones formatted as NTFS from the start; Microsoft notes the master file table might become fragmented.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why file systems matter in cybersecurity
&lt;/h2&gt;

&lt;p&gt;If you’re studying security, file systems aren’t just trivia. They decide what evidence exists and what protection a file has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permissions only exist on NTFS.&lt;/strong&gt; Copy a confidential file from an NTFS drive to an exFAT stick and its access controls are gone. Anyone with the stick can open it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamps differ.&lt;/strong&gt; Microsoft’s comparison shows FAT32 stores the last access time as a date only, and notes that NTFS last-access updating has changed between Windows versions. Investigators need to know that before reading too much into a timestamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alternate data streams&lt;/strong&gt; exist on NTFS but not on exFAT or FAT32, so copying a file to a FAT-family drive strips them, including the “downloaded from the internet” marker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption at rest&lt;/strong&gt; needs BitLocker or EFS. A lost USB stick formatted as plain exFAT is an open book.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NTFS permissions work hand in hand with account rights. Even an administrator’s programs run with limited rights until a prompt approves more, which I cover in &lt;a href="https://mfaysal.com/blog/what-is-uac-in-windows" rel="noopener noreferrer"&gt;what UAC in Windows is&lt;/a&gt;. Permissions on files plus limited rights for programs is the basic Windows defence model.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between NTFS, FAT32 and exFAT?
&lt;/h3&gt;

&lt;p&gt;NTFS is the Windows file system with permissions, journaling and encryption. FAT32 is an old, highly compatible format limited to 4 GB files and, in Windows’ format tool, 32 GB volumes. exFAT removes those size limits and works on Mac and Windows but has no permissions or journaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is exFAT or NTFS better for a USB drive?
&lt;/h3&gt;

&lt;p&gt;exFAT is usually better for a USB stick you share between computers, because macOS can read and write it. Choose NTFS if the drive is only used with Windows and you want journaling, permissions or file-level encryption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why can’t I copy a 4 GB file to my USB stick?
&lt;/h3&gt;

&lt;p&gt;The stick is almost certainly formatted as FAT32, which can’t hold any single file of 4 GB or more. Back up its contents and reformat it as exFAT or NTFS, or convert it to NTFS with &lt;code&gt;convert E: /fs:ntfs&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why doesn’t Windows offer FAT32 for my 64 GB drive?
&lt;/h3&gt;

&lt;p&gt;Windows limits new FAT32 volumes to a maximum of 32 GB, so the option disappears for bigger drives. Use exFAT instead. Windows can still read and write larger FAT32 volumes created elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a Mac format a drive as NTFS?
&lt;/h3&gt;

&lt;p&gt;Not with Apple’s Disk Utility. The Windows-compatible formats it offers are MS-DOS (FAT) and ExFAT. If a drive needs to be used on both Mac and Windows, format it as exFAT.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does formatting a drive delete everything?
&lt;/h3&gt;

&lt;p&gt;Yes. Formatting writes a new, empty file system, so the existing files are no longer accessible. A quick format doesn’t overwrite every sector, but you should still treat formatting as permanent and back up first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which file system is most secure?
&lt;/h3&gt;

&lt;p&gt;NTFS, because it supports access control lists, file ownership, EFS encryption and BitLocker. exFAT and FAT32 have no permissions, so anyone with physical access can read the files unless the whole drive is encrypted.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mfaysal.com/blog/ntfs-vs-fat32-vs-exfat" rel="noopener noreferrer"&gt;mfaysal.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm learning cybersecurity on TryHackMe — follow my progress at &lt;a href="https://tryhackme.com/p/mfaysal.dev" rel="noopener noreferrer"&gt;tryhackme.com/p/mfaysal.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>cybersecurity</category>
      <category>beginners</category>
      <category>storage</category>
    </item>
  </channel>
</rss>
