🚀 Implementing Blackjack Basic Strategy in JavaScript

A comprehensive, developer-focused guide to representing the Basic Strategy as JSON and performing lightning-fast lookups in code.

🎯 Learn Basic Strategy — Interactive Trainer

💡
Why Basic Strategy belongs in code

The Basic Strategy is a deterministic decision table that minimizes the house edge to just 0.5%. For developers building a simulator, trainer, or AI-assisted dealer, encoding that table as structured data (JSON) and using a fast lookup function is far simpler and more maintainable than hundreds of nested conditionals.

This approach separates data from logic, making your code more readable, testable, and adaptable to different rule variations. Whether you're building a casino game, training application, or statistical analyzer, this pattern will save you time and reduce bugs.

📊
Data model: JSON lookup table

Store the strategy chart as a JSON object. Each key represents the player's hand row (hard totals, soft totals and pairs), and each value is a map from the dealer up-card to the action code:

JSON Strategy Table

// small excerpt (full table would include all rows)
const blackjackStrategy = {
  "5-8": {"2":"H","3":"H","4":"H","5":"H","6":"H","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "9": {"2":"H","3":"D","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "10": {"2":"D","3":"D","4":"D","5":"D","6":"D","7":"D","8":"D","9":"D","10":"H","A":"H"},
  "11": {"2":"D","3":"D","4":"D","5":"D","6":"D","7":"D","8":"D","9":"D","10":"D","A":"D"},
  "12": {"2":"H","3":"H","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "13": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "14": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "15": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","10":"SR","A":"H"},
  "16": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"SR","10":"SR","A":"H"},
  "A2": {"2":"H","3":"H","4":"H","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "A3": {"2":"H","3":"H","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "A4": {"2":"H","3":"H","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "A5": {"2":"H","3":"H","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "A6": {"2":"H","3":"D","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "A7": {"2":"S","3":"D/S","4":"D/S","5":"D/S","6":"D/S","7":"S","8":"S","9":"H","10":"H","A":"H"},
  "2-2": {"2":"P","3":"P","4":"P","5":"H","6":"P","7":"P","8":"H","9":"H","10":"H","A":"H"},
  "3-3": {"2":"P","3":"P","4":"P","5":"H","6":"P","7":"P","8":"H","9":"H","10":"H","A":"H"},
  "4-4": {"2":"H","3":"H","4":"H","5":"P","6":"P","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "6-6": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"H","8":"H","9":"H","10":"H","A":"H"},
  "7-7": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"P","8":"H","9":"H","10":"H","A":"H"},
  "8-8": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"P","8":"P","9":"P","10":"P","A":"P"},
  "9-9": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"S","8":"P","9":"P","10":"S","A":"S"}
};
      
H Hit
S Stand
D Double (else Hit)
P Split
SR Surrender (else Hit)
D/S Double else Stand

Lookup function: simple and fast

Use a small function to look up the recommended move by table key and dealer up-card. This returns the action code directly and is O(1) complexity.

JavaScript Lookup Function

// lookup function
function getBlackjackAction(playerHand, dealerCard, strategyTable) {
  if (!strategyTable) strategyTable = blackjackStrategy; // fallback to global if not passed
  playerHand  = String(playerHand).toUpperCase();
  dealerCard  = String(dealerCard).toUpperCase();

  const row = strategyTable[playerHand];
  if (!row) return null; // unknown row
  return row[dealerCard] || null; // action or null if unknown dealer card
}

// examples
console.log(getBlackjackAction("A7", "6"));   // "D/S"
console.log(getBlackjackAction("16", "10"));  // "SR"
console.log(getBlackjackAction("9-9", "7"));  // "S"
      

This approach keeps strategy and logic separated: the JSON is the data, the function is the lookup/adapter. It's easy to swap in rule variants (e.g., dealer hits/stands on soft 17, or different surrender rules) by loading a different JSON object.

🎯

Instant Lookups

O(1) complexity means consistent performance even with complex rule variations.

🔧

Easy Maintenance

Update strategy rules by modifying JSON data, not complex conditional logic.

🚀

Scalable Design

Perfect for simulations running thousands of hands per second.

🃏
Extending for real card inputs

For production use you might want the function to accept raw cards (e.g. ['A','6']) instead of pre-computed row keys. Implement a small normalizer that:

  • detects pairs (both ranks equal) → row like "8-8"
  • detects soft totals (contains an Ace counted as 11) → row like "A6"
  • falls back to hard totals (numeric sum) → row like "16"
Hand Normalizer Function

// example normalizer (very small)
function normalizeHand(cards) {
  // cards: array of ranks, e.g. ['A','6'] or ['9','9'] or ['10','6']
  const ranks = cards.map(c => String(c).toUpperCase());
  if (ranks.length === 2 && ranks[0] === ranks[1]) {
    // pair
    return `${ranks[0]}-${ranks[1]}`;
  }
  const hasAce = ranks.includes('A');
  const numeric = ranks.reduce((sum, r) => sum + (r === 'A' ? 11 : (r === 'J' || r === 'Q' || r === 'K' ? 10 : parseInt(r,10) || 0)), 0);
  // naive soft detection for two-card hands
  if (hasAce && ranks.length === 2) {
    const other = ranks.find(r => r !== 'A');
    return `A${other}`;
  }
  return String(numeric);
}
      

Combine the normalizer with getBlackjackAction to accept either structured keys or raw card arrays. This makes your API more flexible and user-friendly.

🏆
Why developers should use this model

📖

Clarity

Data-driven approach keeps logic readable and maintainable. No more spaghetti code with nested if-statements.

Performance

Constant-time lookup suitable for simulations that run thousands of hands per minute.

🔄

Flexibility

Swap tables for rule variants or implement training modes without touching your core logic.

🧪

Testability

Easy to unit test with different strategy tables and edge cases.

📦

Modularity

Strategy data can be loaded from files, APIs, or databases independently.

🌍

Internationalization

Support different regional blackjack variations with separate JSON files.

🎮
Resources & Next Steps

Ready to put this knowledge into practice? Try our interactive trainer to see the Basic Strategy in action:

🎯 21Count — Interactive Basic Strategy Trainer

📁 Download Complete JavaScript File with Examples

Includes the full strategy table, lookup functions, normalizer, and usage examples ready for your projects.