Control Flow: if/else, switch, and Conditional Logic
Learn control flow structures in programming: if/else statements, switch/case, pattern matching, fall-through behavior, and short-circuit evaluation for DSA.
Control Flow: if/else, switch, and Conditional Logic
Programs make decisions constantly—validating input, branching on state, choosing between algorithms. Conditional statements are the building blocks that let code do something other than run straight through from top to bottom. The main tools at your disposal are if/else, switch/case, and pattern matching.
Introduction
Control flow is what transforms a list of instructions into a program that makes decisions. Without it, code would execute top-to-bottom with no branching, no repetition, and no way to respond to different inputs. Every real program needs to evaluate conditions, choose between alternatives, and repeat blocks of work—and that’s exactly what if/else, switch/case, and pattern matching give you.
Why does this belong in an algorithms context? Most algorithms are defined by their control flow decisions. A binary search picks a half based on a comparison. A sorting algorithm decides whether to swap based on order. A graph traversal decides whether to explore a neighbor based on visited state. Getting control flow right—understanding short-circuit evaluation, knowing when if beats switch, and recognizing how pattern matching simplifies complex conditionals—is foundational to writing correct, efficient code.
When to Use if/else vs switch
Reach for if/else when:
- You have complex boolean conditions with multiple operators
- You’re checking ranges rather than discrete values
- You have 2-4 branches and the logic isn’t simple equality
- You need short-circuit behavior
Use switch/case when:
- A single variable gets compared against many discrete values
- You have 5+ simple equality checks
- A chain of if/else would get hard to read
- The compiler can optimize it with a jump table
Use pattern matching when your language supports it and you need:
- Destructuring and binding parts of a value
- Type or structure-based matching
- Guards on your matches
Comparison and Logical Operators
Comparison operators produce boolean values:
| Operator | Meaning |
|---|---|
== | Equal to |
!= | Not equal to |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
Logical operators combine booleans:
| Operator | Behavior |
|---|---|
&& | Logical AND - true only if both operands are true |
|| | Logical OR - true if at least one operand is true |
! | Logical NOT - inverts the boolean value |
Short-Circuit Evaluation
A && B never evaluates B if A is false. A || B never evaluates B if A is true. This matters for both performance and safety:
// B is never called if users is null (avoids NullPointerException)
if (users != null && users.length > 0) {
processUsers(users);
}
// B is never called if hasPermission is true (security check)
if (hasPermission && verifyOwnership(resource)) {
deleteResource(resource);
}
if/else Statements
The if/else is the most basic control flow construct:
function classifyTemperature(temp) {
if (temp < 0) {
return "freezing";
} else if (temp < 10) {
return "cold";
} else if (temp < 20) {
return "moderate";
} else if (temp < 30) {
return "warm";
} else {
return "hot";
}
}
Nested if/else
Deep nesting gets hard to read. Early returns or extracted functions help:
// Deeply nested (hard to read)
function processOrder(order) {
if (order != null) {
if (order.items != null) {
if (order.items.length > 0) {
if (validatePayment(order)) {
// process...
}
}
}
}
}
// Early return pattern (cleaner)
function processOrder(order) {
if (order == null) return { error: "Order is null" };
if (order.items == null) return { error: "No items" };
if (order.items.length === 0) return { error: "Empty order" };
if (!validatePayment(order)) return { error: "Invalid payment" };
// process...
}
switch/case Statements
Switch statements branch on discrete values efficiently:
function getDayType(day) {
switch (day) {
case "Saturday":
case "Sunday":
return "weekend";
case "Monday":
case "Tuesday":
case "Wednesday":
case "Thursday":
case "Friday":
return "weekday";
default:
return "invalid";
}
}
Fall-Through Behavior
In languages like C and Java, switch cases fall through unless you use break:
// C/Java example - intentional fall-through
switch (day) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
printf("Vowel\n");
break; // Without break, execution continues to next case
case 'y':
printf("Sometimes vowel\n");
break;
default:
printf("Consonant\n");
}
Most modern languages (JavaScript, Python, Rust) do not allow fall-through by default, or require explicit fallthrough keywords.
Branching Logic Flowchart
flowchart TD
A[Start] --> B{Condition evaluated}
B -->|if/else| C{Test condition 1}
C -->|true| D[Execute branch 1]
C -->|false| E{Test condition 2}
E -->|true| F[Execute branch 2]
E -->|false| G[Execute else/default]
B -->|switch| H{Value matches case?}
H -->|case 1| I[Execute case 1]
I --> J{break present?}
J -->|yes| K[Exit switch]
J -->|no| L[Fall through to next case]
H -->|case 2| M[Execute case 2]
M --> K
H -->|default| N[Execute default]
N --> K
D --> O[Continue]
G --> O
K --> O
N --> O
L --> M
Switch Expression (Modern Syntax)
Languages like Java, C#, and Rust support switch as an expression returning values:
// Java switch expression (Java 14+)
int days = switch (month) {
case "Jan", "Mar", "May", "Jul", "Aug", "Oct", "Dec" -> 31;
case "Apr", "Jun", "Sep", "Nov" -> 30;
case "Feb" -> 28;
default -> -1;
};
Pattern Matching
Pattern matching takes switch statements further with more expressive power:
# Python match statement (3.10+)
def http_status(status):
match status:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Server Error"
case int() if status < 0:
return "Invalid negative status"
case _:
return "Unknown status"
// Rust pattern matching
match value {
Some(x) if x > 0 => println!("Positive: {}", x),
Some(x) => println!("Non-positive: {}", x),
None => println!("No value"),
}
Guard Clauses
Pattern matching often supports guards—additional boolean conditions:
// TypeScript with type guards
function describe(value: string | number | null) {
if (value === null) {
return "null";
} else if (typeof value === "string" && value.length > 10) {
return "Long string";
} else if (typeof value === "number" && value > 0) {
return "Positive number";
}
}
Control Flow in DSA Context
You’ll find control flow in every algorithm. Here are some applications:
Decision Points in Graph Traversal
// BFS with level tracking - control flow determines when to change levels
function bfsLevels(graph, start) {
const visited = new Set();
const queue = [[start, 0]];
visited.add(start);
while (queue.length > 0) {
const [node, level] = queue.shift();
if (!visited.has(node)) {
visited.add(node);
processNode(node, level);
}
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
queue.push([neighbor, level + 1]);
}
}
}
}
Conditional State Transitions in State Machines
// Simplified state machine for string parsing
function matchPattern(input) {
let state = "start";
for (const char of input) {
switch (state) {
case "start":
state = char === "a" ? "foundA" : "start";
break;
case "foundA":
state = char === "b" ? "foundB" : "start";
break;
case "foundB":
state = char === "c" ? "success" : "start";
break;
}
}
return state === "success";
}
Conditional Logic in Sorting
// Quick sort with conditional partitioning
function quicksort(arr, low, high) {
if (low < high) {
const pivotIndex = partition(arr, low, high);
// Control flow determines recursive branches
quicksort(arr, low, pivotIndex - 1);
quicksort(arr, pivotIndex + 1, high);
}
}
function partition(arr, low, high) {
const pivot = arr[high];
let i = low - 1;
for (let j = low; j < high; j++) {
if (arr[j] <= pivot) {
// Conditional swap decision
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
return i + 1;
}
When NOT to Use Control Flow Statements
Sometimes you should avoid control flow statements:
- Polymorphism over conditionals: When you have many cases based on type, consider using a map/dictionary of functions or inheritance
- Lookup tables over switch: When mapping discrete inputs to outputs, an array or hash map is often faster
- Early returns over deep nesting: Restructure code to handle error conditions first
// Switch to lookup table transformation
// Instead of:
function getColor(code) {
switch (code) {
case "r":
return "red";
case "g":
return "green";
case "b":
return "blue";
default:
return "unknown";
}
}
// Use:
const colorMap = { r: "red", g: "green", b: "blue" };
const getColor = (code) => colorMap[code] ?? "unknown";
Production Failure Scenarios and Mitigations
Reference: Bug: Assignment Instead of Comparison
Reference: Bug: Accidental Fall-Through
Bug: Assignment Instead of Comparison
Problem: Using = instead of == in conditions (assignment instead of comparison).
// Bug: assigns instead of comparing
if ((userRole = "admin")) {
// Always true, userRole is now "admin"
grantAccess();
}
Mitigation: Use strict equality === which does not allow implicit coercion, and enable linters (ESLint eqeqeq rule).
Bug: Switch Without Default
Problem: Missing default case causes silent failures when unexpected values appear.
// Bug: no default handling
switch (status) {
case "active":
return "User is active";
case "inactive":
return "User is inactive";
// If status is 'suspended' or 'pending', returns undefined silently
}
Mitigation: Always include a default case that handles unknown values, even if it throws an error or logs a warning.
Bug: Short-Circuit with Side Effects
Problem: Relying on short-circuit evaluation for control flow that has side effects can cause subtle bugs.
// Bug: createObject might not be called when condition is true
if (isValid && createObject()) {
saveToDatabase();
}
Mitigation: Keep conditions focused on boolean logic; extract side-effecting calls outside of conditions when possible.
Bug: Floating-Point Comparison
Problem: Comparing floating-point numbers with == fails due to precision issues.
// Bug: fails due to floating-point precision
if (result == 0.1 + 0.2) {
// 0.1 + 0.2 = 0.30000000000000004
// This branch is rarely taken
}
Mitigation: Use epsilon-based comparisons: Math.abs(result - (0.1 + 0.2)) < 0.0001
Bug: Accidental Fall-Through
Problem: Accidental fall-through in languages that require explicit break statements.
Mitigation: Use modern languages with no implicit fall-through, or be explicit with fallthrough keywords.
Failure: Fall-through bugs in switch statements
Missing break causes execution to continue into the wrong case — wrong state transitions, corrupted data. In C and Java this is the default behavior and it’s cost plenty of engineers a weekend.
Fix: always break or return at the end of each case unless fall-through is intentional. GCC/Clang’s -Wimplicit-fallthrough catches most of these. Some teams add a linter rule too.
Failure: Timing attacks on secret-dependent conditionals
When branching depends on secret values, attackers can infer correctness character-by-character from how long comparisons take. This bypasses account lockouts entirely.
Use constant-time comparison instead: crypto.timingSafeEqual() in Node, hmac.compare_digest() in Python, MessageDigest.isEqual() in Java. These check every byte regardless of where the mismatch occurs.
Failure: Branch prediction thrash in hot paths
When if/else chains in tight loops have unpredictable branch outcomes, CPU pipelines flush constantly. I’ve seen this drop throughput by 10-50x on real workloads processing large datasets.
Order conditions by probability (most likely first). Use lookup tables for dense domains. Profile with hardware performance counters to find the mispredicted branches — __builtin_expect in GCC lets you hint at likely paths.
Trade-Off Table
| Aspect | if/else | switch/case | Lookup Table |
|---|---|---|---|
| Readability | Good for 2-4 branches | Good for 5+ branches | Excellent for static mappings |
| Performance | O(1) per condition | O(1) with jump tables | O(1) direct access |
| Flexibility | Complex boolean logic | Simple equality only | Key-value pairs only |
| Maintainability | Degrades with depth | Good for flat structures | Easy to extend |
| Compiler Optimization | Sequential evaluation | Jump table possible | Direct memory access |
Observability Checklist
When debugging control flow issues in production:
- Log branch taken (if/else outcome) at
DEBUGlevel with correlation ID - Measure condition evaluation latency in hot paths
- Alert on unexpected case hit rates in switch statements
- Trace nested conditional depth for performance profiling
Security and Compliance Notes
Security Pattern Reference
Input Validation
- Always validate and sanitize inputs before using them in switch statements or equality comparisons
- Use allowlists over denylists for switch cases when possible
Timing Attacks
- Avoid branch-based behavior that leaks information through timing
- Use constant-time comparisons for secrets:
// Vulnerable to timing attacks
function checkPassword(input, stored) {
return input === stored; // Returns early on first mismatch
}
// Constant-time comparison
function secureCompare(a, b) {
if (a.length !== b.length) return false;
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return result === 0;
}
Audit Trails
- Log significant conditional decisions, especially those affecting access control or financial transactions
- Include who made the decision, what data was evaluated, and what action was taken
Constant-Time Comparisons for Secrets
Use constant-time comparison for anything secret — passwords, tokens, API keys, session IDs. The plain === operator returns on the first mismatch, and attackers can measure exactly how long each comparison takes. crypto.timingSafeEqual() in Node, hmac.compare_digest() in Python, MessageDigest.isEqual() in Java check every byte, so timing reveals nothing.
Allowlist Enforcement for Switch Cases
Validate switch case values against allowlists before using them in security-sensitive paths. Reject unknown values instead of falling through to a default that may be exploitable.
Compiler Optimization Guard
- Disable compiler optimizations that could reintroduce timing side channels in security-critical conditionals (e.g.,
-fno-branch-probabilitiesin GCC for sensitive paths) - Test constant-time implementations with timing analysis tools to verify no branches depend on secret data
Common Pitfalls / Anti-Patterns
- Magic Numbers: Literal values in conditions without named constants
// Bad
if (user.age > 18) { ... }
// Good
const LEGAL_AGE = 18;
if (user.age > LEGAL_AGE) { ... }
- Overly Complex Conditions: Squeezing too much logic into single conditions
// Hard to read
if ((a && b) || (c && !d) || (!e && f)) { ... }
// Better: extract into named functions
const isEligible = (a && b) || (c && !d) || (!e && f);
if (isEligible) { ... }
-
Missing Break Statements: Classic bug in C/Java switches
-
Boolean Equality Redundancy
// Redundant
if (isValid === true) { ... }
// Correct
if (isValid) { ... }
- Using if instead of switch for many equality checks
// Hard to maintain
if (status === "pending") return "Pending";
if (status === "approved") return "Approved";
if (status === "rejected") return "Rejected";
if (status === "cancelled") return "Cancelled";
// ... many more
// Better
return statusLabels[status] ?? "Unknown";
Quick Recap Checklist
- Use
if/elsefor complex boolean logic and 2-4 branches - Use
switch/casefor 5+ simple equality checks on discrete values - Use lookup tables/maps for static key-value mappings
- Prefer early returns to reduce nesting
- Use strict equality (
===) over loose equality (==) - Always include default cases in switch statements
- Be explicit about fall-through behavior
- Avoid side effects in short-circuit evaluations
- Use named constants instead of magic numbers
- Log decision points for observability
- Consider timing attacks when comparing secrets
Interview Questions
Short-circuit evaluation stops boolean expression evaluation as soon as the result is determined. For A && B, if A is false, B is never evaluated. For A || B, if A is true, B is never evaluated.
This matters for two reasons: performance (unnecessary computations are skipped) and safety (you can prevent errors by checking conditions before side-effecting operations). For example, if (list != null && list.size() > 0) avoids a NullPointerException by checking the nullity before accessing the list.
The === operator (strict equality) checks both value and type without type coercion. The == operator (loose equality) performs type coercion before comparison.
Use === in almost all cases because it is predictable and prevents subtle bugs from unexpected coercion. For example, '' == 0 is true due to coercion, but '' === 0 is false.
The only times == might be acceptable is when checking for both null and undefined in one check: if (value == null) catches both null and undefined, though this is often considered a code smell.
Fall-through occurs when execution in a switch case continues into the next case after the current case completes, because the break statement is missing. In C and Java, this is the default behavior.
To prevent accidental fall-through: use explicit break statements at the end of each case, or use modern languages that disallow implicit fall-through by default. In Java, you can use the --enable-preview flag with Java 12+ switch expressions where fall-through requires an explicit yield keyword.
Prefer if/else when dealing with complex boolean conditions that cannot be reduced to simple equality checks, when evaluating ranges (if (score >= 90 && score <= 100)), or when you have fewer than 5 branches.
Use switch when you have 5+ discrete values to compare against a single variable. A lookup table or map is even better if the mapping is static, as it provides O(1) access without any branching overhead.
Cyclomatic complexity measures the number of linearly independent paths through a function's control flow graph. It is calculated as: M = E - N + 2P where E is edges, N is nodes, and P is connected components.
Alternatively, you can count: 1 (base path) + the number of decision points (if, while, for, switch, catch). Each decision point adds a new path.
High cyclomatic complexity indicates functions that are difficult to test and maintain. Aim for complexity under 10. High complexity often signals that a function does too much and should be refactored into smaller functions with clearer control flow.
Further Reading
Recommended Books
- “Code Complete” by Steve McConnell — Chapters on control flow, conditionals, and reducing complexity
- “Clean Code” by Robert C. Martin — Chapter 3 (Functions) covers early returns, guard clauses, and avoiding deep nesting
- “The Art of Computer Programming” by Donald Knuth — Volume 1 covers control flow structures and structured programming in depth
- “Refactoring” by Martin Fowler — Catalog includes techniques for simplifying conditional logic and replacing conditionals with polymorphism
Online Resources
- MDN Web Docs: Control flow and error handling — JavaScript guide on if/else, switch, try/catch
- Wikipedia: Cyclomatic complexity — Theory and calculation of code complexity metrics
- Wikipedia: Branch predictor — How modern CPUs optimize control flow execution
- Wikipedia: Short-circuit evaluation — Formal definition and common usage patterns
Language-Specific References
- Java 14+ Switch Expressions (JEP 361) — Expanded switch with arrow syntax, pattern matching preview
- Python Structural Pattern Matching (PEP 634-636) — Match statement syntax, guards, and sequence matching
- Rust Pattern Matching — Rust Book Chapter 6: exhaustive matching, binding, and refutability
- C++17 if constexpr — Compile-time conditional branching in templates and constexpr contexts
- JavaScript Switch (MDN) — Compatibility notes, fall-through behavior, and best practices
Related DSA Topics
- State Machines — Mathematical models of computation that rely on conditional transitions between states
- Decision Trees — Supervised learning models built on hierarchical if/else decisions
- Branch and Bound — Algorithm design paradigm using conditional pruning to reduce search space
- Binary Search — Classic divide-and-conquer that uses conditional comparison at each step
The ternary operator (condition ? exprIfTrue : exprIfFalse) is a concise way to express simple conditional assignments in a single expression. It returns a value, making it suitable for inline use.
Use it when: the condition is simple and readable, both branches are short expressions (not statements), and the result is assigned to a variable or used in an expression. Avoid nesting ternary operators—nested ternaries harm readability. For complex logic or side effects, prefer if/else blocks.
Pattern matching extends switch-like constructs with destructuring, type checking, guards, and recursive patterns. Traditional switch handles only simple equality against discrete values.
Advantages include: matching against shapes of data rather than just values, binding matched sub-patterns to variables, combining with guards for extra conditions, and compiler exhaustiveness checking (ensuring all cases are handled). Rust's match, Python's match (3.10+), and Scala's pattern matching are prominent examples.
A lookup table (map, dictionary, or array) maps keys to values for O(1) direct access, replacing chains of if/else or switch statements. It is preferred when: the mapping is static and known at compile/init time, there are many cases (10+), and the logic reduces to pure key-value translation.
Example: replacing a 20-case switch for HTTP status codes with a dictionary lookup. Trade-offs: lookup tables cannot include complex conditions, range checks, or side effects; they trade branching for memory.
Modern CPUs prefetch instructions along predicted branch paths. When branch prediction succeeds, the pipeline continues without stalls. When it fails (misprediction), the pipeline flushes, causing a ~10-20 cycle penalty.
In tight loops with unpredictable conditions (e.g., random data), mispredictions can reduce throughput by 10-50x. Mitigations: order conditions by probability (most likely first), use lookup tables for dense domains, use branch hints like GCC's __builtin_expect, or redesign the algorithm to minimize unpredictable branches.
Arrow code refers to deeply nested if/else blocks that form a triangular or arrow-like shape from successive indentation levels. It makes code difficult to read, test, and maintain.
Refactoring techniques include: early returns to flatten the structure, extracting nested logic into separate named functions, using guard clauses to handle error/precondition cases first, replacing conditionals with polymorphism, and using the strategy pattern for variant behavior.
De Morgan's laws state: !(A && B) = !A || !B and !(A || B) = !A && !B. They transform negated compound conditions into equivalent forms that are often more readable.
Example: if (!(isLoggedIn && hasPermission)) becomes if (!isLoggedIn || !hasPermission). The second form is clearer—it reads as "deny if not logged in OR lacks permission." Apply these laws to simplify negative conditions in guard clauses and validation checks.
Refactoring strategies (in order of preference):
- Early returns — Handle error/invalid conditions first and return immediately
- Extract method — Move each nested level into a well-named function
- Guard clauses — Check preconditions at the top and exit early
- Polymorphism — Replace type-checking conditionals with polymorphic dispatch
- State/Strategy pattern — Replace conditional transitions with encapsulated state objects
After refactoring, the goal is no more than 2-3 levels of nesting, with each function having a single clear responsibility.
Switch expressions return a value and can be used on the right-hand side of assignments, while switch statements are imperative control flow that executes blocks of code. Key differences:
- Return value: Expressions yield a result; statements do not
- Fall-through: Expressions typically don't fall through (use arrow syntax or explicit yield)
- Exhaustiveness: Compilers often enforce that all cases are covered in expressions
- Scope: Expression cases have their own scope, avoiding variable leakage
Java 14+, C# 8.0+, and Rust's match expressions are examples.
Each decision point (if, switch, while, for) doubles or multiplies the number of paths through code. High cyclomatic complexity means more test cases are needed to achieve branch coverage.
A function with 10 decision points has up to 2^10 = 1024 theoretical paths. In practice, not all paths are reachable, but high complexity still indicates testing difficulty. Keep per-function complexity under 10 (McConnell's recommendation). Use tools like linters with complexity thresholds and write tests that cover each branch to maintain confidence in code correctness.
Strategies for clean null handling:
- Optional chaining —
user?.profile?.address?.cityshort-circuits on null - Nullish coalescing —
value ?? defaultValueprovides fallback only for null/undefined - Guard clauses —
if (user == null) return defaultUser;at the top of functions - Maybe/Option types — In functional languages, use monadic chaining (e.g., Rust's
Option, Haskell'sMaybe) - Null object pattern — Return a no-op object instead of null
Short-circuit evaluation with && and || can replace simple if statements where the condition controls whether an expression is evaluated. For example, isLoggedIn && renderDashboard() calls renderDashboard only when isLoggedIn is true, acting as a compact if statement.
This pattern is common in React (JSX conditional rendering) and Ruby/Rails. However, it can reduce readability when overused, especially with side effects. Reserve it for simple, idiomatic cases and prefer explicit if statements for complex logic.
Guard clauses are conditional checks at the beginning of a function that handle edge cases or invalid states by returning early. They are runtime control flow constructs that affect the normal execution path.
Assertions (assert) are debugging aids that check invariants and crash/throw if violated. They are typically disabled in production builds and do not control business logic. Use guard clauses for expected error conditions and input validation; use assertions for internal invariants that should never be false in correct code.
Use if/else chains when: the number of variants is small and unlikely to grow, the conditions are complex or overlapping, or you're dealing with primitive types that don't support polymorphism.
Use polymorphism when: the same condition appears in multiple places (violating DRY), new variants are expected to be added, or the behavior for each variant involves multiple methods. The Open/Closed Principle favors polymorphism — you add new behavior by creating a new class, not by modifying existing conditionals.
The refactoring replaces a chain of if/else or switch statements that map discrete inputs to outputs with a lookup map/dictionary. Example: replacing if (code === 200) return "OK"; else if (code === 404) return "Not Found"; with httpStatusMap[code] ?? "Unknown".
Trade-offs: maps are more performant for many cases (O(1) vs O(n)), easier to extend (add a key-value pair), and configurable at runtime. However, maps cannot handle range conditions, complex boolean logic, or multi-condition evaluations. They also use more memory for the data structure.
Compilers can optimize switch statements using jump tables (for dense, small-integer ranges), binary search trees (for sparse integer ranges), or hash tables (for string values). These provide O(1) or O(log n) dispatch rather than O(n) sequential comparison.
If/else chains are typically compiled as sequential conditional branches, though some compilers can transform an if/else chain of simple equality checks into a switch-like dispatch. The key insight: switch tells the compiler the branches are mutually exclusive comparisons on a single value, enabling optimizations that if/else chains don't express as clearly.
Conclusion
Control flow structures—if/else, switch/case, and pattern matching—direct how programs make decisions. Use if/else for complex boolean logic and ranges; use switch for 5+ discrete equality checks; use lookup tables/maps for static mappings. Short-circuit evaluation (A && B skips B if A is false; A || B skips B if A is true) enables safe conditions like null checks before property access. Prefer early returns to reduce nesting, strict equality (===) to avoid type coercion bugs, and named constants over magic numbers. Cyclomatic complexity counts independent execution paths—keep it under 10 to maintain testable, readable code. For security-sensitive comparisons like passwords, use constant-time comparisons to prevent timing attacks.
Category
Tags
Related Posts
Loops: For, While, and Do-While Explained
Master iteration constructs in programming: for loops, while loops, do-while loops, termination conditions, off-by-one errors, infinite loops, nested loops, and loop invariants.
Variables & Data Types: The Building Blocks of Programming
Master the fundamentals of variables, primitive and composite data types, type systems, casting, overflow, and precision issues that every developer needs to understand.
Complexity Justification: Proving Algorithm Correctness
Learn to rigorously justify and communicate algorithm complexity — deriving time and space bounds, proving correctness, and presenting analysis in interviews.