cds-amal
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # If You Can't Enumerate the Cases, You're Guessing **STATUS**: draft draft daft! ## Stop Guessing About Boolean Logic The most common bugs I see from refactoring aren't algorithmic. They come from this: "these conditions look the same; let's merge them." Sometimes you can. Sometimes you can't. The problem is knowing which case you're in. Sometimes, we have to be clinical not intuitive. What follows are two small tools that make that decision mechanical: truth tables (for verifying whether predicates are actually equivalent) and [De Morgan's laws](https://en.wikipedia.org/wiki/De_Morgan%27s_laws) (for rewriting predicates into a form we can read). They're basic. They're also underused in day-to-day engineering. I want to show how they work on real-ish examples, because the gap between "I've heard of truth tables" and "I reach for one when I'm staring at a suspicious refactor" is surprisingly wide. Chris, if you're reading this, and I'm sure you are because I will have pestered you; thanks for the inspiration and nudge to tackle this explanation. ## Ground rules Before we jump into examples, let's pin down three terms we'll use throughout: 1. **Predicate**: a boolean-valued function that encodes a decision boundary. `user.isAdmin`, `kind.is_fn_ptr()`, `form.hasTitle && !form.isSaving`; these are all predicates. 2. **Truth table**: an exhaustive mapping from every combination of predicate values to outcomes. It makes implicit logic explicit; there's nowhere for edge cases to hide. 3. **Enumerating predicates**: turning implicit branching logic into a finite, explicit set of boolean variables so we can reason about them systematically. --- So: two predicates, `A` and `B`. If a system's behavior depends on both, how many input combinations do we need to account for? Each predicate has 2 possible values, so the total is 2² = 4: | A | B | |:---:|:---:| | 0 | 0 | | 0 | 1 | | 1 | 0 | | 1 | 1 | --- Now that we've laid out the input space, what do the operations actually produce? Here are the three compositions that come up most often: AND, OR, and XOR. (We'll use standard [boolean algebra](https://en.wikipedia.org/wiki/Boolean_algebra) notation for the formulas: `¬` is negation (`!`), `∧` is AND (`&&`), `∨` is OR (`||`), `⊕` is XOR. The code examples use the code-syntax equivalents; the math notation appears only in the algebraic reductions.) | A | B | A ∧ B | A ∨ B | A ⊕ B | |:---:|:---:|:-----:|:-----:|:-----:| | 0 | 0 | 0 | 0 | 0 | | 0 | 1 | 0 | 1 | 1 | | 1 | 0 | 0 | 1 | 1 | | 1 | 1 | 1 | 1 | 0 | That's the whole toolkit. Everything below is just application. ## 1. "Looks Equivalent" Is Not Equivalent ### The situation Here's a function you've inherited: ```javascript function canEditDocument(user) { if (user.isAdmin || user.isOwner) { return true; } if (user.hasEditGrant && !user.isSuspended) { return true; } return false; } ``` Two branches. Both return `true`. Both "grant edit access." A natural instinct: collapse them. ```javascript function canEditDocument(user) { return ( (user.isAdmin || user.isOwner || user.hasEditGrant) && !user.isSuspended ); } ``` Shorter. Cleaner. Also wrong. But here's the thing: it's difficult what's wrong by inspection. It *looks* right. The bug only becomes visible when you stop reading and start enumerating. ### The approach Name the predicates so we can reason about them without getting lost in the syntax: - `P` = `isAdmin || isOwner` (privileged editor) - `G` = `hasEditGrant` - `S` = `isSuspended` The original logic is: `P || (G && !S)` The "simplified" version is: `(P || G) && !S` Worth pausing on the parentheses here, because they're doing all the work. In boolean algebra (just like arithmetic), precedence matters. The hierarchy in code is `!` > `&&` > `||` (the mnemonic is "NOT tightens, AND binds, OR loosens"). In arithmetic: `×`, `/` > `+`, `-`. Same idea: the "multiplication-like" operator evaluates before the "addition-like" one. If you've ever been bitten by `2 + 3 × 4` evaluating to `14` (not `20`), this is the same trap in boolean form. A concrete example you can verify in a REPL: ```javascript const result = true || false && false; // Actual: true || (false && false) → true // Wrong assumption: (true || false) && false → false ``` Same terms, completely different result depending on which operator binds first. The arithmetic version is even easier to see: `5 + (3 × 0)` is `5` (the multiplication is scoped; the `5` stands alone), but `(5 + 3) × 0` is `0` (the zero applies to everything). That's exactly what's happening with our predicates. The two expressions parse into different trees: ``` Original: P || (G && !S) Refactor: (P || G) && !S OR AND / \ / \ P AND OR !S / \ / \ G !S P G ``` Same symbols, different shape. In the original tree, `!S` only applies to the G branch; `P` exits to `true` independently. In the refactored tree, `!S` sits at the root and applies to *everything*, including the P path. The refactor didn't rearrange terms; it changed the structure of the expression. If you're not tracking operator precedence carefully, the code looks like it's just moving things around; the trees show that it's changing *what gets negated by suspension*. (Practical advice: even if you know the precedence rules, use explicit parentheses when the logic matters. `return (isAdmin || isOwner) && !isSuspended` is not redundant; it's communication. You're not writing for the compiler; you're writing for the next engineer, which is often you in two weeks.) These look similar. Are they equivalent? We don't guess. We enumerate. - let Orig = `P || (G && !S)` - let Ref = `(P || G) && !S` | P | G | S | Orig| Ref | ≡ | |---|---|---|---|---|---| | 0 | 0 | 0 | 0 | 0 | ✓ | | 0 | 0 | 1 | 0 | 0 | ✓ | | 0 | 1 | 0 | 1 | 1 | ✓ | | 0 | 1 | 1 | 0 | 0 | ✓ | | 1 | 0 | 0 | 1 | 1 | ✓ | | **1** | **0** | **1** | **1** | **0** | **✗** | | 1 | 1 | 0 | 1 | 1 | ✓ | | **1** | **1** | **1** | **1** | **0** | **✗** | The rows where `P=1` and `S=1` are the problem: a privileged user who is suspended. The original returns `true`; the refactor returns `false`. The "simplification" silently changed what suspension means for admins and owners. The duplication wasn't accidental. It encoded a real asymmetry: admins and owners bypass suspension; grant-based editors do not. The two branches *looked* like equivalent rules, but they were actually two **different** rules that changed the branching outcome. ### The principle **Truth tables turn intuition into proof.** The real question when deduplicating branching logic isn't "can we merge these?" It's: *do the predicates produce the same result for every possible input?* That's not something to reason about informally. Enumerate the input space; tabulate the outputs side by side. If they agree everywhere, merge freely. If they disagree somewhere, the disagreeing rows tell you exactly what the refactor would break. So what does the correct refactor look like? The truth table tells us: we need to preserve the asymmetry. Privileged editors get one rule; grant-based editors get another. Name the variation, keep it explicit: ```javascript function canEditDocument(user) { const isPrivileged = user.isAdmin || user.isOwner; const hasConditionalGrant = user.hasEditGrant && !user.isSuspended; return isPrivileged || hasConditionalGrant; } ``` No duplication. No loss of meaning. The table didn't just catch the bug; it told us how to structure the fix. ### A practical loop for safe deduplication When collapsing similar branches: 1. **Name the predicates.** Give each boolean condition a short variable name so you can reason about the logic, not the syntax. 2. **Enumerate the input space.** Build a truth table. List every combination of predicate values. 3. **Compare outputs side by side.** Add a column for each version of the logic. Add an equivalence column. 4. **If they differ, identify the variation.** The disagreeing rows tell you exactly which inputs would break under a naive merge. 5. **Make that variation explicit in the code.** Name the variation point; keep the shared logic shared. If you skip step 2, you're guessing. ## 2. De Morgan's Law: Make Predicates Readable ### The situation Truth tables help us decide whether two predicates are the same. De Morgan's laws help us rewrite a single predicate into a form we can actually reason about. Different problem, complementary tool. Here's a function that works correctly but is unpleasant to read: ```javascript function isSubmitEnabled(form) { return !(!form.hasTitle || !form.hasEmail || form.isSaving); } ``` This is the kind of thing people read three times and still don't trust. The logic is: "it's NOT the case that (title is missing OR email is missing OR we're currently saving)." Correct, but written in "reasons to reject" form, behind a negation. The reader has to mentally invert a disjunction of negations, which is one of those operations we're bad at. A side note, I cry a little every time I have to do this exercise. ### The approach Name the pieces: - `T` = `form.hasTitle` - `E` = `form.hasEmail` - `S` = `form.isSaving` The expression is: `!((!T) || (!E) || S)` De Morgan's law says: `!(X || Y || Z)` = `(!X) && (!Y) && (!Z)` Apply it: ``` !((!T) || (!E) || S) = T && E && !S ``` Rewrite: ```javascript function isSubmitEnabled(form) { return form.hasTitle && form.hasEmail && !form.isSaving; } ``` Same logic. Now it reads as a list of requirements ("title exists, email exists, not currently saving") instead of a negated list of rejections. That's a better predicate for both maintenance and review, because the conditions map directly to what we'd say if asked "when is submit enabled?" ### A more realistic example ```javascript function canPublish(post) { return !( post.tags.length === 0 || post.title.trim() === "" || post.errors.some(e => e.severity === "fatal") ); } ``` Apply De Morgan: ```javascript function canPublish(post) { return ( post.tags.length > 0 && post.title.trim() !== "" && !post.errors.some(e => e.severity === "fatal") ); } ``` The rewrite is easier to extend (add a new requirement: just add another `&&` clause), easier to test (each clause is independently verifiable), and easier to reason about (the conditions read top to bottom as "what does the post need?"). ### The principle **De Morgan's laws convert negated compound conditions into an easier-to-read form.** The mechanical rule is simple: push the negation inward, flip `||` to `&&` (or vice versa), negate each term. The payoff is that you go from "it's NOT the case that (A OR B OR C)" to "not-A AND not-B AND not-C," which almost always maps more naturally to how most people (at least me) think about the logic. A small example that's almost toy-sized but memorable: ```javascript if (!(user.isActive && user.hasPassword)) { denyLogin(); } ``` De Morgan: `!(A && B)` = `!A || !B` ```javascript if (!user.isActive || !user.hasPassword) { denyLogin(); } ``` That second form is often better when you want to log *why* login failed, because you can split the cases afterward. The negated conjunction hides the individual failure modes; the disjunction exposes them. ## 3. When to Reach for Each Tool These two techniques solve different problems. It's worth being explicit about which one to reach for: | Situation | Tool | What it answers | |-----------|------|-----------------| | Merging or deduplicating branches | Truth table | Are these predicates actually equivalent? | | Verifying a refactor preserves behavior | Truth table | Does the new version agree on every input? | | Something "feels equivalent" but you're not sure | Truth table | Where exactly do they disagree? | | A predicate is full of negations | De Morgan | Can we rewrite this into positive form? | | Logic is correct but hard to read | De Morgan | Can we express requirements instead of rejections? | | Code review: "I can't tell what this condition does" | De Morgan | What does this actually say in plain English? | Truth tables answer: *can we merge these branches safely?* De Morgan answers: *can we rewrite this condition into a clearer shape?* Both tools make our reasoning explicit. That matters in systems work, because a missed edge case becomes a production incident, a "harmless" refactor changes behavior under rare inputs, and duplicated logic diverges over time. So: next time the code looks right but you don't quite trust it, or a refactor seems obvious but carries risk, or two conditions seem identical but something feels off; reach for these. Truth tables give you a complete model. De Morgan gives you a readable one. If you can't enumerate the cases, you're guessing. I created a [repo for you to practice these concepts](https://github.com/cds-io/logic-exercises). Happy learning!

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully