I01 β Core CS & Algorithmic Rigor (DSA & System Logic)
π― Why this matters for Infosys: the online round (HackerRank / InfyTQ) is pattern-recognition under time pressure β one clever math observation often replaces a whole loop. The interview round is narration: they want to hear brute force β why it's slow β the optimisation β the complexity, out loud, before you type. Every subsection here arms both: the code to write and the sentence to say.
In this module β 5 sections
- π§ One-screen mental model
- Matrix & math trap β the Bulb Switcher
- Dynamic programming on strings β Edit Distance
- Longest Palindromic Substring
- Arrays & strings β two-pointer, sliding window, immutability
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
HOW INFOSYS GRADES A CODING ANSWER
CLARIFY β restate the problem, ask about size, nulls, sorted?
BRUTE β state the naive O(n^2) so you always have a working answer
OPTIMISE β name the trick + the new time/space, THEN code it
NARRATE β talk every line; dry-run one normal + one edge case
A correct SILENT answer scores lower than a narrated near-miss.
They are hiring someone who reasons out loud on a team.
Matrix & math trap β the Bulb Switcher
Scenario: n bulbs start OFF. On pass i you toggle every bulb at a multiple of i (pass 1 toggles all, pass 2 toggles 2,4,6β¦). After n passes, how many bulbs are ON?
Answer:
- Brute force = two nested loops toggling a boolean array β O(nΒ²) time. Correct, but it's the answer that fails you β they want the insight.
- The insight: bulb
kis toggled once per divisor ofk. Divisors come in pairs (dandk/d), so the count is even β bulb ends OFFβ¦ - β¦except perfect squares, where one divisor is unpaired (
d == k/d) β odd count β bulb ends ON. - The O(1) realisation: the answer is simply the number of perfect squares β€
n=floor(sqrt(n)).
import math
def bulbs(n):
return int(math.isqrt(n)) # n=100 -> 10 (1,4,9,...,100)
int bulbs(int n){ return (int) Math.floor(Math.sqrt((double) n)); }
π§ Memory map: Toggles = divisor count; divisors pair up except in perfect squares. Hook: "Only perfect squares survive β βn."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Why exactly do only perfect squares end ON? β Every divisor
dpairs withk/d, giving an even number of toggles; only whend == k/d(a perfect square) is one divisor unpaired, making the toggle count odd. - β³β³ Deepest: Generalise it β after
npasses, which bulbs are toggled an odd number of times, and how would you list them in O(βn)? β The perfect squares 1,4,9,β¦; iterateifrom 1 whilei*i <= nand emiti*iβ O(βn) to enumerate, O(1) to just count.
Dynamic programming on strings β Edit Distance
Scenario: Minimum single-character inserts, deletes, or substitutions to turn string a (len m) into b (len n). (Levenshtein distance.)
Answer:
- State:
dp[i][j]= edit distance between the firstichars ofaand firstjofb. - Transition: if
a[i-1] == b[j-1]β carrydp[i-1][j-1](no cost); else1 + minof deletedp[i-1][j], insertdp[i][j-1], substitutedp[i-1][j-1]. - Base cases:
dp[i][0] = i(delete all),dp[0][j] = j(insert all). - Complexity: O(mΒ·n) time; space O(mΒ·n) naive, reducible to O(min(m,n)) with two rolling rows.
"" r o s
"" 0 1 2 3
h 1 1 2 3
o 2 2 1 2
r 3 2 2 2
s 4 3 3 2
e 5 4 4 3 <-- horse -> ros = 3
def edit_distance(a, b):
m, n = len(a), len(b)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1): dp[i][0] = i
for j in range(n+1): dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
if a[i-1] == b[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
return dp[m][n]
π§ Memory map: Match β carry the diagonal; differ β 1 + min(up, left, diagonal). Hook: "Same? steal the diagonal. Differ? 1 + cheapest neighbour."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: How do you cut the space from O(mΒ·n) to O(n)? β Each cell needs only the previous row and the current row so far; keep two 1-D arrays (
prev,cur) and swap them each outer iteration. - β³β³ Deepest: If insertion and deletion cost 1 but substitution costs 2, what changes β and what problem does that reduce to? β Substitution is never worth it (delete+insert = 2 = same), so it becomes the Longest Common Subsequence distance:
edits = m + n β 2Β·LCS(a,b).
Longest Palindromic Substring
Scenario: Return the longest contiguous substring of s that reads the same both ways.
Answer:
- Expand around centre (preferred): a palindrome mirrors around a centre; there are
2nβ1centres (each char, and each gap). Expand outward while characters match. - Complexity: O(nΒ²) time, O(1) space β the version they want written live.
- DP alternative:
dp[i][j] = trueifs[i..j]is a palindrome;dp[i][j] = (s[i]==s[j]) && (jβi<2 || dp[i+1][jβ1]). O(nΒ²) time and O(nΒ²) space β mention it, don't lead with it. - Name Manacher's (O(n)) only as "if n is very large" β signalling range without over-engineering.
def longest_palindrome(s):
if not s: return ""
start = end = 0
def expand(l, r):
while l >= 0 and r < len(s) and s[l] == s[r]:
l -= 1; r += 1
return l + 1, r - 1
for i in range(len(s)):
for l, r in (expand(i, i), expand(i, i + 1)): # odd + even centres
if r - l > end - start:
start, end = l, r
return s[start:end + 1]
π§ Memory map: Every palindrome has a centre; try all 2nβ1 centres and grow outward. Hook: "Grow from the middle, both parities."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Why must you expand from two kinds of centre? β Odd-length palindromes centre on a character (
i,i); even-length ones centre on the gap between two characters (i,i+1). Missing the gap centres misses all even-length answers. - β³β³ Deepest: Longest palindromic substring vs subsequence β different answers and different algorithms? β Substring must be contiguous (expand-around-centre / Manacher). Subsequence can skip characters and is a DP:
LPS(s) = LCS(s, reverse(s)), O(nΒ²).
Arrays & strings β two-pointer, sliding window, immutability
Scenario: The manipulation techniques Infosys screens lean on, and the string gotcha they love.
Answer:
- Two-pointer: two indices converging (or one chasing) to avoid a nested loop. Canonical: pair-sum in a sorted array in O(n) β move
iin when the sum is too small,jin when too big. - Sliding window: a moving contiguous range with running state β turns many O(nΒ²) substring/subarray problems into O(n). Canonical: longest substring without repeating characters.
- String immutability: Java / Python / C# strings are immutable β every "edit" allocates a new object, so concatenating in a loop is O(nΒ²). Use
StringBuilder(Java) or"".join(list)(Python). - In-place means mutate the input at O(1) extra space β reverse via the two-pointer swap; rotate-by-k = reverse-whole then reverse-two-parts.
def two_sum_sorted(a, target): # O(n), O(1)
i, j = 0, len(a) - 1
while i < j:
s = a[i] + a[j]
if s == target: return (i, j)
i, j = (i + 1, j) if s < target else (i, j - 1)
return None
def longest_unique(s): # sliding window, O(n)
seen, start, best = {}, 0, 0
for i, c in enumerate(s):
if c in seen and seen[c] >= start:
start = seen[c] + 1
seen[c] = i
best = max(best, i - start + 1)
return best
π§ Memory map: Sorted or converging β two pointers; contiguous window with a running constraint β sliding window; "why is my string loop slow?" β immutability. Hook: "Sorted=pointers, window=substring, strings=immutable."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Interviewer says "swap two characters in a Java
Stringin place." What's the catch? β You can't βStringis immutable. Convert tochar[], swap, thennew String(arr). Naming the constraint is the answer they're after. - β³β³ Deepest: Two-pointer needs sorted data but sorting is O(n log n) β when is the hash-map O(n) two-sum better, and what's the space trade? β Unsorted input where you can't afford to sort, or you need original indices: a hash map gives O(n) time at O(n) space; two-pointer is O(1) space but needs the array sorted first.
I02 β Object-Oriented Programming & Memory Management
π― Why this matters for Infosys: OOP and memory are the L1/L2 fundamentals round β they're testing whether you understand why, not whether you can recite four words. The winning move on every question is the crisp distinction (abstraction vs encapsulation, overloading vs overriding, stack vs heap) plus a one-line code example. Say the distinction, show the snippet, name the trap.
In this module β 5 sections
- π§ One-screen mental model
- The four pillars β abstraction vs encapsulation
- Polymorphism β overloading vs overriding
- Abstract class vs Interface (and the diamond)
- Memory β stack, heap, GC, and vtables
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
THE FOUR DISTINCTIONS THEY PROBE
Abstraction vs Encapsulation -> design (hide complexity) vs
implementation (hide data)
Overloading vs Overriding -> compile-time (declared type) vs
run-time (actual object)
Abstract cls vs Interface -> "is-a" + shared state vs
"can-do" capability, multiple
Stack vs Heap -> frames/locals/refs vs
every `new` object, GC-managed
The four pillars β abstraction vs encapsulation
Scenario: "Explain the OOP pillars β and the difference between abstraction and encapsulation." The trap is treating the last two as the same thing.
Answer:
- Abstraction β expose what an object does, hide how. A design concern: interfaces, contracts, hiding complexity.
- Encapsulation β bundle data + methods and restrict direct access via private fields + getters/setters. An implementation concern: hiding data and protecting invariants.
- Inheritance β a subclass reuses/extends a superclass ("is-a"); prefer composition when it's really "has-a".
- Polymorphism β one interface, many implementations (next subsection).
- The one-liner: "Abstraction hides complexity; encapsulation hides data. One is design, one is implementation."
class Account {
private double balance; // encapsulated state
public void deposit(double a){ if(a>0) balance += a; } // guarded invariant
public double getBalance(){ return balance; }
}
π§ Memory map: Abstraction = the steering wheel (what); encapsulation = the sealed engine (data protected). Hook: "Abstraction = design/what, Encapsulation = data/how."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Give a case where you have encapsulation but not abstraction. β A private field with a public getter/setter and no higher-level contract: state is protected (encapsulated) but callers still see the raw data model β no complexity is abstracted away.
- β³β³ Deepest: "Prefer composition over inheritance" β why, with a concrete failure of inheritance? β Inheritance couples you to the parent's implementation and breaks the Liskov substitution rule when the "is-a" is false (e.g.
Stack extends VectorexposesinsertAt, letting callers violate LIFO). Composition exposes only what you delegate.
Polymorphism β overloading vs overriding
Scenario: "Difference between compile-time and run-time polymorphism?" A favourite output-prediction trap hides here.
Answer:
- Overloading = compile-time (static / early binding). Same method name, different parameter list; the compiler picks the version from the declared argument types/count.
- Overriding = run-time (dynamic / late binding). A subclass redefines a superclass method with the same signature; the JVM picks the version from the actual object at runtime (via the vtable/method table).
- The trap: overloading is resolved by the declared (static) type; overriding by the actual (runtime) type. That single sentence answers most "what does this print?" puzzles.
class Animal { String speak(){ return "..."; } }
class Dog extends Animal { @Override String speak(){ return "Woof"; } }
Animal a = new Dog();
a.speak(); // "Woof" -> overriding, runtime type wins
// add(int,int) vs add(double,double) -> overloading, compiler decides
π§ Memory map: OverLOADING = compiler + parameter list; overRIDING = runtime + actual object. Hook: "Load at compile, Ride at runtime."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Can you override a
staticorprivatemethod? β No.staticmethods are hidden, not overridden (resolved by declared type);privatemethods aren't inherited so aren't polymorphic. Only instance methods with sufficient visibility are virtual in Java. - β³β³ Deepest:
Animal a = new Dog();β ifAnimalhas an overloadedfeed(Animal)andfeed(Dog), and you calla.feed(a), which runs and why? βfeed(Animal)β overloads are chosen at compile time by the declared type ofa(Animal), even though the object is aDog. Overload resolution never uses the runtime type.
Abstract class vs Interface (and the diamond)
Scenario: "When would you use an abstract class over an interface?" Plus multiple-inheritance resolution.
Answer:
- Abstract class β can hold instance state, constructors, and concrete methods; a class extends only one. Use for "is-a" with shared code/state (e.g.
AbstractList). - Interface β a capability/contract; only
public static finalconstants, but a class can implement many. Java 8+ addsdefaultandstaticmethods, Java 9+privatehelpers. Use for "can-do" shared by unrelated types (Comparable,Runnable). - Default rule: program to interfaces; reach for an abstract class only when there's genuine shared state or partial implementation.
- Diamond problem: Java bans two-class inheritance to avoid ambiguous state; two interfaces with the same
defaultmethod force you to disambiguate explicitly.
interface A { default String hi(){ return "A"; } }
interface B { default String hi(){ return "B"; } }
class C implements A, B {
@Override public String hi(){ return A.super.hi(); } // MUST resolve, else compile error
}
π§ Memory map: Abstract = is-a + state + one parent; Interface = can-do + no state + many. Hook: "Class shares code, Interface shares capability."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Since Java 8 interfaces have method bodies, why keep abstract classes at all? β State. Interfaces still can't hold instance fields or constructors β if implementations must share mutable state or construction logic, only an abstract class can carry it.
- β³β³ Deepest: How does C++ resolve the diamond differently from Java? β C++ uses virtual inheritance (
class D : virtual public Base) so only one sharedBasesubobject exists; Java sidesteps it by forbidding multiple class inheritance and forcing explicitX.super.method()resolution for conflicting interface defaults.
Memory β stack, heap, GC, and vtables
Scenario: "Where do variables live, and how is memory reclaimed?" Then the C++/Java dispatch contrast.
Answer:
- Stack β method frames, local primitives, and references. LIFO, per-thread, tiny and fast, auto-freed when the frame pops. Overflow = deep/infinite recursion β
StackOverflowError. - Heap β every object created with
newand all arrays. Shared across threads, GC-managed, larger and slower. Exhaustion βOutOfMemoryError. - In
Person p = new Person();the referencepis on the stack, the object is on the heap. - Garbage collection: an object is collectable when no chain of references from a GC root reaches it (handles cycles, unlike ref-counting). Generational: most objects die young β cheap Young-gen minor GCs, rare Old-gen major GCs; mark-sweep-compact.
- Leaks still happen via lingering references: ever-growing static collections, un-removed listeners, unclosed resources.
- Dispatch: C++ builds a vtable per class and each polymorphic object holds a vptr to it β you opt in with
virtual. Java is virtual by default; the JIT can devirtualise/inline hot monomorphic calls.
Person p = new Person(); // p -> stack, Person object -> heap
p = null; // object now unreachable -> eligible for GC
π§ Memory map: Reference on the stack, object on the heap; GC frees the unreachable, not the unreferenced-by-you. Hook: "Stack = frames & refs, Heap = new objects, GC = reachability."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Can you have a memory leak in a garbage-collected language like Java? β Yes β via unintended reachability: static maps that only grow, listeners never de-registered, caches without eviction. The GC can't collect what's still referenced.
- β³β³ Deepest: Why must a C++ base-class destructor be
virtual, and what's the Java parallel? β Deleting a derived object through a base pointer with a non-virtual destructor is undefined behaviour β the derived destructor never runs, leaking resources. Java has no destructors; you release non-memory resources deterministically withtry-with-resources/AutoCloseable, notfinalize().
I03 β Database Management Systems & Advanced SQL
π― Why this matters for Infosys: DBMS is guaranteed on the panel and the online test. They probe four things: command categories (and the DELETE/TRUNCATE/DROP trap), indexing that kills full-table scans, joins + window functions to solve "Nth highest salary" cleanly, and ACID/isolation. Answer with the exact behaviour and, where it helps, the exact SQL.
In this module β 5 sections
- π§ One-screen mental model
- Command categories & DELETE vs TRUNCATE vs DROP
- Indexing & query optimisation
- Joins & window functions β the Nth-highest weapon
- ACID & concurrency control
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
THE FOUR DBMS PILLARS
COMMANDS DDL / DML / DCL / TCL -> DELETE(row,logged,rollback)
vs TRUNCATE(all,DDL) vs DROP(gone)
INDEXES B-Tree, clustered/non -> kill the FULL TABLE SCAN
JOINS+WIN inner/outer/cross + -> ROW_NUMBER/RANK/DENSE_RANK
window functions solve Nth-highest
ACID Atomic/Consistent/ -> isolation levels vs
Isolated/Durable dirty/non-repeatable/phantom
Command categories & DELETE vs TRUNCATE vs DROP
Scenario: "Categorise SQL commands, then tell me the difference between DELETE, TRUNCATE and DROP β and which is safe in production."
Answer:
- DDL (Data Definition) β
CREATE,ALTER,DROP,TRUNCATE. DML (Manipulation) βINSERT,UPDATE,DELETE,MERGE. DCL βGRANT,REVOKE. TCL βCOMMIT,ROLLBACK,SAVEPOINT. - DELETE = DML: removes rows, supports
WHERE, logs each row, fires triggers, rollback-able, does not reset identity. Slower on big tables. - TRUNCATE = DDL: removes all rows by deallocating pages, no
WHERE, minimal logging, resets identity/auto-increment, no triggers, fast. Rollback is engine-dependent. - DROP = DDL: removes the rows and the table structure itself.
- Production-safe answer: "
DELETE β¦ WHEREβ it's logged, scoped and rollback-able. I neverTRUNCATE/DROPin production without a backup and change window, because in several engines they auto-commit and can't be undone."
π§ Memory map: DELETE = surgical & recoverable; TRUNCATE = empty-the-whole-table fast; DROP = table gone. Hook: "DELETE rows, TRUNCATE table, DROP structure."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Can you roll back a
TRUNCATE? β It depends on the engine: SQL Server logs page deallocations and can roll it back inside a transaction; MySQL and Oracle treat it as DDL that implicitly commits, so it can't be rolled back. Answer with the nuance, never a flat yes/no. - β³β³ Deepest: After
TRUNCATEvsDELETEon a table with an identity column, what differs on the next insert? βTRUNCATEresets the identity seed (next id = start),DELETEkeeps the counter advancing from the last value.TRUNCATEalso releases the storage;DELETEmay leave high-water-mark bloat until a rebuild.
Indexing & query optimisation
Scenario: "How would you speed up a slow query / how do indexes work?"
Answer:
- B-Tree index β the default; a balanced sorted tree giving O(log n) lookups, range scans and ordered reads instead of an O(n) full-table scan. Serves
=,<,>,BETWEEN,ORDER BY, and prefixLIKE 'abc%'. - Clustered index β defines the physical row order (one per table; the leaf level is the data). Non-clustered β a separate structure with a pointer back to the row (many per table).
- Composite index on
(a,b,c)obeys the left-prefix rule: usable fora,a,b,a,b,cβ not a filter onbalone. - Covering index β includes every column a query needs β index-only scan, no table touch.
- Reading a plan: the enemy is a Full Table / Seq Scan on a big table. Common causes: a function/cast on the indexed column (
WHERE YEAR(dt)=2024β rewrite as a range), a leading wildcardLIKE '%x', low selectivity, or stale statistics.
π§ Memory map: Index to kill the full-table scan; watch the left-prefix rule and functions-on-columns that disable it. Hook: "No function on the indexed column; obey the left prefix."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Why can
WHERE YEAR(order_date) = 2024ignore an index onorder_date, and how do you fix it? β Wrapping the column in a function makes it non-sargable, so the B-Tree can't seek. Rewrite as a range:order_date >= '2024-01-01' AND order_date < '2025-01-01'. - β³β³ Deepest: "Add an index to everything" β why is that wrong? β Every index speeds reads but slows writes (each
INSERT/UPDATE/DELETEmaintains it) and costs storage; unused indexes are pure overhead. Index the predicates you actually run and drop the rest.
Joins & window functions β the Nth-highest weapon
Scenario: "Explain the joins, then write a query for the 2nd (or Nth) highest salary."
Answer:
- INNER = rows matching in both. LEFT = all left rows + matches (NULLs where none). RIGHT = mirror. FULL OUTER = all rows from both. CROSS = Cartesian product. SELF = table joined to itself (employeeβmanager).
- Window functions number rows within a
PARTITIONby anORDER BY; the difference is tie handling: ROW_NUMBER()β always unique (1,2,3,4).RANK()β skips after ties (1,1,3).DENSE_RANK()β no gaps (1,1,2).- Nth highest distinct salary β use
DENSE_RANKso ties count once.
-- Nth highest DISTINCT salary
SELECT DISTINCT salary FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
) t WHERE rnk = :N;
-- Highest paid per department
SELECT * FROM (
SELECT e.*, ROW_NUMBER() OVER
(PARTITION BY dept_id ORDER BY salary DESC) AS rn
FROM employees e
) x WHERE rn = 1;
π§ Memory map: RANK skips, DENSE_RANK doesn't, ROW_NUMBER is unique; Nth-distinct β DENSE_RANK. Hook: "Distinct value = DENSE_RANK; specific row = ROW_NUMBER."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: For "2nd highest salary", when do
RANKandDENSE_RANKgive different answers? β When the top salary is tied.DENSE_RANK=2 returns the next distinct value;RANK=2 returns nothing (it jumped 1,1,3). If they want the 2nd distinct amount,DENSE_RANKis correct. - β³β³ Deepest: Write "Nth highest" without window functions. β Correlated subquery:
SELECT DISTINCT salary FROM employees e1 WHERE N-1 = (SELECT COUNT(DISTINCT salary) FROM employees e2 WHERE e2.salary > e1.salary);β orORDER BY salary DESC LIMIT 1 OFFSET N-1for row semantics (dialect-dependent).
ACID & concurrency control
Scenario: "Explain ACID, the read anomalies, and isolation levels."
Answer:
- Atomicity β all statements commit or none do. Consistency β every transaction moves the DB between valid states (constraints hold). Isolation β concurrent transactions don't corrupt each other's view. Durability β once committed, it survives a crash (write-ahead log).
- Anomalies: Dirty read = reading another txn's uncommitted change. Non-repeatable read = a row changes value between two reads (another txn updated+committed). Phantom read = new rows appear in a repeated range query (another txn inserted).
- Isolation levels trade correctness for concurrency:
| Level | Dirty | Non-repeatable | Phantom |
|---|---|---|---|
| READ UNCOMMITTED | β possible | β | β |
| READ COMMITTED (common default) | β | β | β |
| REPEATABLE READ | β | β | β* |
| SERIALIZABLE | β | β | β |
- Trade-off to state: higher isolation = more locking = less throughput. (*MySQL InnoDB's REPEATABLE READ uses next-key locks and largely prevents phantoms too.)
π§ Memory map: ACID = all-or-nothing, valid, isolated, durable; anomalies get worse as isolation drops. Hook: "Dirty=uncommitted, Non-repeatable=value changed, Phantom=rows appeared."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Which anomaly does REPEATABLE READ still permit per the SQL standard? β Phantom reads β a repeated range query can see newly-inserted matching rows. Only SERIALIZABLE fully prevents phantoms in the standard (engine locking like InnoDB's next-key can prevent them earlier).
- β³β³ Deepest: Two transactions each hold a lock the other needs β what is it and how does the DBMS handle it? β A deadlock. The engine runs deadlock detection (a wait-for graph), picks a victim, and rolls it back with an error to retry. Prevention: acquire locks in a consistent order and keep transactions short.
I04 β Templating Paradigm Shift: AMPscript β Handlebars.js
π― Why this matters for Infosys: the role pairs your SFMC templating past with a Handlebars-based stack. The single sentence they want to hear: AMPscript is a server-side scripting language with data access; Handlebars is a logic-less presentation template that renders a pre-built JSON context and cannot fetch data. Everything β lookups, comparisons, logic β moves upstream. Master that and the mapping is direct.
In this module β 4 sections
- π§ One-screen mental model
- Architecture & execution differences
- Conditionals & custom helpers
- Security & escaping
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
AMPSCRIPT vs HANDLEBARS β WHERE THE LOGIC LIVES
AMPSCRIPT (server-side, has data access)
Lookup() ββ query DE mid-render ββ branch ββ output (all in template)
HANDLEBARS (logic-less presentation)
App layer: fetch + join + decide ββ> flat JSON "view model"
β
{{ template }} just paints it
Architecture & execution differences
Scenario: "What's the fundamental difference between AMPscript and Handlebars?"
Answer:
- AMPscript runs server-side inside SFMC at send/render and has data access β
Lookup(),LookupRows(),LookupOrderedRows()query Data Extensions mid-render. It has variables (VAR @x), arithmetic, and full comparison. - Handlebars is logic-less β it renders a JSON context handed to it and cannot reach a database. No variables, no
Lookup, comparisons need a helper. - The consequence to articulate: all data retrieval and business logic move into the application layer (e.g. a Node service) that assembles a flat view model; the template only paints it.
- Why the JD pairs them: both do the same job β templating/personalisation β but AMPscript does fetch+decide+render in one pass, Handlebars splits fetch/decide (upstream) from render (template).
{{firstName}} {{! HTML-escaped }}
{{{emailBodyHtml}}} {{! raw HTML β deliberate }}
π§ Memory map: AMPscript can talk to the database mid-render; Handlebars only paints a context you built first. Hook: "AMPscript fetches; Handlebars only paints."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Your AMPscript email does a
Lookup()for loyalty tier at render. How do you deliver the same in a Handlebars stack? β Do the lookup upstream in the app: query the tier, fold it into the JSON context ({ tier: "Gold", isGold: true }), and pass that to the template β the template never queries anything. - β³β³ Deepest: Why is "logic-less" a design choice, not a limitation? β It forces separation of concerns: data/logic sit in testable application code, templates stay pure presentation. That makes templates safe for non-engineers to edit and impossible to slow down with a mid-render DB call.
Conditionals & custom helpers
Scenario: "How do you do a conditional comparison in Handlebars?" (There's no ==.)
Answer:
{{#if x}}tests truthiness only β no==,>, orAND.- Option 1 (preferred): pre-compute the boolean upstream β the context arrives with
isGold: truealready decided. Keeps the template genuinely logic-less. - Option 2: register a custom helper when comparison must live in the template.
{{#each}}gives loop metadata:{{@index}},{{@first}},{{@last}},{{this}}, and parent scope via{{../x}}. Reusable markup β partials{{> productCard}}.
Handlebars.registerHelper('eq', function (a, b, options) {
return a === b ? options.fn(this) : options.inverse(this);
});
{{#eq tier "Gold"}}Gold offer{{else}}Standard{{/eq}}
{{#each products}}
{{@index}} β {{this.name}} {{#if @first}}(first){{/if}}
{{/each}}
π§ Memory map: #if = truthiness; comparison = pre-compute a boolean or register eq. Hook: "No == in Handlebars β pre-compute or write a helper."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: In the
eqhelper, what dooptions.fn(this)andoptions.inverse(this)do? βoptions.fn(this)renders the block body (the{{#eq}}β¦) with the current context;options.inverse(this)renders the{{else}}branch. Returning one or the other is what makes it a block helper. - β³β³ Deepest: Why is pushing comparisons into helpers sometimes considered an anti-pattern? β It smuggles business logic back into the "logic-less" template, scattering decisions across templates and code and making them hard to test. Pre-computing booleans in the view model keeps logic in one testable place β helpers are best reserved for presentation (formatting), not decisions.
Security & escaping
Scenario: "When would you use triple braces, and what's the risk?"
Answer:
{{value}}β HTML-escaped by default:<becomes<. Safe for all user/data-driven text.{{{value}}}β raw HTML, injected verbatim. Use only for trusted, pre-sanitised markup (a CMS block).- The risk = XSS: any user-supplied string reaching
{{{ }}}can inject<script>β stored cross-site scripting. - Rule: default to double braces; treat triple braces as a deliberate, audited exception, and sanitise upstream (e.g. DOMPurify) before the value ever enters the context.
π§ Memory map: Double = escaped/safe (default); triple = raw/dangerous (audited only). Hook: "Two braces safe, three braces XSS."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: A CMS block legitimately contains HTML you must render. How do you use
{{{ }}}safely? β Sanitise the HTML before it enters the context (server-side allow-list / DOMPurify), store the cleaned string, and only then render with triple braces β never sanitise in the template. - β³β³ Deepest: Escaping the value protects the HTML body β what context does Handlebars' default escaping not protect? β Non-HTML sinks: a value placed inside a URL, an inline
onclick/style/JS context, or an attribute without quotes needs context-specific encoding. HTML-entity escaping alone doesn't stopjavascript:URLs or attribute-breakout β encode per sink.
I05 β Enterprise Communications & AWS Pinpoint Architecture
π― Why this matters for Infosys: Pinpoint is AWS's multichannel engagement service, and every SFMC concept you own maps to it β the vocabulary changes, the model doesn't. The deep shift to nail is subscriber β endpoint, and that orchestration moves out of a boxed tool into AWS (EventBridge + Lambda + the Pinpoint API). Show the mapping table in your head and you sound native.
In this module β 4 sections
- π§ One-screen mental model
- Platform architecture mapping
- Endpoint & multi-channel audience modeling
- Orchestration & event-driven triggers
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
SFMC ββββββββββββββΊ AMAZON PINPOINT
Business Unit Project (application)
Subscriber / Contact Endpoint (one destination per channel)
Subscriber Key User ID (one user -> many endpoints)
Data Extension / List Segment
Content Builder asset Message Template
Journey Builder Journey
Automation Studio EventBridge Β· Lambda Β· schedules
(email send engine) Amazon SES underneath (SPF/DKIM/IP warm-up)
Platform architecture mapping
Scenario: "You know SFMC β map it onto Amazon Pinpoint."
Answer:
- Business Unit β Project (the top-level application container).
- Subscriber/Contact β Endpoint; Subscriber Key β User ID / Endpoint ID.
- Data Extension / List β Segment (dynamic or imported).
- Content Builder asset β Message Template (email, SMS, push, voice, in-app).
- Journey Builder β Journey; Automation Studio β EventBridge / Lambda / schedules.
- Send/Campaign β Campaign (segment + template + schedule); AMPscript personalisation β Handlebars-style variables.
- Tracking β event stream to Kinesis / CloudWatch (raw events; you build the reporting). Email itself is sent by Amazon SES underneath.
- The sentence: "Pinpoint separates the who (endpoints/segments) from the what (templates) from the when (campaigns/journeys) β the same split SFMC makes with DEs, Content Builder and Journey Builder."
π§ Memory map: Same three-way split (who/what/when), different nouns; SES does the actual email send. Hook: "Who=endpoints, What=templates, When=campaigns."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: SFMC tracking gives you Data Views to query β where does that reporting data live in Pinpoint? β Pinpoint streams raw events (sends, opens, clicks, bounces) to Kinesis Firehose β S3/Redshift (or CloudWatch); there are no built-in Data Views, so you build the analytics layer on the event stream.
- β³β³ Deepest: If SES is the send engine, which SFMC deliverability concepts carry over unchanged? β All of them: dedicated IPs, IP warm-up, SPF/DKIM/DMARC alignment, reputation, bounce/complaint handling and suppression β SES exposes the same levers, so your deliverability knowledge transfers directly.
Endpoint & multi-channel audience modeling
Scenario: "How does Pinpoint's audience model differ from SFMC's?"
Answer:
- SFMC centres on the subscriber keyed by Subscriber Key β one row holds email, mobile, push as columns.
- Pinpoint centres on the endpoint β a single addressable destination (one email, one phone, one device token) with its own channel type + attributes.
- A User ID ties multiple endpoints into one person, so one user = many endpoints across channels.
- Benefit: you can suppress or update one channel without touching the others β cleaner true-multichannel modelling.
- Personalisation uses Handlebars-style variables with attribute fallback:
{{User.UserAttributes.FirstName "there"}}β the quoted default mirrors AMPscript'sIIF(Empty(...)).
User u-8842
ββ Endpoint: EMAIL akash@x.com
ββ Endpoint: SMS +9198...
ββ Endpoint: GCM <fcm-token> (Android push)
π§ Memory map: SFMC = one subscriber row with channel columns; Pinpoint = one endpoint per channel, unified by a User ID. Hook: "One user, many endpoints."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: The same person has email + SMS + push. How is that modelled, and how do you personalise across them? β Three endpoints under one User ID; shared data goes in UserAttributes (name, tier), channel-specific data in the endpoint's Attributes. Templates read
{{User.UserAttributes.X}}with a fallback default. - β³β³ Deepest: A user opts out of SMS but not email β how does the endpoint model make that clean, and what's the SFMC contrast? β You disable/opt-out just the SMS endpoint; the email endpoint is untouched. In SFMC's subscriber-centric model, channel opt-outs and status live on the one subscriber record, so per-channel suppression takes extra design (separate lists/attributes).
Orchestration & event-driven triggers
Scenario: "How do event-triggered campaigns work in Pinpoint vs SFMC's Automation Studio?"
Answer:
- SFMC's Automation Studio is a boxed tool inside the platform; Pinpoint pushes orchestration out into AWS and you compose it.
- Schedule β EventBridge scheduled rule. Event trigger β app emits an event β EventBridge rule matches β Lambda runs logic β calls the Pinpoint
SendMessagesAPI. - Custom logic / SSJS-style work β Lambda (Node/Python) with the Pinpoint SDK.
- Benefit: any AWS service can be injected into the flow; it's serverless, scaling with event volume.
export const handler = async (event) => {
const pinpoint = new PinpointClient({ region: "us-east-1" });
await pinpoint.send(new SendMessagesCommand({
ApplicationId: PROJECT_ID,
MessageRequest: {
Addresses: { [event.email]: { ChannelType: "EMAIL" } },
MessageConfiguration: { EmailMessage: { /* template + substitutions */ } }
}
}));
};
π§ Memory map: EventBridge = the trigger/schedule, Lambda = the logic, Pinpoint API = the send. Hook: "EventBridge fires, Lambda decides, Pinpoint sends."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: An order-placed event must fire a confirmation email. Trace the AWS path. β App/API emits
order.placedβ EventBridge rule matches β triggers a Lambda β Lambda calls PinpointSendMessageswith the endpoint + transactional template. Optionally the event also streams to Kinesis for analytics. - β³β³ Deepest: What does composing orchestration in AWS buy you β and what does it cost β versus SFMC's single tool? β Buys flexibility and serverless scale (inject any AWS service, pay per event); costs more moving parts and ops (IAM, retries/DLQs, monitoring across services) that a boxed tool hides. The trade is control vs convenience.
I06 β Omnichannel Messaging Infrastructure (SMS, RCS, IVR)
π― Why this matters for Infosys: beyond email, the comms stack is SMS, RCS and voice. The three signals that read as senior: the GSM-7 vs UCS-2 cost math (one emoji triples the bill), the mandatory SMS fallback for RCS, and SSML control for voice. These are small facts that only hands-on developers know β say them unprompted.
In this module β 4 sections
- π§ One-screen mental model
- SMS encodings & the financial math
- RCS β Rich Communication Services
- IVR & voice personalization (SSML)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
THREE CHANNELS, THREE GOTCHAS
SMS GSM-7 160/153 vs UCS-2 70/67 -> one emoji flips encoding
-> cost multiplies (per segment)
RCS rich cards/carousels/replies -> ALWAYS design the SMS fallback
IVR TTS + SSML + DTMF -> read digits/dates deliberately,
short prompts, always offer agent
SMS encodings & the financial math
Scenario: "Why does adding one emoji blow up an SMS campaign?"
Answer:
- GSM-7 (plain Latin) = 160 chars single, 153 per segment concatenated.
- UCS-2 (any Unicode/emoji) = 70 chars single, 67 per segment concatenated.
- The 7-char drop on concatenation is the UDH header (6 bytes) that lets the phone reassemble multi-part messages.
- The trap: a single emoji β or a curly quote / em-dash pasted from Word β flips the whole message to UCS-2, cutting 160β70 and multiplying segment count. Billing is per segment, so a 150-char message (1 segment) can become 3 (70+67+13) β triple the cost.
- Constraints: plain text only (no HTML/images), opt-out (
STOP) required, 10DLC (US) / DLT (India) registration, shortened links still consume characters, front-load the brand.
π§ Memory map: Emoji β UCS-2 β 160 becomes 70 β more segments β more money. Hook: "One emoji = UCS-2 = triple the bill."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: A 150-character promo is one segment. Marketing adds a π. What's the new segment count and why? β It becomes UCS-2 at 67 chars/segment, so 150 chars β 3 segments (67+67+16). The emoji re-encoded the entire message, not just itself.
- β³β³ Deepest: Besides emoji, what silently forces UCS-2, and how do you defend against it pre-send? β Non-GSM characters: curly βsmartβ quotes, em/en-dashes, ellipsis, accented letters pasted from Word. Defence: run copy through a GSM-7 validator/transliterator that flags or down-converts them before approval.
RCS β Rich Communication Services
Scenario: "What is RCS and what changes for you as a developer?"
Answer:
- RCS is the successor to SMS, delivered via Google's RCS Business Messaging; a verified brand profile gets a name, logo, colour and badge in the thread.
- Features: rich cards (title, media, up to 4 suggestions), carousels (scrollable cards), suggested replies (tappable chips), suggested actions (dial, open URL, share location, calendar), plus read receipts & typing indicators β true two-way messaging.
- The mandatory rule β always author the fallback: if the device/carrier doesn't support RCS, the message degrades to plain SMS. A carousel or a reply chip won't exist on the fallback, so every RCS template needs an SMS version that stands alone.
π§ Memory map: RCS = branded, rich, interactive β but never ship it without its SMS fallback. Hook: "Rich when you can, SMS fallback always."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Your RCS message is a carousel with "Buy" and "Track" action buttons. What must the SMS fallback contain? β A self-contained plain-text version with the same intent and a tappable link in place of the buttons (buttons/carousels can't render), so the message still works with zero rich features.
- β³β³ Deepest: How would you decide, per recipient, whether to send RCS or SMS β and keep cost/UX sane? β Check RCS capability for the number first; send RCS where supported, SMS otherwise. Treat RCS as progressive enhancement: author once with an SMS baseline, upgrade to rich where capability + business value justify it, and measure fallback rate.
IVR & voice personalization (SSML)
Scenario: "Design a voice/IVR prompt β how do you control how it's read?"
Answer:
- Voice has no visual channel: keep each prompt short, confirm what you captured, always offer a repeat and an agent.
- SSML controls the text-to-speech:
<speak>(root),<say-as interpret-as="digits|date|currency">,<break time="300ms"/>(pause),<prosody rate/pitch>,<emphasis>,<sub>(say "Doctor" for "Dr."). - DTMF = keypad tones for menu input ("press 1"); keep menus short, confirm the selection, allow "press 0 for an agent".
- Read deliberately:
4821as "four-eight-two-one" viasay-as, not "four thousand eight hundred twenty-one".
<speak>
Your order <say-as interpret-as="digits">4821</say-as>
ships <break time="300ms"/> tomorrow.
Press <emphasis level="strong">1</emphasis> to confirm, or 2 for an agent.
</speak>
π§ Memory map: No screen β short prompts, confirm, repeat; SSML for how digits/dates/pauses are spoken; DTMF for input. Hook: "Short, confirm, repeat β SSML shapes the voice."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Why wrap
4821in<say-as interpret-as="digits">instead of leaving it as text? β Without it the engine reads the cardinal number ("four thousand eight hundred twenty-one"), which is wrong for an order/OTP/account number.say-asforces digit-by-digit reading β clearer and unambiguous. - β³β³ Deepest: For an OTP read aloud, what SSML choices make it usable, and what's the accessibility risk you must handle? β Read as digits with a small
<break>between them and a moderate<prosody rate="slow">; repeat it once. Risk: users need time to write it down and may be hearing-impaired β so always pair voice OTP with a repeat option and an alternate channel, never voice-only.
I07 β Cross-Client QA & Email Rendering Hacks
π― Why this matters for Infosys: this is your home turf β the part of the JD you already do daily; only the tool names change. The three things to demonstrate: you know why Outlook breaks (the Word engine), you can map Litmus β Email on Acid without flinching, and you have a dark-mode/responsive strategy that survives clients you can't fully control.
In this module β 4 sections
- π§ One-screen mental model
- Outlook rendering-engine quirks
- QA tools β Litmus vs Email on Acid
- Responsive & dark-mode strategy
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
WHY EMAIL QA IS HARD (AND WHERE)
OUTLOOK (Windows) = MS WORD engine
no flex, no grid, no max-width -> tables + ghost tables + VML
QA TOOLS Litmus <-> Email on Acid (same workflow, new names)
RESPONSIVE fluid-hybrid (ghost tables + max-width inline-block divs)
DARK MODE color-scheme meta + prefers-color-scheme
...but some clients FORCE-INVERT anyway -> design for it
Outlook rendering-engine quirks
Scenario: "An email renders correctly everywhere except Outlook. Walk me through it."
Answer:
- Root cause: Windows Outlook renders with the Microsoft Word engine, not a browser β so no flexbox, no grid, no
max-width, no reliablefloat, no background-image shorthand. - Fixes: nested
<table>structure with fixed pixel widths; ghost tables inside<!--[if mso]>conditionals for multi-column layouts that must stack on mobile; VML (<v:roundrect>) for bulletproof buttons and background images;mso-line-height-rule:exactlyandmso-padding-altfor spacing. - Then re-run the client matrix (Outlook, Gmail, Apple Mail, mobile).
<!--[if mso]><table role="presentation" width="600"><tr><td width="300"><![endif]-->
<div style="display:inline-block;width:100%;max-width:300px;vertical-align:top;">column</div>
<!--[if mso]></td></tr></table><![endif]-->
π§ Memory map: Outlook = Word engine β tables, ghost tables, VML, mso- fixes. Hook: "Outlook is Word β tables, ghosts, VML."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: What is a "ghost table" and why is it wrapped in
<!--[if mso]>? β An Outlook-only<table>that enforces fixed column widths. The conditional comment means only Outlook parses it, while modern clients ignore it and use the fluidinline-blockdivs β giving you rigid Outlook layout and responsive everywhere else. - β³β³ Deepest: Why build a button in VML instead of a styled
<a>for Outlook? β Outlook ignorespadding,border-radiusand background on an<a>, so the clickable area collapses to the text. VML<v:roundrect>draws a real rounded, filled, fixed-size shape with a reliable click target β a "bulletproof button" β behind an<!--[if !mso]>normal button for other clients.
QA tools β Litmus vs Email on Acid
Scenario: "We use Email on Acid, you've used Litmus β is that a problem?"
Answer:
- No β same workflow, different names. Map them directly:
| What you do | Litmus | Email on Acid |
|---|---|---|
| Client previews | Litmus Previews | Email Previews |
| Pre-send QA sweep | Litmus Checklist | Campaign Precheck |
| Code + live preview | Litmus Builder | Editor |
| Accessibility | In Checklist | Strong accessibility checks |
| Spam / deliverability | Spam testing | Deliverability & inbox display |
- Your line: "I've used Litmus for cross-client rendering across Outlook, Gmail, Apple Mail and mobile. Email on Acid's Campaign Precheck is the same pre-send sweep under a different name β content, links, images, accessibility, spam. The workflow transfers directly."
π§ Memory map: Litmus Checklist β Campaign Precheck; the sweep is identical. Hook: "Same QA sweep, different label."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Walk your pre-send QA sequence, tool-agnostic. β Verify audience + suppression β render across the client matrix β images-off + dark-mode β alt text β link/UTM validation β spam/content check β seed test β deploy. Any production defect earns a root-cause pass + a new checklist line so it can't recur.
- β³β³ Deepest: Which checks catch problems rendering previews cannot? β Previews are static screenshots; they miss interaction and delivery issues β broken/expired links, wrong UTMs, spam-filter triggers, load time, and accessibility (contrast, alt text, semantic order). Those need Checklist/Precheck-style validators plus a real seed send.
Responsive & dark-mode strategy
Scenario: "How do you handle mobile and dark mode across clients?"
Answer:
- Fluid-hybrid ("spongy") β combine ghost tables (Outlook fixed widths) with
max-width+display:inline-blockdivs so modern clients flow fluidly. Robust because it survives clients that strip media queries (Gmail app, some Outlook). - Media queries for finer control, but never make critical layout depend on them.
- Dark mode: declare
color-scheme+supported-color-schemesmeta, use@media (prefers-color-scheme: dark), and swap logos (light logo on dark). - The honest caveat: some clients (Outlook.com, Gmail Android) force-invert regardless β so design for graceful behaviour under inversion, not pixel control.
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<style>@media (prefers-color-scheme: dark){ .body{background:#15161B!important} .text{color:#E9E7E2!important} }</style>
π§ Memory map: Fluid-hybrid for layout (media queries can be stripped); dark mode = meta + query, but expect forced inversion. Hook: "Hybrid survives stripped queries; design for forced dark."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Why can't you rely on
@mediaqueries for the core layout in email? β Several major clients (Gmail app on some setups, older Outlook) strip<style>blocks or ignore media queries, so a layout that only works with them collapses. The fluid-hybrid structure degrades to a sensible single/limited-width layout even with no CSS applied. - β³β³ Deepest: A pure-black logo on transparent PNG vanishes in a force-inverting dark client β how do you defend? β Give the logo a non-transparent background or a light halo/outline, provide a dark-mode logo swap, and use client-specific overrides (
[data-ogsc]/[data-ogsb]for Outlook) β because you can influence but not fully control forced inversion.
I08 β Comprehensive Infosys Interview Execution Strategy
π― Why this matters for Infosys: technical depth gets you shortlisted; execution gets you hired. The panel is grading how you open, how you think aloud while coding, and how you handle a question you can't answer. This chapter is the playbook: the scripts, the 4-step live-coding protocol, and the composure rules β rehearse these out loud, because recall under pressure is the real test.
In this module β 4 sections
- π§ One-screen mental model
- First 5 minutes & proactive steering
- Live coding β the think-aloud protocol
- Project defensibility & handling unknowns
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
THE THREE MOMENTS THEY JUDGE
OPEN 45s intro -> acknowledge gap -> MAP to your stack -> steer
CODE clarify -> brute force -> optimise -> narrate every line
UNKNOWN don't bluff -> reason from fundamentals -> bound the gap
-> "here's how I'd find out" is a COMPLETE answer
First 5 minutes & proactive steering
Scenario: Introduce yourself and handle "we use Handlebars/Pinpoint, not AMPscript/SFMC."
Answer:
- 45-second intro: who you are (comms-stack developer, 4+ yrs, enterprise email/SMS/multichannel), your core depth (templating, personalisation, cross-client QA, the data side), and why you're here.
- Bridge a domain shift with 3 beats: Acknowledge honestly ("my production depth is AMPscript, not Handlebars") β Map immediately ("same job β one has data access, one is logic-less, so logic moves upstream; I've worked the mapping") β Steer to strength ("day-one value is the delivery side β Outlook, encoding, deliverability β identical across stacks").
- Never hide a gap β name it and bridge it faster than they can probe it.
π§ Memory map: Open with a tight intro; on any gap, acknowledge β map β steer. Hook: "Acknowledge, Map, Steer."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: They say "but you've never used Handlebars in production." Exact response? β "Correct β my production templating is AMPscript. They're the same discipline: AMPscript fetches and renders, Handlebars renders a context I build upstream. I've already mapped output, conditionals, loops and helpers β so it's a vocabulary switch, not a relearn."
- β³β³ Deepest: How do you steer an interview toward your strengths without dodging their question? β Answer the question first (never dodge), then bridge: "β¦and that connects to X, which is where I've done the most depth β for exampleβ¦". You earn the redirect by fully addressing what they asked, then extending into your strong area.
Live coding β the think-aloud protocol
Scenario: They share a coding problem. What's your process?
Answer:
- 1. Clarify constraints & edge cases: empty/null? sorted? signed? input size (does O(nΒ²) pass)? return-on-no-match? Restating buys thinking time and catches misunderstandings.
- 2. State the brute force β "nested loops, O(nΒ²)" β so you always have a working answer on the board.
- 3. Propose the optimal β name the trick and the new time/space ("hash map: O(n) time for O(n) space" / "sorted β two pointers, O(1) space").
- 4. Code cleanly while narrating β real names, handle the edge cases you listed, then dry-run one normal and one edge case aloud.
- The meta-signal: a correct silent solution scores lower than a narrated near-miss. Talk continuously; if you pause, say what you're weighing.
π§ Memory map: Clarify β brute force β optimise β narrate + dry-run. Hook: "Clarify, Brute, Optimise, Narrate."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: You realise mid-code your approach is wrong. What do you do out loud? β Say it: "I see a flaw β this misses the duplicate case. Let me switch to a hash-set approach." Catching and correcting your own bug on the record scores higher than a lucky clean run; it shows real debugging.
- β³β³ Deepest: Interviewer stays silent as you code. What does that usually mean and how do you use it? β Silence is usually "keep going, I'm observing your process" β not disapproval. Use it to narrate more: state assumptions, complexity, and the next step. If truly stuck, ask a specific question ("is memory or latency the priority here?") to reopen dialogue.
Project defensibility & handling unknowns
Scenario: They challenge a design decision, then ask something you don't know.
Answer:
- Defend a decision with context β options β trade-off β decision β verification: "At 2M rows nightly the options were file transfer or per-row API; I chose file/SFTP because API rate limits made 2M calls fragile and slow; I kept the API only for the real-time slice; I verified with a row-count check that halts on anomaly."
- Don't bluff β fabrication is the fastest disqualifier; traps exist to catch exactly that.
- Reason from fundamentals: "I haven't used that specific API, but from first principles it should behave like X because Y."
- Bound the gap, then bridge: "I haven't administered Data Cloud, but I know exactly where it meets my work β it activates a segment into the engagement layer as a data extension, which I treat like any audience."
- Composure rule: a hard question often means you cleared the earlier bar β slow down, think aloud; "I don't know that, but here's how I'd find out" is a complete, strong answer.
π§ Memory map: Defend with a stated trade-off; on unknowns, reason from fundamentals and bound honestly β never bluff. Hook: "A trade-off defended beats a fact recited; honesty beats bluffing."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: "Why did you do it this way and not the obvious way?" β how do you answer without sounding defensive? β Lead with the constraint, not the ego: "Given [constraint], the obvious way fails at [X], so I traded [A] for [B]. If [constraint] changed, I'd revisit it." A decision tied to a trade-off reads senior; "that's how we always did it" reads junior.
- β³β³ Deepest: They keep drilling until you genuinely hit the edge of your knowledge. What's the ideal final move? β Name the boundary precisely, show the adjacent competence, and state how you'd close it: "That's past where I've worked hands-on. Based on fundamentals I'd expect X; to be sure I'd check the docs / prototype / ask the team." That converts a limit into evidence of judgment and honesty β which is what the drill was testing.
I09 β Tutorial: Handlebars.js (Zero β Hero)
π― What you'll master: the full jump from AMPscript to Handlebars β the mental model, the context JSON contract, writing custom helpers, and shipping XSS-safe templates. By the Hero part you can defend every escaping and logic decision in an L3 panel.
In this module β 3 sections
- Part 1 β The Fundamentals
- Part 2 β The Intermediate Application
- Part 3 β The Hero (Interview Level)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
Part 1 β The Fundamentals
The one idea everything hangs on
Lookup() against a Data Extension mid-render. Handlebars is a logic-less presentation template β it renders a JSON context handed to it and cannot fetch data. Every lookup and decision moves upstream, into the application that builds the context.
Core syntax
{{value}}β output, HTML-escaped (the safe default).{{{value}}}β output raw HTML (dangerous β see Part 3).{{#if x}}β¦{{else}}β¦{{/if}}β block helper, tests truthiness only (no==).{{#unless x}}β negatedif.{{#each list}}β¦{{/each}}β iterate; metadata{{@index}},{{@first}},{{@last}},{{this}}.{{#with obj}}β change scope.{{../x}}β reach the parent scope.{{> partial}}β include a reusable partial.{{!-- comment --}}.
AMPscript β Handlebars, at a glance
%%=v(@firstName)=%%
%%[ IF @tier == "Gold" THEN ]%%Gold%%[ ELSE ]%%Std%%[ ENDIF ]%%
%%[ FOR @i=1 TO @n DO ]%% ... %%[ NEXT @i ]%%
Lookup("DE","col","key",@k){{firstName}}
{{#if isGold}}Gold{{else}}Std{{/if}}
{{#each rows}} ... {{/each}}
<!-- impossible: pass data in via the context -->Part 2 β The Intermediate Application
The context JSON is a contract
The template renders exactly one object β the context (a.k.a. view model). Build it upstream by doing every Lookup() and every decision in application code, then hand the template a flat, pre-decided object.
// Upstream (Node): fetch + join + DECIDE, then render
const ctx = {
firstName: user.firstName || "there",
isGold: user.tier === "Gold", // pre-computed boolean
order: { id: order.id, amountCents: order.total },
products: cart.map(p => ({ name: p.name, priceCents: p.price }))
};
const html = Handlebars.compile(source)(ctx);
<p>Hi {{firstName}}.</p>
{{#if isGold}}<p>Your Gold reward is ready.</p>{{/if}}
<ul>{{#each products}}<li>{{name}} β {{priceCents}}</li>{{/each}}</ul>
Custom helpers β because there is no ==
{{#if}} only tests truthiness. To compare, you either pre-compute a boolean upstream (preferred β keeps the template logic-less) or register a helper. Both are correct; naming both is the strong-candidate signal.
// Block helper: renders the block (fn) or the {{else}} branch (inverse)
Handlebars.registerHelper('eq', function (a, b, options) {
return a === b ? options.fn(this) : options.inverse(this);
});
// Inline helper: returns a formatted value (presentation only)
Handlebars.registerHelper('money', cents => '$' + (cents / 100).toFixed(2));
{{#eq tier "Gold"}}Gold offer{{else}}Standard{{/eq}}
Total: {{money order.amountCents}}
Partials for reuse (the ContentBlockByName equivalent)
Handlebars.registerPartial('productCard', '<li>{{name}} β {{money priceCents}}</li>');
// usage: {{#each products}}{{> productCard}}{{/each}}
Part 3 β The Hero (Interview Level)
XSS sanitization β the question they are fishing for
{{value}} encodes < to <. {{{value}}} injects raw markup β so any user-supplied string reaching triple braces is a stored-XSS vector. Rule: default to double braces; treat triple braces as an audited exception, and sanitize upstream before the value enters the context.
import DOMPurify from 'isomorphic-dompurify';
// Sanitize ONCE, upstream β never in the template
ctx.bodyHtml = DOMPurify.sanitize(cmsBlock, { ALLOWED_TAGS: ['b','i','a','p','br','ul','li'] });
{{{bodyHtml}}} {{! safe ONLY because it was sanitized upstream }}
Edge case: escaping protects HTML body, not every sink
| Sink | Default {{ }} enough? |
Why |
|---|---|---|
| HTML text/body | β yes | entity-encodes < > & " |
href="{{url}}" |
β οΈ no | a javascript: URL survives entity-encoding β validate the scheme |
onclick="{{x}}" / inline JS |
β no | JS context needs JS-encoding, not HTML-encoding |
| Unquoted attribute | β no | value can break out of the attribute β always quote |
Production debugging & optimization
- Pre-compile templates at build/startup with
Handlebars.precompileβ parsing on every render is wasted CPU at scale. {{log}}and rendering a partial in isolation isolate "is it the data or the template?" β 90% of "personalization is blank" bugs are a missing/renamed context key, not template syntax.- Keep helpers presentational. Comparison/branching helpers scatter business logic across templates and are hard to test β push decisions into the view model; reserve helpers for formatting (money, dates, pluralization).
- Never let a template do I/O. If you feel the urge to fetch inside a helper, the architecture is wrong β the fetch belongs upstream.
{{{ }}} is a deliberate, sanitized exception and never a default."
I10 β Tutorial: AWS Pinpoint & SES (Zero β Hero)
π― What you'll master: the endpoint audience model, dynamic segments, and event-driven orchestration with EventBridge + Lambda + the Pinpoint API β mapped from everything you already know in SFMC, down to the SES deliverability layer.
In this module β 3 sections
- Part 1 β The Fundamentals
- Part 2 β The Intermediate Application
- Part 3 β The Hero (Interview Level)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
Part 1 β The Fundamentals
The vocabulary map (memorize this table)
| SFMC | Amazon Pinpoint | Note |
|---|---|---|
| Business Unit | Project (application) | top-level container |
| Subscriber / Contact | Endpoint | one destination + attributes |
| Subscriber Key | User ID / Endpoint ID | one user β many endpoints |
| Data Extension / List | Segment | dynamic or imported |
| Content Builder asset | Message Template | email/SMS/push/voice/in-app |
| Journey Builder | Journey | multi-step orchestration |
| Automation Studio | EventBridge Β· Lambda | orchestration lives in AWS |
| (email send engine) | Amazon SES | IPs, SPF/DKIM, reputation |
An endpoint is just JSON
{
"ChannelType": "EMAIL",
"Address": "akash@example.com",
"Attributes": { "cartValue": ["120"] },
"User": {
"UserId": "u-8842",
"UserAttributes": { "FirstName": ["Akash"], "Tier": ["Gold"] }
}
}
Part 2 β The Intermediate Application
Dynamic segments
A Segment is Pinpoint's Data Extension. Imported = a static CSV/S3 list. Dynamic = a live filter over endpoint attributes that re-evaluates at send time.
{
"SegmentGroups": { "Groups": [{
"Dimensions": [{
"Attributes": { "Tier": { "AttributeType": "INCLUSIVE", "Values": ["Gold"] } },
"Demographic": { "Channel": { "DimensionType": "INCLUSIVE", "Values": ["EMAIL"] } }
}],
"SourceType": "ALL"
}]}
}
Personalization with attribute fallback
Hi {{User.UserAttributes.FirstName "there"}}, order
{{Attributes.OrderId}} shipped.
The quoted second argument is the default β the AMPscript IIF(Empty(...)) equivalent.
Event-driven orchestration
Scheduled / API-event automation inside the platform: Entry -> Filter -> Send activity
App emits event -> EventBridge rule matches -> Lambda runs logic -> Pinpoint SendMessages API
// order.placed event -> Lambda -> transactional send
export const handler = async (event) => {
const pinpoint = new PinpointClient({ region: "us-east-1" });
await pinpoint.send(new SendMessagesCommand({
ApplicationId: PROJECT_ID,
MessageRequest: {
Addresses: { [event.email]: { ChannelType: "EMAIL" } },
MessageConfiguration: { EmailMessage: { /* template + substitutions */ } }
}
}));
};
Part 3 β The Hero (Interview Level)
SES is the deliverability layer β everything transfers
- SPF authorizes sending IPs (TXT record); DKIM signs the message (CNAME records SES gives you); DMARC requires SPF or DKIM to align with the visible From domain. Misalignment β spam folder even with valid DKIM.
- Configuration Sets attach to sends to route event publishing (opens, clicks, bounces, complaints) to Kinesis Firehose β S3/Redshift β this is how you rebuild SFMC's Data Views, because Pinpoint has none.
Edge cases & production debugging
| Symptom | Root cause | Fix |
|---|---|---|
| High bounce, new domain | cold IP / no warm-up | ramp volume over 4β8 weeks, engaged users first |
| Emails land in spam | DKIM/SPF not aligned with From | fix DMARC alignment, not just "add DKIM" |
| Send throttled | SES sending quota / rate exceeded | request quota increase; queue + drain via SQS |
| Duplicate sends | Lambda ret/at-least-once delivery | make the send idempotent (dedupe key per event) |
| Segment sends to wrong people | dynamic segment evaluated stale attrs | refresh endpoints before send; check attribute latency |
Optimization
- Idempotency: EventBridge/Lambda is at-least-once β a retried event double-sends. Store a processed-event key (DynamoDB) and short-circuit duplicates.
- Back-pressure: for spikes, put a SQS queue between the producer and the sending Lambda so a burst becomes a controlled drain instead of SES throttling.
- DLQ everything: attach a dead-letter queue to the Lambda so a poison event is captured, not silently lost.
I11 β Tutorial: Omnichannel Infrastructure β SMS, RCS, IVR (Zero β Hero)
π― What you'll master: the SMS encoding cost math (the emoji trap), RCS rich payloads with a mandatory SMS fallback, and SSML voice scripting for IVR β the small, hands-on facts that separate a developer from a reciter.
In this module β 3 sections
- Part 1 β The Fundamentals
- Part 2 β The Intermediate Application
- Part 3 β The Hero (Interview Level)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
Part 1 β The Fundamentals
SMS encoding & segment limits
| Encoding | Single message | Per segment (concatenated) | Used for |
|---|---|---|---|
| GSM-7 | 160 chars | 153 chars | plain Latin, digits, basic punctuation |
| UCS-2 | 70 chars | 67 chars | any emoji / non-GSM char |
RCS and IVR in one line each
- RCS β the rich successor to SMS via Google's RCS Business Messaging: verified brand, rich cards, carousels, suggested replies/actions, read receipts.
- IVR β voice menus driven by TTS + SSML (how it's spoken) and DTMF (keypad input).
Part 2 β The Intermediate Application
The character-math that controls cost
"Your order 4821 has shipped." 28 chars -> 1 segment (GSM-7) Cost: 1x
"Your order 4821 has shipped π" now UCS-2, 70/segment same text -> 3 segments -> Cost: 3x
An RCS rich-card payload (with the fallback)
{
"contentMessage": {
"richCard": { "standaloneCard": {
"cardContent": {
"title": "Order shipped",
"description": "Arrives tomorrow",
"suggestions": [
{ "action": { "text": "Track", "openUrlAction": { "url": "https://x.co/t/4821" } } },
{ "reply": { "text": "Contact support" } }
]
}
}}
},
"smsFallback": "Your order 4821 shipped, arrives tomorrow. Track: https://x.co/t/4821"
}
An SSML voice prompt
<speak>
Your order <say-as interpret-as="digits">4821</say-as>
ships <break time="300ms"/> tomorrow.
Your balance is <say-as interpret-as="currency">USD42.50</say-as>.
Press <emphasis level="strong">1</emphasis> to confirm, or 2 for an agent.
</speak>
Part 3 β The Hero (Interview Level)
The traps and how to answer them
- The hidden UCS-2 trigger isn't just emoji β curly "smart" quotes, em/en-dashes, ellipsis and accented characters pasted from Word silently force UCS-2. Defense: run copy through a GSM-7 validator/transliterator before approval.
- OTP read aloud: use
<say-as interpret-as="digits">with a small<break>between digits and<prosody rate="slow">, then repeat once β and never rely on voice-only for OTP (accessibility + recall). - Regulatory gates: US = 10DLC registration; India = DLT header + template registration. An unregistered template is blocked at the carrier, not by your code.
Debugging & optimization
| Symptom | Cause | Fix |
|---|---|---|
| SMS cost 3Γ expected | one non-GSM char β UCS-2 | strip/transliterate; validate encoding pre-send |
| RCS not delivered | recipient not RCS-capable | capability check first; fall back to SMS |
| Truncated message | segment limit hit mid-word | count segments incl. shortened links |
| IVR says "four thousandβ¦" | number not wrapped in say-as |
force interpret-as="digits" |
I12 β Tutorial: Email Development & Outlook Hacks (Zero β Hero)
π― What you'll master: why Outlook breaks, and the four fixes that always work β ghost tables, VML bulletproof buttons, the fluid-hybrid layout, and dark-mode CSS that survives clients that force-invert.
In this module β 3 sections
- Part 1 β The Fundamentals
- Part 2 β The Intermediate Application
- Part 3 β The Hero (Interview Level)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
Part 1 β The Fundamentals
The root cause of 90% of email bugs
max-width, no reliable float, no background-image shorthand, no CSS positioning. Every "renders fine except Outlook" ticket starts here.
The rules of resilient email
- Tables for layout, not
divs β nested<table role="presentation">with fixed pixel widths. - Inline your CSS (many clients strip
<style>blocks); keep a<style>block only for progressive enhancement (media queries, dark mode). - Images need
alttext and a styled fallback β many clients block images by default. - 600px is the safe content width.
Part 2 β The Intermediate Application
Ghost tables β fluid modern clients, rigid Outlook
A "ghost table" is an Outlook-only table (inside <!--[if mso]>) that forces fixed column widths, while modern clients ignore it and use fluid inline-block divs.
<!--[if mso]><table role="presentation" width="600" cellpadding="0" cellspacing="0"><tr><td width="300"><![endif]-->
<div style="display:inline-block;width:100%;max-width:300px;vertical-align:top;">Column A</div>
<!--[if mso]></td><td width="300"><![endif]-->
<div style="display:inline-block;width:100%;max-width:300px;vertical-align:top;">Column B</div>
<!--[if mso]></td></tr></table><![endif]-->
VML bulletproof button
Outlook ignores padding/border-radius/background on an <a>, collapsing the click target to the text. VML draws a real, filled, fixed-size shape.
<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" href="https://ex.com"
style="height:44px;width:200px;v-text-anchor:middle;" arcsize="12%" fillcolor="#B4652F" stroke="f">
<center style="color:#fff;font-family:sans-serif;font-size:15px;">Shop now</center>
</v:roundrect>
<![endif]-->
<!--[if !mso]><!-->
<a href="https://ex.com" style="display:inline-block;padding:12px 28px;background:#B4652F;color:#fff;border-radius:6px;text-decoration:none;">Shop now</a>
<!--<![endif]-->
Part 3 β The Hero (Interview Level)
The fluid-hybrid ("spongy") pattern
max-width + display:inline-block divs. Result: responsive on modern clients, rigid on Outlook β and it still degrades to a sane single-column layout even when a client strips the media queries (Gmail app, some Outlook builds). This is the most robust responsive email architecture.
Dark mode CSS (and the client that ignores you)
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<style>
:root { color-scheme: light dark; }
@media (prefers-color-scheme: dark) {
.body { background:#15161B !important; }
.text { color:#E9E7E2 !important; }
.logo-light { display:none !important; }
.logo-dark { display:block !important; }
}
/* Outlook.com force-inversion overrides */
[data-ogsc] .text { color:#E9E7E2 !important; }
</style>
| Client | Dark-mode behaviour | Your defense |
|---|---|---|
| Apple Mail | respects your CSS | prefers-color-scheme |
| Gmail app (Android) | force-inverts | avoid pure-black-on-transparent logos |
| Outlook.com | force-inverts | [data-ogsc] / [data-ogsb] overrides |
| Windows Outlook | ignores dark CSS | design light to still read |
Debugging checklist
- Random vertical gaps under images β set
display:block+font-size:0/line-height:0on containers. - Extra line spacing in Outlook β
mso-line-height-rule:exactly+mso-padding-alt. - Background image missing in Outlook β VML
<v:rect>/<v:fill>fallback. - Black logo disappears in dark mode β give it a non-transparent bg or a light halo; provide a dark-mode swap.
I13 β Tutorial: Cross-Client QA (Zero β Hero)
π― What you'll master: the Litmus vs Email on Acid workflow (same job, different names), accessibility checks that previews can't catch, and images-off fallbacks β the QA discipline that keeps defects out of production.
In this module β 3 sections
- Part 1 β The Fundamentals
- Part 2 β The Intermediate Application
- Part 3 β The Hero (Interview Level)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
Part 1 β The Fundamentals
The two tools are the same workflow
| What you do | Litmus | Email on Acid |
|---|---|---|
| Client previews | Litmus Previews | Email Previews |
| Pre-send QA sweep | Litmus Checklist | Campaign Precheck |
| Code + live preview | Litmus Builder | Editor |
| Review / sign-off | Litmus Proof | Collaboration tools |
| Accessibility | in Checklist | strong accessibility checks |
| Spam / deliverability | Spam testing | Deliverability & inbox display |
Part 2 β The Intermediate Application
The pre-send QA sequence (tool-agnostic)
1. DATA verify audience + suppression/exclusion logic
2. RENDER preview across Outlook, Gmail, Apple Mail, mobile
3. IMAGES-OFF every image has meaningful alt + styled fallback
4. DARK MODE check both schemes; logos survive inversion
5. LINKS every href + UTM validated, no 404 / redirect loop
6. CONTENT spelling, subject, preheader, personalization defaults
7. ACCESS contrast, alt text, semantic order, real text not images
8. SEED SEND send to a seed list, read on real devices
9. DEPLOY
Images-off fallbacks
Many clients block images by default, so an image-only email can arrive blank.
<img src="hero.png"> <!-- blank when images are off -->
<img src="hero.png" width="600" alt="20% off, this weekend only" style="background:#B4652F;color:#fff; font-size:20px;line-height:200px;"> <!-- alt shows, styled, on a colored bg -->
- Live text over images for anything critical (offer, CTA) β text always renders.
- Bulletproof buttons (VML + styled
<a>) instead of image buttons β an image CTA vanishes with images off.
Part 3 β The Hero (Interview Level)
What previews cannot catch
Accessibility checks that matter
| Check | Why | How |
|---|---|---|
lang attribute |
screen readers pick the right voice | <html lang="en"> |
role="presentation" on layout tables |
stops SR reading table structure | on every layout <table> |
| Alt text | images-off + SR users | meaningful, not "image1.png" |
| Color contrast β₯ 4.5:1 | low-vision readability | check text vs background |
| Real text, not text-in-images | SR + images-off + translation | live HTML text |
| Logical source order | SR reads DOM order | order matches visual intent |
The production discipline
- Root-cause every escaped defect. Any bug that reaches production earns a root-cause pass + a new line on the QA checklist so the same class can't recur β this is the single strongest QA answer you can give.
- Seed lists per major client, refreshed β rendering engines change (Gmail promotions tab, new Outlook).
- Automate the deterministic checks (links, alt-text presence, image weight) so humans spend attention on judgment, not clicking.
I14 β Tutorial: Advanced SQL & DBMS (Zero β Hero)
π― What you'll master: window functions to solve Nth-highest-salary cleanly, the DELETE vs TRUNCATE architecture (and when each is production-safe), and B-Tree indexing that kills full-table scans.
In this module β 3 sections
- Part 1 β The Fundamentals
- Part 2 β The Intermediate Application
- Part 3 β The Hero (Interview Level)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
Part 1 β The Fundamentals
Command categories
- DDL (
CREATE,ALTER,DROP,TRUNCATE) β structure. DML (INSERT,UPDATE,DELETE,MERGE) β rows. DCL (GRANT,REVOKE) β permissions. TCL (COMMIT,ROLLBACK,SAVEPOINT) β transactions.
The joins in one screen
| Join | Returns |
|---|---|
INNER |
rows matching in both |
LEFT OUTER |
all left + matches (NULLs where none) |
RIGHT OUTER |
all right + matches |
FULL OUTER |
all rows from both |
CROSS |
Cartesian product |
SELF |
table joined to itself |
Part 2 β The Intermediate Application
DELETE vs TRUNCATE vs DROP β the architecture
DELETE FROM orders WHERE status='X'; - row-by-row, fully LOGGED - supports WHERE, fires TRIGGERS - ROLLBACK-able - keeps identity counter - slow on big tables
TRUNCATE TABLE orders; - deallocates PAGES, minimal log - no WHERE, no triggers - RESETS identity/auto-increment - fast; rollback engine-dependent - DROP also removes the STRUCTURE
DELETE β¦ WHERE β it's logged, scoped and rollback-able. I never TRUNCATE/DROP in production without a backup and change window, because in MySQL/Oracle they auto-commit and can't be undone (SQL Server can roll a TRUNCATE back inside a transaction)."
Window functions
ROW_NUMBER/RANK/DENSE_RANK number rows within a PARTITION by an ORDER BY; they differ only in tie handling.
| salary | ROW_NUMBER | RANK | DENSE_RANK |
|---|---|---|---|
| 5000 | 1 | 1 | 1 |
| 5000 | 2 | 1 | 1 |
| 4000 | 3 | 3 | 2 |
| 3000 | 4 | 4 | 3 |
Part 3 β The Hero (Interview Level)
Nth highest salary β the interview classic
-- Nth highest DISTINCT salary (ties count once -> DENSE_RANK)
SELECT DISTINCT salary FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
) t WHERE rnk = :N;
-- Highest paid per department (partitioned)
SELECT * FROM (
SELECT e.*, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn
FROM employees e
) x WHERE rn = 1;
DENSE_RANK=2 returns the next distinct value; RANK=2 returns nothing (it jumped 1,1,3). For "2nd distinct amount," DENSE_RANK is correct. Ask which they mean.
B-Tree indexing & killing the full-table scan
- A B-Tree keeps keys sorted β O(log n) seeks, ranges and ordered reads instead of an O(n) scan. Serves
=,<,>,BETWEEN,ORDER BY, prefixLIKE 'abc%'. - Clustered = physical row order (one/table, leaf = data). Non-clustered = separate structure + row pointer (many/table).
- Composite
(a,b,c)obeys the left-prefix rule: usable fora,a,b,a,b,cβ notbalone.
| Query smell | Why the index is skipped | Fix |
|---|---|---|
WHERE YEAR(dt)=2024 |
function on column = non-sargable | range: dt>='2024-01-01' AND dt<'2025-01-01' |
LIKE '%x' |
leading wildcard | full-text index or restructure |
filter on b of (a,b) |
violates left-prefix | reorder index or add one |
| stale plan | out-of-date statistics | UPDATE STATISTICS / ANALYZE |
ACID & the concurrency trap
- ACID = Atomic, Consistent, Isolated, Durable. Isolation levels trade correctness for throughput:
- Dirty read (uncommitted), Non-repeatable read (value changed), Phantom read (rows appeared). REPEATABLE READ still allows phantoms per the standard; only SERIALIZABLE prevents all three.
- Deadlock: two txns each hold what the other needs β the engine detects a wait-for cycle, rolls back a victim. Prevent by acquiring locks in a consistent order and keeping txns short.
DELETE β¦ WHERE for anything recoverable, reserving TRUNCATE for a full, backed-up reset."
I15 β Tutorial: OOP & CS Fundamentals (Zero β Hero)
π― What you'll master: abstract class vs interface (when each, and the diamond), stack vs heap memory with GC mechanics, and virtual function tables β the L1/L2 fundamentals answered at a level that survives L3 follow-ups.
In this module β 3 sections
- Part 1 β The Fundamentals
- Part 2 β The Intermediate Application
- Part 3 β The Hero (Interview Level)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
Part 1 β The Fundamentals
The four pillars, crisply
- Abstraction β hide complexity behind a contract (design; hides what). Encapsulation β hide data behind access modifiers (implementation; hides data).
- Inheritance β "is-a" reuse. Polymorphism β one interface, many implementations.
- Overloading = compile-time (declared type). Overriding = run-time (actual object).
Part 2 β The Intermediate Application
Abstract class vs Interface
| Dimension | Abstract class | Interface (Java 8+) |
|---|---|---|
| Instance state / fields | β yes + constructors | only public static final constants |
| Method bodies | concrete + abstract | default/static (8+), private (9+) |
| Multiple inheritance | β one superclass | β implement many |
| Semantic | "is-a" + shared code/state | "can-do" capability |
| Use when | subclasses share state + partial impl | unrelated types share a capability |
abstract class Shape {
protected String id; // state
Shape(String id){ this.id=id; }
abstract double area(); // must override
String label(){ return id; } // shared impl
}interface Drawable {
void draw(); // contract
default void hint(){ // Java 8 default
System.out.println("render me");
}
}The diamond problem
interface A { default String hi(){ return "A"; } }
interface B { default String hi(){ return "B"; } }
class C implements A, B {
@Override public String hi(){ return A.super.hi(); } // MUST disambiguate
}
Java bans two-class inheritance to avoid ambiguous state; conflicting interface defaults force explicit X.super.hi(). C++ solves its diamond with virtual inheritance (class D : virtual public Base).
Part 3 β The Hero (Interview Level)
Stack vs Heap & Garbage Collection
- method frames, local primitives, reference variables - LIFO, per-thread, tiny & fast - auto-freed when the frame pops - overflow -> StackOverflowError
- every `new` object & array - shared across threads - GC-managed, larger, slower - exhaustion -> OutOfMemoryError
Person p = new Person(); the reference p is on the stack, the object is on the heap. GC frees by reachability (no reference chain from a GC root), which handles cycles that reference-counting leaks.
- Generational GC: most objects die young β cheap Young-gen minor GCs (Eden + survivors), rare Old-gen majors; mark-sweep-compact.
- Leaks still happen in Java: ever-growing static collections, un-deregistered listeners, unclosed resources β the GC can't collect what's still referenced.
Virtual function tables (vtable / vptr)
class Shape { public: virtual double area() = 0; }; // pure virtual
class Circle : public Shape {
double r;
public:
Circle(double r): r(r) {}
double area() override { return 3.14159 * r * r; }
};
Shape* s = new Circle(2.0);
s->area(); // dynamic dispatch: follow vptr -> Circle's vtable
| C++ | Java | |
|---|---|---|
| Dispatch default | static; opt in with virtual |
virtual by default |
| Mechanism | per-class vtable, per-object vptr | per-class method table |
| Optimization | β | JIT devirtualizes/inlines monomorphic calls |
| Destructor gotcha | base dtor must be virtual (else UB/leak) |
no dtor; use AutoCloseable |
I16 β Tutorial: Algorithmic Logic / DSA (Zero β Hero)
π― What you'll master: the two Infosys signature problems end-to-end β the Bulb Switcher's O(NΒ²) β O(1) math collapse, and Dynamic Programming for Edit Distance β with the narration that scores in a live-coding round.
In this module β 3 sections
- Part 1 β The Fundamentals
- Part 2 β The Intermediate Application
- Part 3 β The Hero (Interview Level)
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
Part 1 β The Fundamentals
How the coding round is actually graded
The two problems in one line each
- Bulb Switcher β
nbulbs OFF; on passitoggle every multiple ofi; how many are ON afternpasses? - Edit Distance β minimum inserts/deletes/substitutions to turn string
aintob(Levenshtein).
Part 2 β The Intermediate Application
Bulb Switcher β brute force first
def bulbs_bruteforce(n):
state = [False] * (n + 1) # 1-indexed
for i in range(1, n + 1): # pass i
for j in range(i, n + 1, i): # multiples of i
state[j] = not state[j] # toggle
return sum(state) # O(n^2) time, O(n) space
Edit Distance β the DP table
def edit_distance(a, b):
m, n = len(a), len(b)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1): dp[i][0] = i # delete-all base case
for j in range(n+1): dp[0][j] = j # insert-all base case
for i in range(1, m+1):
for j in range(1, n+1):
if a[i-1] == b[j-1]:
dp[i][j] = dp[i-1][j-1] # match: carry diagonal
else:
dp[i][j] = 1 + min(dp[i-1][j], # delete
dp[i][j-1], # insert
dp[i-1][j-1]) # substitute
return dp[m][n] # O(m*n)
"" r o s
"" 0 1 2 3
h 1 1 2 3
o 2 2 1 2
r 3 2 2 2
s 4 3 3 2
e 5 4 4 3 <-- horse -> ros = 3
Part 3 β The Hero (Interview Level)
Bulb Switcher β the O(1) collapse
k is toggled once per divisor of k. Divisors come in pairs (d, k/d) β even count β OFF, except perfect squares where one divisor is unpaired (d == k/d) β odd count β ON. So the answer is the number of perfect squares β€ n = floor(sqrt(n)).
toggle a boolean array, two nested loops, then count the True cells
import math
def bulbs(n):
return int(math.isqrt(n))
# n=100 -> 10Edit Distance β the optimizations they probe
- Space O(mΒ·n) β O(min(m,n)): each cell needs only the previous row and the current row so far β keep two 1-D arrays (
prev,cur) and swap them each outer iteration. - Cost-model twist: if substitution costs 2 while insert/delete cost 1, substitution is never worth it (delete+insert = 2), so it reduces to Longest Common Subsequence:
edits = m + n β 2Β·LCS(a,b).
def edit_distance_1d(a, b):
m, n = len(a), len(b)
prev = list(range(n + 1))
for i in range(1, m + 1):
cur = [i] + [0]*n
for j in range(1, n + 1):
cur[j] = prev[j-1] if a[i-1]==b[j-1] else 1+min(prev[j],cur[j-1],prev[j-1])
prev = cur
return prev[n] # O(m*n) time, O(n) space
The narration script (say this aloud)
| Step | What you say |
|---|---|
| Clarify | "Empty strings? Case-sensitive? What's the max length β does O(mΒ·n) pass?" |
| Brute | "NaΓ―ve recursion on each position branches 3 ways β exponential." |
| Optimize | "Overlapping subproblems β memoize into a DP table, O(mΒ·n)." |
| Refine | "Each row needs only the previous row β drop to O(n) space." |
| Verify | "Dry-run 'horse'β'ros': answer 3. Base cases fill the first row/column." |
floor(sqrt(n)) in O(1). For Edit Distance I'd build the O(mΒ·n) DP, then reduce to O(n) space with rolling rows β and I'd narrate the whole arc, because that's what the round is grading."
I17 β Charter (Spectrum): Company Track
π― Why this matters: the Infosys interview is for the end-client Charter Communications β the company behind the Spectrum brand, one of the largest US broadband/cable/mobile/voice providers. A telecom of that scale runs enormous customer-communications volume β billing, outage and service alerts, technician-appointment reminders, Spectrum Mobile, retention β across exactly the email + SMS + voice/IVR stack this role is built on. This chapter turns "Software Developer & Communications Stack" into "how I'd build comms for Charter," plus the compliance layer (TCPA) that a telecom lives and dies by.
In this module β 6 sections
- π§ One-screen mental model
- Who Charter is, and why this role exists
- The communications a telecom actually sends β mapped to your skills
- TCPA & telecom compliance β the layer that decides everything
- Likely Charter scenario questions (with model answers)
- How to frame your experience for Charter + questions to ask
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
WHY CHARTER NEEDS THIS ROLE
CHARTER = Spectrum: Internet Β· TV Β· Mobile Β· Voice (tens of millions of customers)
β
ββ TRANSACTIONAL comms billing, payments, outages, appointments, OTP
ββ MARKETING comms upsell (Mobile), win-back, onboarding
ββ CHANNELS Email (SES) Β· SMS/10DLC Β· Voice/IVR Β· Push (My Spectrum App)
β
built on AWS (Charter is an AWS + GenAI shop)
β
GOVERNED BY TCPA Β· CAN-SPAM Β· 10DLC consent
Who Charter is, and why this role exists
Context: Charter Communications operates the Spectrum brand β internet, cable TV, Spectrum Mobile, and voice β serving tens of millions of US residential and business customers. Publicly, Charter is an AWS shop: it announced a strategic collaboration with AWS on generative AI to modernize software development (standardizing on GitLab Duo with Amazon Q Developer), and it invests heavily in digital self-service (the My Spectrum App) that reaches customers by their preferred channel β phone, chat, email or text.
Answer (what to know):
- Charter's business generates massive transactional communication volume: every bill, payment, autopay notice, outage/service alert, technician-appointment reminder, activation and security OTP is a message that must arrive.
- On top of that sits marketing/lifecycle comms: Spectrum Mobile upsell, onboarding, retention/win-back β the high-value growth area.
- The role β Software Developer & Communications Stack β is about building and running the platform that renders and sends those messages: templating (Handlebars), the send/orchestration layer (AWS Pinpoint/SES + EventBridge/Lambda), and cross-channel delivery (SMS/RCS/IVR/email).
- Charter being an AWS + GenAI organization is why the JD pairs Handlebars with Pinpoint rather than SFMC β and it's a strong thing to reference.
π§ Memory map: Charter = Spectrum, AWS shop, enormous transactional + lifecycle comms across every channel. Hook: "Bills, outages, appointments, Mobile upsell β on AWS."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: Why would a cable/mobile operator invest so heavily in a dedicated communications stack? β Volume and stakes: tens of millions of customers Γ multiple transactional events each = billions of messages, where a missed outage/appointment/payment notice directly drives call-center load and churn. The platform is a cost-and-retention lever, not a nice-to-have.
- β³β³ Deepest: Charter publicly standardized software dev on AWS + GenAI tooling β how does that shape how you'd pitch yourself? β Lean into AWS-native thinking (Pinpoint/SES/EventBridge/Lambda), mention comfort with AI-assisted development (Amazon Q/GitLab Duo-style workflows), and frame your SFMC background as a direct translation onto their AWS stack rather than a different world.
The communications a telecom actually sends β mapped to your skills
Scenario: "What kinds of communications would you build for a client like Charter?" This is where you connect the guide's 8 skills to Charter's real use cases.
Answer (use cases β the skill that delivers them):
- Billing & payments β bill-ready, autopay receipt, payment-failed/past-due, paperless-billing β transactional email (SES) + SMS; personalized with Handlebars from a pre-built context.
- Outage & service alerts β proactive "we see an issue in your area / it's resolved" β event-driven (EventBridge/Lambda) SMS + push; time-critical, so idempotent and fast.
- Technician appointments β confirmation, day-before reminder, "tech en route", reschedule β SMS + IVR, with reply-to-confirm.
- Onboarding/activation β welcome, self-install guidance, app adoption β email journeys + push.
- Spectrum Mobile & upsell β line-add offers, device promos, win-back β marketing comms (consent-gated).
- Security β OTP / fraud alerts β SMS/voice, read digit-by-digit (SSML).
π§ Memory map: Map each Charter event to a channel + the guide skill that ships it; split transactional from marketing. Hook: "Outage=event SMS, bill=Handlebars email, OTP=SSML voice, upsell=consented."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: An outage alert and a Mobile upsell are both "an SMS" β why build them completely differently? β Different classification, latency and consent: the outage is transactional, real-time (EventBridgeβLambdaβsend), and legally sendable to all affected customers; the upsell is marketing, batched/segmented, and may only go to customers with prior express consent. Same channel, different pipeline and rules.
- β³β³ Deepest: For proactive outage alerts at scale, what's the hardest engineering problem? β Fan-out + idempotency + suppression under a spike: an outage hits thousands of endpoints at once, so you need a queue/back-pressure (SQS), dedupe so a flapping outage doesn't re-blast, and correct who's-actually-affected targeting β a wrong-audience outage blast is worse than silence.
TCPA & telecom compliance β the layer that decides everything
Scenario: "How do you keep Charter's SMS and voice campaigns compliant?" For a telecom this is not optional trivia β it's the constraint every design bends around.
Answer:
- TCPA (Telephone Consumer Protection Act) governs calls and texts. Marketing SMS/autodialed or prerecorded voice requires prior express written consent; purely transactional/informational messages need prior express consent but not written.
- Consent is revocable by any reasonable means β a "STOP" reply, a call, an email β and revocation must propagate across channels quickly.
- Quiet hours: no marketing calls/texts outside 8amβ9pm local to the recipient.
- 10DLC β US A2P SMS must run on registered brand + campaign (message content, opt-in flow) or carriers filter it.
- CAN-SPAM for email β accurate From/subject, physical address, working unsubscribe honored promptly.
- Engineering consequence: consent/preference state is a first-class data model and a hard gate at send time β you never rely on the campaign author to remember.
π§ Memory map: TCPA = consent for marketing texts/calls, revocable anytime, quiet hours, 10DLC registered; enforce consent as a send-time gate. Hook: "Consent is architecture: gate every marketing send, honor STOP everywhere."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: A customer replies STOP to a marketing text but still needs outage alerts. How do you handle it? β Model consent per channel and per message-category: STOP suppresses marketing on SMS, but transactional/service alerts (outage, security) are a separate lawful basis and continue. One global flag is wrong β you need category-scoped suppression.
- β³β³ Deepest: How do you make consent revocation reliable across email, SMS and voice at scale? β A central consent/preference service as the source of truth that every channel checks at send time, updated by every opt-out path (STOP, unsubscribe link, call-center, app), with low propagation latency and an audit log. Channel-local opt-out lists drift and cause violations; centralize and gate.
Likely Charter scenario questions (with model answers)
Scenario: telecom-flavored versions of the scenarios you've drilled β expect these shaped around Spectrum's world.
Answer (three they may throw, answered briefly):
- "Design proactive outage notifications." β Event from the network monitoring system β EventBridge rule β Lambda resolves affected endpoints from the address/account model β SMS + push via Pinpoint, idempotent (dedupe on outage-id + endpoint) with SQS back-pressure for the spike; suppress marketing, allow service class; send an "all-clear" on resolve.
- "A bill-ready email renders wrong in Outlook for some customers." β It's the Word engine: check unsupported CSS, switch to table + ghost-table layout, VML button; but also verify the Handlebars context β a missing key (e.g.
amountDue) is the more common "some customers" cause than rendering. - "Appointment reminders aren't reaching some customers." β Split never-sent vs failed: check consent/suppression (did they opt out of SMS?), 10DLC campaign health/carrier filtering, invalid/rotated numbers, then the event trigger itself β prove it with send + delivery logs, not guesses.
π§ Memory map: Outage = event-driven + idempotent + back-pressure; render bug = Word engine and check the context; not-delivered = never-sent vs failed via logs. Hook: "Classify, design, fail-think, verify β and always ask who it's legal to send to."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: For outage alerts, how do you avoid alert fatigue when an outage flaps up and down? β Debounce at the source (confirm the outage is stable for N minutes before notifying), dedupe per outage-id, and cap notifications per customer per window β so a flapping node doesn't send ten messages.
- β³β³ Deepest: Millions of customers, a regional outage β how do you send fast without melting SES/SMS throughput or tripping rate limits? β Pre-resolve the affected segment, enqueue to SQS, drain with a concurrency-controlled worker pool sized to your SES/10DLC throughput, use template + bulk send APIs, and monitor bounce/throttle to back off β turning a spike into a fast-but-controlled drain instead of failures.
How to frame your experience for Charter + questions to ask
Scenario: the opening and closing of the interview, tuned to Charter.
Answer:
- Frame your SFMC/comms background as a direct fit: you've built high-volume transactional and lifecycle messaging (email/SMS/multichannel) with templating, personalization and cross-client QA β exactly Charter's problem, and the AMPscriptβHandlebars / SFMCβPinpoint mapping is a vocabulary switch, not a relearn.
- Reference their world: Spectrum's scale, the transactional-vs-marketing split, TCPA discipline, and their AWS + GenAI direction β it shows you researched the client, not just the JD.
- Use the acknowledge β map β steer move on any gap (see the Execution chapter).
- Smart questions to ask them:
- "Is the comms platform Pinpoint/SES-native, or a mix with an existing ESP?"
- "How is consent/preference managed today across email, SMS and voice β one service or per-channel?"
- "What's the split between transactional (outage/billing/appointments) and marketing volume?"
- "Where does the team spend most time β templating/build, orchestration, or deliverability?"
π§ Memory map: Fit = same problem (comms at scale); reference Spectrum scale + TCPA + AWS; ask about stack, consent, and the transactional/marketing split. Hook: "Same problem, their vocabulary β and I did my homework on Charter."
π― Drill deeper (the follow-ups they'll ask):
- β³ Deeper: "You've used SFMC, not our stack β why should we trust you here?" β "Because the hard parts transfer: high-volume deliverability, personalization, cross-client QA and consent discipline are stack-independent. The tool-specific syntax β Handlebars, Pinpoint β I've already mapped from AMPscript and SFMC; that's the fast part to pick up."
- β³β³ Deepest: What one question, asked in the interview, signals you think like an owner of Charter's comms platform? β "How is consent and preference managed across channels today?" β it shows you understand that at a telecom the platform's hardest, highest-stakes problem is compliant, cross-channel consent, not templating β which is exactly what a lead worries about.
I18 β Advanced Email Dev Lab: Outlook, VML & Handlebars
π― What you'll master: the exact, copy-pasteable snippets an L2/L3 Email Developer uses in a live coding interview or on the job β ghost tables, VML backgrounds and buttons, web-font fallbacks, dark mode, the Outlook 120-DPI fix, and hands-on Handlebars. Every skill = the Fundamentals (the why) and the Hero (the production code).
In this module β 8 sections
- π§ One-screen mental model
- Skill 1 β Email breakage & Ghost Tables
- Skill 2 β VML Background Images for Outlook
- Skill 3 β Bulletproof Buttons (VML roundrect)
- Skill 4 β Custom Web Fonts & the Outlook "Times New Roman" bug
- Skill 5 β Media Queries & Dark Mode (prefers-color-scheme)
- Skill 6 β QA Tools & the Outlook 120-DPI Bug
- Skill 7 β Handlebars.js Hands-On Scenarios
Open each section below, or use Next ▶ (or the → key) to move through them one at a time.
π§ One-screen mental model
THE OUTLOOK SURVIVAL KIT (Windows Outlook = MS Word engine)
LAYOUT ghost tables <!--[if mso]> ... <![endif]-->
BACKGROUND VML <v:rect> + <v:fill type="frame">
BUTTON VML <v:roundrect arcsize> + <w:anchorlock/>
FONTS web font for modern clients; force Arial for mso
DARK MODE color-scheme meta + prefers-color-scheme + [data-ogsc]
120 DPI <o:PixelsPerInch>96</o:PixelsPerInch>
DATA Handlebars: {{#each}}, {{@last}}, helpers, {{{raw}}}
Skill 1 β Email breakage & Ghost Tables
The Fundamentals β why emails break in Outlook
- Windows Outlook (2007β2021 + the classic client) renders with the Microsoft Word engine, not a browser.
- Consequence: no
max-width, no flexbox, no grid, nofloat, no negative margins, nobackgroundshorthand. Amax-widthcontainer simply expands full-width in Outlook. - The fix pattern: give Outlook a fixed-width table it does understand, while modern clients use a fluid
max-widthdiv β the two coexist via a conditional comment.
max-width:600px centered layout is full-bleed in Outlook β why?" The Word engine ignores max-width. The answer is a ghost table: an Outlook-only fixed-width table that props the layout open.
The Hero β the ghost table
<!--[if mso]>
<table role="presentation" align="center" width="600" cellpadding="0" cellspacing="0" border="0">
<tr><td width="600">
<![endif]-->
<div style="max-width:600px; margin:0 auto;">
<!-- fluid, responsive content for every modern client -->
</div>
<!--[if mso]>
</td></tr></table>
<![endif]-->
Two-column that stacks on mobile but stays side-by-side in Outlook:
<!--[if mso]><table role="presentation" width="600"><tr><td width="300" valign="top"><![endif]-->
<div style="display:inline-block; width:100%; max-width:300px; vertical-align:top;">Column A</div>
<!--[if mso]></td><td width="300" valign="top"><![endif]-->
<div style="display:inline-block; width:100%; max-width:300px; vertical-align:top;">Column B</div>
<!--[if mso]></td></tr></table><![endif]-->
Skill 2 β VML Background Images for Outlook
The Fundamentals β the scenario
- A hero section needs a background image with HTML text overlaid (headline + CTA on top of a photo).
- Standard CSS
background-image(andbackgroundshorthand) does not render in Windows Outlook β you get a blank or solid-color block. - The fix is VML (Vector Markup Language) β Office's own drawing layer β with
type="frame"to scale the image, and a<v:textbox>to hold the real HTML overlay.
The Hero β VML <v:rect> + <v:fill> (Outlook 2007β2021)
<div style="background-color:#222222; background-image:url('https://cdn.example.com/hero.jpg');
background-size:cover; background-position:center;">
<!--[if mso]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
fill="true" stroke="false" style="width:600px; height:320px;">
<v:fill type="frame" src="https://cdn.example.com/hero.jpg" color="#222222" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div>
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0">
<tr><td align="center" style="padding:80px 20px;">
<h1 style="color:#ffffff; font-family:Arial,sans-serif; margin:0;">Summer Sale</h1>
</td></tr>
</table>
</div>
<!--[if mso]>
</v:textbox>
</v:rect>
<![endif]-->
</div>
width/height must be fixed pixels (no %); type="frame" gives cover-style scaling; always set a color= fallback and a CSS background-color so images-off still reads. Text lives in the <v:textbox>, mirrored by the normal HTML for other clients.
Skill 3 β Bulletproof Buttons (VML roundrect)
The Fundamentals β the scenario
- Outlook ignores
border-radius,paddingand background on an<a>, so a CSS button renders square and the click target shrinks to the text. - Fix: draw a real VML
<v:roundrect>witharcsize(the rounded corners),<w:anchorlock/>(locks the text so Outlook can't shift it), and a<center>for text β behind a normal styled<a>for every other client.
The Hero β VML roundrect + <w:anchorlock/>
<a href="https://ex.com" style=" background:#B4652F; border-radius:6px; color:#fff; padding:14px 28px; text-decoration:none;">Shop Now</a>
<!--[if mso]>
<v:roundrect
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w="urn:schemas-microsoft-com:office:word"
href="https://ex.com" arcsize="12%"
fillcolor="#B4652F" strokecolor="#B4652F"
style="height:48px;width:220px;
v-text-anchor:middle;">
<w:anchorlock/>
<center style="color:#fff;
font-family:Arial,sans-serif;
font-size:16px;font-weight:bold;">
Shop Now</center>
</v:roundrect>
<![endif]-->Complete, both paths (Outlook + everyone else):
<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
href="https://ex.com" arcsize="12%" fillcolor="#B4652F" strokecolor="#B4652F"
style="height:48px; width:220px; v-text-anchor:middle;">
<w:anchorlock/>
<center style="color:#ffffff; font-family:Arial,sans-serif; font-size:16px; font-weight:bold;">Shop Now</center>
</v:roundrect>
<![endif]-->
<!--[if !mso]><!-->
<a href="https://ex.com" style="background-color:#B4652F; border-radius:6px; color:#ffffff;
display:inline-block; font-family:Arial,sans-serif; font-size:16px; font-weight:bold;
line-height:48px; text-align:center; text-decoration:none; width:220px;
-webkit-text-size-adjust:none;">Shop Now</a>
<!--<![endif]-->
arcsize="12%" β the CSS border-radius (as a % of height); v-text-anchor:middle + <w:anchorlock/> vertically center and lock the label; match the VML height/width to the <a>'s line-height/width so both clients look identical.
Skill 4 β Custom Web Fonts & the Outlook "Times New Roman" bug
The Fundamentals β the scenario
- Brand wants a custom font (Google Fonts or self-hosted). Web fonts work in Apple Mail, iOS Mail and some Samsung/Outlook-app clients β but not Gmail, not Windows Outlook, not Outlook.com.
- The bug: in Windows Outlook, when a
font-familystack starts with a web font it doesn't recognize (e.g.'Poppins', Arial, sans-serif), Outlook doesn't fall through to Arial β it silently defaults the whole thing to Times New Roman. Your clean sans-serif email arrives looking like a legal letter.
The Hero β link the font, then force a safe fallback for Outlook
<!-- 1) Load the web font for capable clients only (hidden from Outlook/mso) -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
</style>
<!--<![endif]-->
<!-- Self-hosted alternative: -->
<style>
@font-face {
font-family: 'BrandSans';
font-style: normal; font-weight: 400;
src: url('https://cdn.example.com/fonts/brandsans.woff2') format('woff2');
mso-font-alt: 'Arial'; /* hint Outlook toward a safe substitute */
}
</style>
<!-- 2) THE FIX: force Outlook to Arial so it never falls to Times New Roman -->
<!--[if mso]>
<style>
* { font-family: Arial, sans-serif !important; }
</style>
<![endif]-->
Then use the stack normally β modern clients get Poppins, Outlook gets Arial (not Times):
<td style="font-family:'Poppins', Arial, Helvetica, sans-serif; font-size:16px;">Body copy</td>
<!--[if mso]> block that hard-sets font-family:Arial !important, plus mso-font-alt on the @font-face. Always name a web-safe font second in the stack regardless.
Skill 5 β Media Queries & Dark Mode (prefers-color-scheme)
The Fundamentals β the scenario
- The email looks right in light mode but Gmail / Outlook (iOS & desktop) aggressively invert colors in dark mode β black-on-white becomes muddy, and dark logos vanish on dark backgrounds.
- You declare dark-mode support so clients that respect your CSS use your palette, and you add mitigations for the clients that force-invert anyway.
The Hero β meta tags + media query + logo swap + Outlook.com overrides
<head>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<style>
:root { color-scheme: light dark; supported-color-schemes: light dark; }
/* Clients that respect author dark styles */
@media (prefers-color-scheme: dark) {
.email-bg { background:#1a1a1a !important; }
.email-card { background:#242424 !important; }
.email-text { color:#eaeaea !important; }
.dark-logo { display:block !important; width:160px !important; max-height:inherit !important; }
.light-logo { display:none !important; }
}
/* Outlook.com dark mode uses [data-ogsc]/[data-ogsb] attribute hooks */
[data-ogsc] .email-text { color:#eaeaea !important; }
[data-ogsb] .email-bg { background:#1a1a1a !important; }
</style>
</head>
Logo swap in the body (both present; CSS shows the right one):
<img src="logo-light.png" class="light-logo" width="160" alt="Brand" style="display:block;">
<div class="dark-logo" style="display:none; mso-hide:all;">
<img src="logo-dark.png" width="160" alt="Brand" style="display:block;">
</div>
Skill 6 β QA Tools & the Outlook 120-DPI Bug
The Fundamentals β the scenario
- A client reports the email is broken on "Outlook 120 DPI." On Windows displays scaled to 120/144 DPI, Outlook multiplies dimensions (Γ1.25 / Γ1.5) β but only for elements without explicit sizing β so images blow up, columns misalign, and VML shifts.
- Root causes: relying on CSS
height, missing imagewidth/height, and percentage widths inside Outlook.
The Hero β the one-line DPI fix + the QA workflow
Force Outlook to render at 96 DPI regardless of Windows scaling (put in <head>):
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
Plus the rules that keep 120 DPI stable:
- Set both
widthandheightas HTML attributes on every<img>(not just CSS). - Give VML elements explicit px dimensions matching their HTML.
- Avoid CSS
heighton<td>; use padding for vertical space.
How to isolate it with Litmus / Email on Acid:
- Reproduce: open the render in the Outlook 120 DPI preview client in Litmus Previews / Email on Acid Email Previews to see the exact break.
- Iterate: edit in Litmus Builder (code + live multi-client preview) and re-check only the Outlook DPI variants.
- Pre-send sweep: run Litmus Checklist / EoA Campaign Precheck for images-off (every image needs meaningful
alt+ styled fallback), link/URL validation, spam scoring, and accessibility (screen-reader order, contrast,role="presentation"on layout tables,langattribute). - Sign off: seed-send and read on a real device before deploy.
<o:PixelsPerInch>96</o:PixelsPerInch> fix and explicit image dimensions, then confirm across the DPI preview matrix β don't debug on one machine.
Skill 7 β Handlebars.js Hands-On Scenarios
The Fundamentals β logic-less rendering
- Handlebars renders a pre-built JSON context and cannot fetch data or run comparisons β logic lives upstream.
{{ }}HTML-escapes;{{{ }}}is raw.{{#each}}iterates;{{@last}}/{{@first}}/{{@index}}are loop metadata.
The Hero β Scenario 1: loop a cart, detect the last item
// upstream context
const ctx = { cart: [
{ name: "Router", priceCents: 12900 },
{ name: "Modem", priceCents: 8900 },
{ name: "Cable", priceCents: 1500 }
]};
<table role="presentation" width="100%">
{{#each cart}}
<tr>
<td>{{this.name}}</td>
<td align="right">{{money this.priceCents}}</td>
</tr>
{{#unless @last}}
<tr><td colspan="2" style="border-bottom:1px solid #eee; font-size:0; line-height:0;"> </td></tr>
{{/unless}}
{{/each}}
</table>
{{#unless @last}} draws a divider between rows but not after the final one β the classic @last use.
The Hero β Scenario 2: register helpers (compare + currency)
// Comparison block helper β because {{#if}} can't do ==
Handlebars.registerHelper('eq', function (a, b, options) {
return a === b ? options.fn(this) : options.inverse(this);
});
// Inline formatting helper (presentation only)
Handlebars.registerHelper('money', cents => '$' + (cents / 100).toFixed(2));
{{#eq currentTier "Gold"}}
<p>Your Gold perks are ready.</p>
{{else}}
<p>Upgrade to Gold for free shipping.</p>
{{/eq}}
Order total: {{money order.amountCents}}
The Hero β Scenario 3: raw HTML safely ({{{ }}} vs {{ }})
{{cmsBlock}}
<!-- <b>Hi</b> renders as
literal text: <b>Hi... -->{{{cmsBlock}}}
<!-- injects markup verbatim;
XSS risk if not sanitized -->// Sanitize ONCE, upstream, before it ever enters the context
import DOMPurify from 'isomorphic-dompurify';
ctx.cmsBlock = DOMPurify.sanitize(rawCmsHtml, { ALLOWED_TAGS: ['b','i','a','p','br','ul','li','strong','em'] });
{{{ }}} is stored XSS. Default to {{ }}; treat {{{ }}} as a deliberate, audited exception. And remember escaping protects the HTML body β a value in an href or inline onclick needs context-specific encoding on top.
β Marked for Review
Sections you flagged with the β Mark button in the bar above (or the M key) while studying. Click any item to jump straight back to it. This list is saved in your browser and updates automatically.