Programming with PythonControl Flow and Collections › Day 50

Day 50: Conditionals and Boolean Logic

Day 50 of 365 — Conditionals and Boolean Logic

After this lesson you will be able to write clear decision code in Python: produce booleans with comparison operators, combine them with and/or/not (using short-circuit evaluation deliberately), and choose a path with if/elif/else, the conditional expression, and guard clauses that keep logic flat and correct.

Course
Programming with Python
Category
Control Flow and Collections
Reading time
≈ 40 min
Practical time
≈ 30 min
Lesson duration
1h 10m
Last verified
2026-07-13

Hands-on lab for this lesson

Lab files on GitHub: https://github.com/ai-roadmap-365/ai-roadmap-365.github.io/tree/main/labs/sections/programming-with-python/day-050-conditionals-and-boolean-logic

  1. Get the hands-on files. Clone the labs repository once (you can reuse this clone for every lesson). This works on macOS, Linux, and Windows (PowerShell or WSL):
    git clone https://github.com/ai-roadmap-365/ai-roadmap-365.github.io.git
    cd ai-roadmap-365.github.io
  2. Open this lesson's lab. Move into the directory for this specific day. Every lab lives at the same predictable path — section / subsection / week / day:
    cd labs/sections/programming-with-python/day-050-conditionals-and-boolean-logic
  3. Read the lab guide. Open `README.md` in that directory. It lists the exact commands, what each does, the expected output, and how to check your work — read it before running anything.
  4. Run it and check your work. Follow the README's "How to run" section: run the example first to see the finished result, then complete the numbered exercises in `starter/`, then run the tests. The tests pass (exit 0) only when your work is correct.
    bash tests/run_tests.sh   # or the test command named in the lab README

You can also open the lab as a local page (works offline, shows the file tree and expected output).

Learning objectives

By the end of this lesson you will be able to:

Prerequisites

Why this matters

Every program you have written so far runs straight down the page: line one, then line two, then line three. Real software does not work that way, and neither does any AI system. The moment a program has to react — accept this input but reject that one, retry on failure, route a request one way when a model is confident and another way when it is not — it needs to make decisions. Today you learn how a computer decides: booleans, comparisons, the logical operators and, or, and not, and the if/elif/else statement that turns a true-or-false answer into a chosen path.

This is not a detour from your AI goal; it is the spine of it. A model-serving endpoint is a tree of conditions: is the request well-formed? is the confidence above a threshold? does this output need a human to review it? A data-cleaning pipeline is a stack of rules — drop the row if the value is missing, clip it if it is out of range, keep it otherwise — and each rule is a boolean test. An agent’s control loop is a loop wrapped around a decision: has the task finished, or should it take another step? When any of these misbehaves — a pipeline silently keeping bad rows, an endpoint accepting predictions it should have flagged — the bug is almost always a boolean expression that says something subtly different from what its author meant.

The concrete consequence is debuggability. A condition written clearly (if score >= 0.9 and verified:) can be read aloud and checked against the spec in seconds. The same logic written as a tangle of nested ifs and doubled-up negatives can hide a wrong answer for months. The difference between a machine-learning pipeline you can trust and one you cross your fingers over is very often nothing more exotic than whether its conditions are readable. By the end of today you will write conditions the clear way by default.

The idea in plain language

A boolean is a value with exactly two possibilities: True or False. It is named after George Boole, a nineteenth-century mathematician, and it is the smallest possible unit of a decision — a single yes or no. Python has a type for it (bool), and the whole of decision-making is built from it.

You produce a boolean by comparing things. 3 < 5 is True; 3 == 5 is False. These comparison operators (==, !=, <, <=, >, >=) take two values and hand back one boolean. You then combine booleans with the logical operators and, or, and not: A and B is true only when both are true, A or B is true when at least one is, and not A flips true to false and back. That is the entire vocabulary. Everything else is arrangement.

Finally, you act on a boolean with an if statement. if condition: runs an indented block only when the condition is True. Add elif (else-if) to test a second condition when the first was false, and else to catch everything that remained. The program checks the branches from top to bottom, takes the first one whose condition is true, runs its block, and skips the rest. One input, one path. Master that sentence and you have mastered control flow.

Historical background

The logic came long before the computers. In 1847 the English mathematician George Boole published The Mathematical Analysis of Logic, and in 1854 his book An Investigation of the Laws of Thought, showing that reasoning with “and,” “or,” and “not” could be treated as algebra — an algebra in which variables take only the values true and false. For decades this was pure mathematics with no machine to run on.

The bridge to hardware came in 1937, when Claude Shannon, then a master’s student at MIT, showed in his thesis that Boole’s algebra described exactly how electrical switching circuits behave: a switch is on or off, circuits combine like and and or, and so a network of relays could compute any logical expression. That insight — that logic and circuitry are the same subject — is the reason every processor you will ever use is, at bottom, boolean. Day 1 of this course walked those logic gates; today you meet the same operators one layer up, in your own code.

The if/then/else structure of programming languages arrived with the first high-level languages of the 1950s, and one more idea completes the story. Augustus De Morgan, a contemporary and correspondent of Boole, gave his name to De Morgan’s laws, the rules that let you rewrite a negated combination: not (A and B) equals not A or not B, and not (A or B) equals not A and not B. You will use those laws later today to turn a confusing condition into a clear one. The names on these ideas — Boole, De Morgan, Shannon — are worth knowing, because you invoke their work every time you type and.

What it is — and what it is not

A conditional is a statement that chooses which code to run based on a boolean. Boolean logic is the small algebra that computes that boolean from comparisons and combinations. Both are about control flow — the order in which statements execute — as opposed to computation, the arithmetic that produces values. A conditional does not calculate a result; it decides which calculation happens.

It helps to be precise about what conditionals are not, because the misconceptions cause real bugs.

Common misconceptionThe reality
= and == are interchangeable.”= assigns a value to a name; == compares two values and yields a boolean. Confusing them is the classic beginner bug.
”A condition must be a True/False comparison.”Any value works: Python treats 0, "", [], {}, and None as falsy and almost everything else as truthy. if items: means “if the list is non-empty."
"and/or return True or False.”They return one of the operands, not a fresh boolean: "" or "default" is "default". The result is still truthy or falsy, which is why it usually looks like a boolean.
elif is just a style choice over nested ifs.”elif chains are mutually exclusive — at most one runs. Separate ifs each test independently, which can run several blocks and change behaviour.
”More nested ifs means more thorough code.”Deep nesting hides logic. A guard clause — handling the exceptional case first and returning — usually reads better and covers the same cases.

The last row is the day’s craft lesson: correctness and clarity are the same goal, and the clear version is usually the correct one.

Why it was created and what problems it solves

Without conditionals a program can only ever do the same thing. It could not validate input, because validation means “if this is wrong, refuse it.” It could not handle an error, because handling means “if something failed, do this instead.” It could not adapt to data, because adaptation means “if the value is large, treat it differently.” The conditional is the mechanism that makes a program general — able to respond correctly to inputs the author never typed by hand.

Boolean logic exists to make those decisions composable and checkable. Real conditions are rarely a single comparison; they are combinations — “confident enough and verified,” “missing or out of range.” Boole’s algebra gives those combinations exact meaning and predictable rules, so you can reason about a condition the way you reason about arithmetic, simplify it with De Morgan’s laws, and be sure your simplification changed nothing. The alternative — deciding by intuition and a pile of nested tests — is how software grows the kind of bug no one can find. Conditionals plus boolean logic are the tools that keep decisions small, named, and legible.

How it works

Let’s build the machinery from the bottom up: booleans, then comparisons, then logical operators, then the statements that act on them.

Booleans and truthiness

True and False are Python’s two boolean values (note the capital letters). Every comparison produces one. But Python also lets any value stand in for a condition, by defining which values are truthy and which are falsy. The falsy values are few and worth memorising: False, None, zero of any numeric type (0, 0.0), and every empty container ("", [], {}, (), set()). Everything else is truthy. This is why if name: is the idiomatic way to check that a string is non-empty, and if results: the idiomatic way to check that a list has items.

Comparison operators

The six comparisons each take two values and return a boolean:

OperatorMeaningExample (True)
==equal to2 + 2 == 4
!=not equal to"cat" != "dog"
<less than0.3 < 0.5
<=less than or equal0.9 <= 0.9
>greater than10 > 3
>=greater than or equal0.95 >= 0.9

Two subtleties matter. First, == compares values, so 1 == 1.0 is True. Second, Python supports chained comparisons: 0.0 <= score <= 1.0 means exactly what a mathematician would read it to mean — score is between 0 and 1 inclusive — and Python evaluates score only once. It is both more readable and correct than writing score >= 0.0 and score <= 1.0 by hand.

Logical operators and short-circuit evaluation

and, or, and not combine booleans according to the three truth tables you can build from their plain-English meaning. But Python evaluates them lazily, left to right, and stops as soon as the answer is settled — this is short-circuit evaluation. In A and B, if A is falsy the whole expression must be falsy, so B is never evaluated. In A or B, if A is truthy the whole expression is truthy, so B is skipped.

Diagram: how comparison operators produce booleans that logical operators combine into a decision

Read the map left to right: comparisons turn values into booleans, logical operators combine those booleans (short-circuiting when they can), and the result selects a branch. Short-circuiting is not just a speed trick — it is a correctness tool. if user is not None and user.is_active: is safe precisely because the and never touches user.is_active when user is None; reverse the order and you get a crash. You will lean on this pattern constantly.

if / elif / else

The statement that acts on all of this is if:

if score >= 0.9:
    category = "high"
elif score >= 0.5:
    category = "medium"
else:
    category = "low"

Python checks the conditions top to bottom, runs the block under the first true one, and skips every other branch. Because the branches are checked in order, elif score >= 0.5 is only reached when score >= 0.9 was already false — so it effectively means “at least 0.5 but below 0.9,” even though you did not write the upper bound. Order matters: if you put the >= 0.5 test first, the >= 0.9 block would become unreachable.

The conditional (ternary) expression

When all you want is to choose between two values, a full if block is heavy. Python offers a one-line conditional expression, often called the ternary:

band = "high" if score >= 0.9 else "low"

Read it as “band is high if score ≥ 0.9, otherwise low.” It is an expression — it produces a value you can assign or pass to a function — whereas if/else is a statement that runs blocks. Use the ternary for a simple two-way value choice; reach for a full if when a branch does more than pick one value.

Guard clauses instead of deep nesting

Suppose a function must reject bad input before doing its work. The tempting shape is to nest:

def classify(score, verified):
    if 0.0 <= score <= 1.0:
        if score >= 0.5:
            if score >= 0.9 and verified:
                return "AUTO_ACCEPT"
            else:
                return "REVIEW"
        else:
            return "REJECT"
    else:
        raise ValueError("score out of range")

Every level of indentation is a fact you must hold in your head. A guard clause flips this inside out: check the exceptional case first, deal with it immediately, and return — so the main logic never indents:

def classify(score, verified):
    if not 0.0 <= score <= 1.0:
        raise ValueError("score out of range")
    if score < 0.5:
        return "REJECT"
    if score >= 0.9 and verified:
        return "AUTO_ACCEPT"
    return "REVIEW"

Same decisions, far less nesting, and each line reads as one rule. This flat, guard-first style is the professional default, and it is exactly the shape you will build in today’s lab.

De Morgan’s laws in practice

Sometimes a condition arrives negated and confusing: if not (verified and score >= 0.9):. De Morgan’s laws let you push the not inward: not (A and B) becomes not A or not B, so the condition is equivalent to if not verified or score < 0.9: (note that not (score >= 0.9) is score < 0.9). The two are identical in behaviour; the second is easier to read aloud. Knowing the laws means you can always choose the clearer of two equivalent forms.

An everyday analogy

Picture a doorkeeper at a members’ event, holding a short set of rules and deciding, for each person who arrives, whether to admit them, send them to a desk for a manual check, or turn them away.

A single rule the doorkeeper checks — “are you over 18?”, “are you on the guest list?” — is a comparison: it has a yes-or-no answer. The doorkeeper combines rules with logical operators: admit if you are on the list and carrying ID (and); send to the desk if your name is missing or your ID looks wrong (or); the velvet rope is not the staff entrance (not). The doorkeeper is also efficient in exactly the way and short-circuits: the instant one required rule fails — no ID at all — they stop checking the rest and act, because the outcome is already decided. There is no point verifying the guest list for someone who cannot prove who they are.

The order of the rules is the if/elif/else chain. The doorkeeper works down the list and acts on the first rule that fires: an obvious turn-away (a guard clause) is handled first and fast, so it never clutters the careful checks that follow; the ordinary “admit” case sits in the middle; and “if none of the above, send them to the desk” is the else that catches everyone left. Each guest takes exactly one path through the door. Keep this doorkeeper in mind and every branch you write has a physical meaning: a rule checked, a combination formed, a single path chosen.

Examples in practice

Let’s trace the doorkeeper’s logic as real code — the same shape you will meet in a model-serving endpoint. Suppose a classifier hands you a score between 0 and 1 (its confidence) and a boolean verified (whether an upstream check passed), and you must route the prediction.

def route(score, verified):
    if not 0.0 <= score <= 1.0:          # guard: reject impossible input first
        raise ValueError(f"score {score} is out of range")
    if score < 0.5:                       # low confidence: turn away
        return "REJECT"
    if score >= 0.9 and verified:         # confident AND checked: admit
        return "AUTO_ACCEPT"
    return "REVIEW"                        # everything else: send to the desk

Now trace three inputs by hand, exactly as the interpreter would:

route(0.95, True)
  0.0 <= 0.95 <= 1.0  -> True, so "not ..." is False; guard skipped
  0.95 < 0.5          -> False; skip
  0.95 >= 0.9         -> True; AND verified (True) -> True; return "AUTO_ACCEPT"

route(0.95, False)
  guard passes
  0.95 < 0.5          -> False; skip
  0.95 >= 0.9         -> True; AND verified (False) -> False; skip
  fall through        -> return "REVIEW"

route(0.30, True)
  guard passes
  0.30 < 0.5          -> True; return "REJECT"   (later branches never checked)

Notice the short-circuit in the second trace: score >= 0.9 is True, so Python does evaluate verified, finds it False, and the and is False. In the third trace the REJECT branch returns immediately, so the AUTO_ACCEPT test never runs at all — the first matching branch wins.

Flowchart: an if/elif/else path that classifies a model prediction into accept, review, or reject

The diagram is that function drawn as a flow: the input enters at the top, the guard diverts invalid input to an error exit, and then each decision either resolves to an outcome or falls through to the next. This picture — validate, then a short ladder of decisions ending in a default — is the archetype of real decision code, from web-form validation to the routing layer in front of a large language model.

A second, tiny example shows truthiness earning its keep. To supply a default when a value is missing, you can write display_name = name or "anonymous": if name is an empty string (falsy), the or returns "anonymous"; otherwise it returns name. One readable line replaces a four-line if, powered entirely by falsiness and short-circuit or.

Implications: security, privacy, performance, scalability, and cost

Security. Conditionals are your input validation, and validation is the front line of security. A missing or wrong condition is how bad data slips in: an authorisation check that uses or where it needed and can admit a request it should have blocked. Order matters too — short-circuit lets you check “is this present?” before “is this valid?” so you never touch a value that is not there. Write the guard clauses that reject bad input first, and you have built a wall exactly where attacks arrive.

Privacy. Decisions determine what data is exposed. A single condition governs whether an error message includes a raw record or a generic apology, whether a log line records a user’s details or a redacted placeholder. Getting these branches right — showing sensitive fields only when a condition genuinely permits it — is privacy engineering at the level of the if statement.

Performance. Short-circuit evaluation is a performance lever: put the cheap, likely-decisive test first in an and/or chain and the expensive one may never run. if cache_hit or expensive_recompute(): skips the recomputation whenever the cache hit. At the scale of a data pipeline processing millions of rows, the order of two conditions can meaningfully change the running time.

Scalability. As a program grows, deeply nested conditionals become the part no one dares change. Flat guard clauses and small, well-named boolean helpers (is_valid, needs_review) keep decision logic legible as the codebase scales, which is why teams treat conditional clarity as a maintainability requirement, not a style preference.

Cost. In AI systems the branch often is the cost decision. “If confidence is high enough, answer directly; otherwise call the larger, pricier model” is a conditional that routes spending. A threshold set one way sends most traffic to a cheap path; set another way it floods an expensive one. The if you write is, quite literally, the shape of the bill.

Alternatives: free, open source, and commercial

Conditionals themselves are built into the language, so “alternatives” means two things: other ways to express a decision, and the free tools that keep your boolean logic honest.

Other ways to express a decision.

Free tools that check your conditions.

For a beginner the honest recommendation is short: install Ruff, run it on everything, and write a pytest assertion for each branch. Both are free, and together they catch the large majority of conditional mistakes before they reach production. Commercial IDEs (such as the paid tiers of some editors) bundle similar checks with a nicer interface, but nothing in that list costs money, and the free tools are the ones professional Python teams actually run in their pipelines.

Concept AConcept BKey difference
if/elif/else statementConditional (ternary) expressionThe statement runs blocks of code; the expression produces a single value you can assign or pass
elif chainSeparate if statementsAn elif chain runs at most one branch; separate ifs each test independently and may run several
and / or& / ``
==is== compares values; is compares identity (same object in memory). Use is only for None, True, False
Guard clauseNested ifA guard handles the exception first and returns, keeping the main path flat; nesting pushes the main path deeper with each case
match/caseif/elif laddermatch shines when dispatching on the value or structure of one subject; if/elif handles arbitrary, unrelated conditions

The pairing to burn in is == versus is. Comparing a value with if x == None: works but is discouraged; the idiom is if x is None:, because there is exactly one None object and identity is what you mean. Reserve is for None, True, and False, and use == everywhere else.

When to use it — and when not to

Reach for a conditional whenever behaviour must depend on data: validating input, handling an error, choosing a route, applying a rule only in some cases. Prefer a guard clause when you can reject or short-cut early — it keeps the happy path flat and readable. Prefer a ternary for a clean two-way value choice, and a chained comparison (0 <= x <= 1) whenever you are testing a range. Prefer match/case or dictionary dispatch when a long if/elif ladder is really just choosing among many named cases.

Know equally when not to add conditions. Every branch is a path that must be tested and maintained, so do not invent branches for inputs that cannot occur — that is dead code pretending to be caution. Do not nest three or four levels deep when a guard clause would flatten it; do not repeat the same negated condition when De Morgan’s laws would simplify it; and do not reach for a conditional when a data structure is cleaner, as when a dict lookup replaces a ten-branch ladder. And beware the two silent traps: never write if when you mean assignment (= versus ==), and never test floating-point numbers for exact equality — 0.1 + 0.2 == 0.3 is False in Python because of how decimals are stored in binary, so compare with a tolerance (abs(a - b) < 1e-9) instead. The professional habit is fewest, flattest, clearest branches that still cover every case — and a test for each one.

Knowledge check

Try these from memory before looking back:

  1. List Python’s falsy values, and explain why if items: is the idiomatic way to check that a list is non-empty.
  2. Rewrite not (verified and score >= 0.9) using De Morgan’s laws, and say why the rewritten form might read more clearly.
  3. In if user is not None and user.is_active:, explain what short-circuit evaluation prevents, and what would break if you swapped the two sides of the and.
  4. Given if score >= 0.5: ... elif score >= 0.9: ..., explain why the second branch can never run, and how to fix the order.
  5. A colleague writes if balance = 0: and gets a syntax error. Explain the bug and the one-character fix, and why Python makes this a hard error rather than a silent one.

Hands-on exercise

Time to build a decision engine. In the Day 50 lab you will complete a small Python program, triage.py, that classifies a model prediction into AUTO_ACCEPT, REVIEW, or REJECT using exactly the boolean logic from this lesson: a chained comparison to validate the input, a guard clause to reject low confidence first, the and operator with short-circuiting to admit confident-and-verified predictions, and a ternary to label the confidence band. The program reads its inputs from the command line — python3 triage.py <score> <status> — so it can be tested without a human typing anything.

Start by reading the finished reference, then rebuild it from the starter:

cd labs/sections/programming-with-python/day-050-conditionals-and-boolean-logic

# 1. See the finished decision engine on good and bad input
python3 examples/triage.py 0.95 verified
python3 examples/triage.py 0.95 unverified
python3 examples/triage.py 0.30 verified
python3 examples/triage.py 1.5 verified      # invalid: prints an error, exits non-zero

# 2. Your task: complete the five exercises in the starter, then run it
python3 starter/triage.py 0.70 verified

# 3. Check your work
bash tests/run_tests.sh

Expected output

A real captured run of the reference program:

$ python3 examples/triage.py 0.95 verified
score=0.95 verified=True  -> AUTO_ACCEPT (confidence: high)

$ python3 examples/triage.py 0.95 unverified
score=0.95 verified=False -> REVIEW (confidence: high)

$ python3 examples/triage.py 0.30 verified
score=0.30 verified=True  -> REJECT (confidence: low)

$ python3 examples/triage.py 1.5 verified   ; echo "exit: $?"
error: score 1.5 is out of range (expected 0.0 to 1.0)
usage: python3 triage.py <score> <status>   (score 0.0-1.0, status verified|unverified)
exit: 2

The classification prints to standard output; the error prints to standard error and sets exit code 2. The program is deterministic, so your numbers will match exactly.

Validate your work

You are done when you can check every box:

Troubleshooting

Common mistakes

Practice assignment

Open starter/decision-worksheet.md in the lab and design a second decision engine of your own before you code it — a spam-or-not filter, a loan pre-check, a support-ticket priority router, anything with a clear set of rules. Write three lists: the inputs (what values arrive and from where), the rules (each as a boolean condition, using and/or/not), and the outcomes (the categories, and which rule leads to each). Identify at least one guard clause (an input you reject immediately) and one place a chained comparison or ternary fits naturally. Then implement it as a small program with the same shape as triage.py: named functions, a main() that returns an exit code, the if __name__ == "__main__": guard, validation that exits non-zero on bad input, and at least three outcomes chosen by an if/elif/else. Keep the worksheet; the Week 8 project builds on exactly this pattern.

Extension challenge

Take your triage.py two steps further. First, add a De Morgan’s law simplification somewhere in the code and prove it: write the condition both ways in a comment (for example, not (verified and score >= 0.9) and its equivalent not verified or score < 0.9), and add a short test that runs the function across a grid of scores and verification flags and asserts both forms always agree. Second, make the confidence band a genuine three-way decision using a nested ternary"high" if score >= 0.9 else ("medium" if score >= 0.5 else "low") — then rewrite the same three-way choice as an if/elif/else and decide, in a comment, which you find more readable and why. Finally, add one new outcome to the engine (say, ESCALATE for a very high score that is still unverified) and update both the classification ladder and the test suite to cover it. You will have exercised every idea from today — comparisons, logical operators, short-circuiting, guard clauses, chained comparisons, the ternary, and De Morgan’s laws — in a program small enough to hold in your head, which is exactly the kind of decision code that keeps real AI pipelines debuggable.

Quiz

Q1. Which of the following is NOT one of Python's falsy values?

  1. The empty string ""
  2. The integer 0
  3. The string "0"
  4. The empty list []
Show answer

Answer: C. The string "0"

The falsy values are False, None, numeric zero (0 and 0.0), and every empty container ("", [], {}, (), set()). The string "0" is a non-empty string, so it is truthy — a classic trap when checking user input, because "0" passes an "if value:" test even though it looks like zero.

Q2. What does the expression `0.0 <= score <= 1.0` do?

  1. It is a chained comparison meaning score is between 0.0 and 1.0 inclusive, evaluating score once
  2. It is a syntax error; Python does not allow two comparisons in one expression
  3. It compares 0.0 <= score, then compares the resulting boolean to 1.0
  4. It only checks the lower bound and ignores the upper one
Show answer

Answer: A. It is a chained comparison meaning score is between 0.0 and 1.0 inclusive, evaluating score once

Python supports chained comparisons: `0.0 <= score <= 1.0` means exactly what it reads as — score is in the range 0.0 to 1.0 inclusive — and score is evaluated only once. It is more readable and less error-prone than writing `score >= 0.0 and score <= 1.0` by hand.

Q3. In the expression `user is not None and user.is_active`, why does the order of the two sides matter?

  1. It does not matter; `and` evaluates both sides regardless
  2. Python evaluates `and` from right to left, so the second side runs first
  3. The order only affects performance, never correctness
  4. Short-circuit evaluation skips `user.is_active` when the first side is False, avoiding a crash when user is None
Show answer

Answer: D. Short-circuit evaluation skips `user.is_active` when the first side is False, avoiding a crash when user is None

Because `and` short-circuits, if `user is not None` is False the whole expression is already False and `user.is_active` is never evaluated — which prevents an AttributeError when user is None. Swap the two sides and accessing `.is_active` on None would crash. Short-circuiting is a correctness tool, not just a speed trick.

Q4. What do the operators `and` and `or` actually return in Python?

  1. Always the literal value True or False
  2. One of their operands — the result is truthy or falsy but not necessarily a bool
  3. The integer 1 for true and 0 for false
  4. A new boolean object created each time
Show answer

Answer: B. One of their operands — the result is truthy or falsy but not necessarily a bool

`and` and `or` return one of their operands, not a fresh boolean. `"" or "default"` returns "default"; `"a" and "b"` returns "b". The result is still truthy or falsy, which is why it usually behaves like a boolean — and why `name or "anonymous"` is a common idiom for supplying a default.

Q5. Given `if score >= 0.5: category = "medium"` followed by `elif score >= 0.9: category = "high"`, what happens for score = 0.95?

  1. category becomes "high" because 0.95 >= 0.9
  2. Both branches run, so category ends up "high"
  3. category becomes "medium" because the first true branch wins and the elif is never reached
  4. Python raises an error about overlapping conditions
Show answer

Answer: C. category becomes "medium" because the first true branch wins and the elif is never reached

An if/elif ladder runs the block under the FIRST true condition and skips the rest. For 0.95 the first test `score >= 0.5` is already True, so category becomes "medium" and the `elif score >= 0.9` is never checked. The high branch is unreachable — a branch-ordering bug fixed by testing the most specific condition (>= 0.9) first.

Q6. Which rewrite of `not (verified and score >= 0.9)` is correct by De Morgan's laws?

  1. `not verified and score < 0.9`
  2. `not verified or score < 0.9`
  3. `verified or score >= 0.9`
  4. `not verified and score >= 0.9`
Show answer

Answer: B. `not verified or score < 0.9`

De Morgan's law says `not (A and B)` equals `not A or not B`. Here A is `verified` and B is `score >= 0.9`, so the result is `not verified or not (score >= 0.9)`, and `not (score >= 0.9)` is `score < 0.9`. The `and` becomes `or` and each part is negated.

Q7. What is the difference between the `if`/`else` statement and the conditional (ternary) expression `x if cond else y`?

  1. The statement runs blocks of code; the ternary is an expression that produces a single value you can assign or pass
  2. They are identical; the ternary is only shorter
  3. The ternary can run multiple statements in each branch
  4. The ternary evaluates both branches and returns both values
Show answer

Answer: A. The statement runs blocks of code; the ternary is an expression that produces a single value you can assign or pass

The `if`/`else` statement chooses which BLOCK of code runs; the ternary `x if cond else y` is an expression that evaluates to a single VALUE (either x or y). Use the ternary for a clean two-way value choice; use a full if when a branch does more than pick one value.

Q8. Why should you avoid testing floating-point numbers for exact equality, as in `if 0.1 + 0.2 == 0.3:`?

  1. Float equality is banned by Python and raises an error
  2. Floats are always equal to each other, so the test is meaningless
  3. The `==` operator does not work on floats at all
  4. Because decimals like 0.1 cannot be stored exactly in binary, the sum is very slightly off and the comparison is False; compare with a tolerance instead
Show answer

Answer: D. Because decimals like 0.1 cannot be stored exactly in binary, the sum is very slightly off and the comparison is False; compare with a tolerance instead

Numbers like 0.1 and 0.2 have no exact binary representation, so `0.1 + 0.2` is 0.30000000000000004, and `== 0.3` is False. Never test floats for exact equality; instead check that they are close enough, e.g. `abs(a - b) < 1e-9`.

Glossary

boolean
A value with exactly two possibilities, `True` or `False`, and Python's type (`bool`) for it. Named after George Boole, it is the smallest unit of a decision — a single yes or no — and every comparison produces one.
truthy / falsy
How Python treats any value when it is used as a condition. The falsy values are `False`, `None`, numeric zero (`0`, `0.0`), and every empty container (`""`, `[]`, `{}`, `()`, `set()`); everything else is truthy. This is why `if items:` means "if the list is non-empty."
comparison operator
An operator that compares two values and returns a boolean: `==` (equal), `!=` (not equal), `<`, `<=`, `>`, `>=`. Note that `==` compares values while `=` assigns a value to a name.
logical operator (and / or / not)
The operators that combine booleans: `A and B` is true only when both are true, `A or B` is true when at least one is, and `not A` flips true to false. In Python `and`/`or` return one of their operands, not a fresh boolean.
short-circuit evaluation
Python's lazy, left-to-right evaluation of `and`/`or` that stops as soon as the result is settled: `A and B` skips `B` when `A` is falsy, and `A or B` skips `B` when `A` is truthy. It is both a speed optimisation and a correctness tool (e.g. `x is not None and x.field`).
conditional expression (ternary)
A one-line expression, `value_if_true if condition else value_if_false`, that produces a single value based on a condition. Unlike an `if` statement (which runs blocks), the ternary yields a value you can assign or pass to a function.
branch
One of the alternative paths through a conditional — the block under an `if`, `elif`, or `else`. In an `if`/`elif`/`else` ladder the branches are mutually exclusive: the first whose condition is true runs, and the rest are skipped.
guard clause
A conditional near the top of a function that handles an exceptional case (invalid input, an early exit) immediately and returns or raises, so the main logic that follows never has to indent. Guard clauses keep decision code flat and readable instead of deeply nested.
De Morgan's laws
The rules for rewriting a negated combination of booleans: `not (A and B)` equals `not A or not B`, and `not (A or B)` equals `not A and not B`. They let you turn a confusing negated condition into an equivalent, clearer one.
chained comparison
Writing two comparisons in one expression, as in `0.0 <= score <= 1.0`, which means `score` is between 0.0 and 1.0 inclusive. Python evaluates the middle value once, making the form both readable and correct.
if / elif / else
Python's conditional statement. `if` runs a block when its condition is true; `elif` (else-if) tests another condition only when the previous ones were false; `else` catches everything remaining. Python takes the first true branch and skips the rest, so order matters.
equality vs identity (== vs is)
`==` tests whether two values are equal; `is` tests whether two names refer to the same object in memory (identity). Use `is` only for `None`, `True`, and `False` (e.g. `if x is None:`), and `==` for ordinary value comparisons.

Sources and further reading


Kept in this browser, no account needed. Your progress page turns the whole record into one link you can bookmark or open on another device.