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.
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:
// 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":"SR"},
"16": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"SR","10":"SR","A":"SR"},
"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":"H","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":"D/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":"P","6":"P","7":"P","8":"H","9":"H","10":"H","A":"H"},
"3-3": {"2":"P","3":"P","4":"P","5":"P","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":"SP"},
"9-9": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"S","8":"P","9":"P","10":"S","A":"S"}
};
The excerpt follows the basic strategy chart for 6 decks, dealer hits soft 17, double on any two cards, double after split allowed, late surrender.
- H
- Hit
- S
- Stand
- D
- Double (else Hit)
- P
- Split
- SR
- Surrender (else Hit)
- SP
- Surrender (else Split)
- 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.
// 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"
// 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.