Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Shift each letter forward by k positions within its own case, wrapping past 'z'/'Z' back to the start, and leave every non-letter untouched. The wrap is modular arithmetic: newChar = base + (c − base + k) % 26, where base is 'a' for lowercase and 'A' for uppercase. Reduce k by k % 26 first so a huge k doesn't overflow. O(n) time, O(n) space for the output.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you implement the wrap with modular arithmetic instead of brittle if-chains, keep the two cases separate, and skip non-letters cleanly.
Where candidates slip
Adding k to the character code without the modulo, so shifting 'y' by 5 produces a non-letter symbol instead of wrapping to 'd'.
How to stand out
Reduce k mod 26 first, derive the (c − base + k) % 26 + base formula explicitly, and mention the same idea generalises to ROT13 and digit rotation.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewcharacter clowercase a-z?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Manual Wrap with If-Chains (Naive)
1Hint 1Explore hintHide hint
Add k to the character code directly, then fix up overflow by hand: if it passes 'z', subtract 26.
2Hint 2Explore hintHide hint
You still have to reduce k first, or a single subtraction won't be enough when k > 26.
3Hint 3Explore hintHide hint
Notice how many edge branches you need (over 'z', over 'Z', wrap by more than once) — that's the smell the modulo removes.
A · Core idea
The hand-rolled version shifts a letter by adding k to its code and then patches the result back into range with explicit comparisons.
Why people reach for it. Without recalling modular arithmetic, the obvious move is: c + k, and if that lands past 'z', subtract 26 to wrap. It mirrors how you'd shift letters on paper.
Where it goes wrong. A single if (x > 'z') x -= 26; only undoes one wrap. If k (or k % 26) plus the offset overshoots the alphabet by more than 26 — which happens whenever you forget to reduce k first — one subtraction isn't enough, and you'd need a loop or repeated subtraction. You also have to duplicate the whole fix-up for uppercase. Every extra branch is a place for an off-by-one or a wrong base.
Why we don't ship it. It is the same O(n), but it trades a clean one-line transform for a thicket of conditionals. The modular formula expresses the wrap exactly once and can never under- or over-shoot, so it is both shorter and safer. Show the if-chain to demonstrate you understand the wrap, then fold it into the modulo.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "yz", k = 2'y' (121) + 2
= 123, past 'z' (122)
123 − 26 = 97 → 'a'"ab"C · Code with teaching comments
Reference implementation previewpublic String caesarCipher(String s, int k) { k %= 26; StringBuilder out = new StringBuilder(s.length()); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i);Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
If you ever write if (x > 'z') x -= 26;, you've re-implemented % 26 by hand — and only the version that handles a single wrap. Reach for the modulo and reduce k first so a single operation always lands in range.
ComplexityO(n) time, O(n) space — Still one pass over n characters, O(n) time, and an O(n) output buffer. The cost is in correctness, not asymptotics — the branchy wrap is easy to get wrong.
E · Edge cases and pitfalls3Skipping k %= 26, so a single subtraction can't undo a multi-alphabet overshoot.Explore edge casesHide edge cases
Pitfalls
- Skipping k %= 26, so a single subtraction can't undo a multi-alphabet overshoot.
- Using the wrong threshold ('z' vs 'Z') for the case being handled.
- Duplicated branches drifting out of sync between lowercase and uppercase.
Approach 2 of 2 · Optimal
Modular Arithmetic Shift (Optimal)
1Hint 1Explore hintHide hint
Map each letter to a 0-based offset within its alphabet: lowercase offset = c − 'a'. What operation naturally wraps an offset back into 0–25?
2Hint 2Explore hintHide hint
(offset + k) % 26 wraps for free, no matter how far you shift. Then add the base back to return to a real character.
3Hint 3Explore hintHide hint
Reduce k %= 26 once at the start (k can be 10⁹), branch on case, leave non-letters alone.
A · Core idea
For each character, decide its class, then for letters apply one expression: base + (c − base + k) % 26, where base is 'a' or 'A'.
Why subtract the base first. Character codes for 'a'..'z' are 97..122, an awkward range to do arithmetic on. Subtracting 'a' re-centres a lowercase letter onto 0..25, the natural index inside the alphabet. Now + k is a plain shift on those indices.
Why `% 26` is the wrap. After adding k, the index might be ≥ 26 (e.g. 'y' is 24, plus 2 is 26). Taking % 26 folds it back into 0..25 — 26 → 0 is exactly 'y' → 'a''s wrap. The modulo encodes 'past z loops to a' in one operation, and it works for any amount of overshoot, unlike a single subtraction.
Why add the base back. The result of % 26 is an index in 0..25; adding base returns it to a real character code in the correct case. Use 'A' for uppercase so the case is preserved — never mix bases.
Why reduce k mod 26 up front. Shifting by k and by k % 26 are identical on a 26-cycle, so collapsing k first keeps the numbers small (important in fixed-width languages where c − base + 10⁹ could be a concern) and signals you understand the cyclic structure. After that, (c − base + k) % 26 always lands in range with no special cases.
Why non-letters fall through. The branch only fires for a–z and A–Z; everything else is appended unchanged, satisfying 'leave non-letters alone' with no extra logic.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "middle-Outz", k = 2Array state
'a'
(0 + 2) % 26 = 2 → 'c'
okffng-QwbzC · Code with teaching comments
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The three-step idiom — normalise to a 0-based offset, do the arithmetic mod the cycle length, re-base — is the universal recipe for any rotation over a fixed alphabet. ROT13 is k = 13, rotating digits is the same with base '0' and % 10. Recognise the cycle and the modulo writes itself.
ComplexityO(n) time, O(n) space — One pass over the string, constant work per character (a couple of arithmetic ops and a branch), so O(n) time. The output buffer is O(n); the transformation itself uses O(1) extra space.
E · Edge cases and pitfalls7s = "abc", k = 0 — Shift of 0 returns the string unchanged.Explore edge casesHide edge cases
Edge cases handled
s = "abc", k = 0Shift of 0 returns the string unchanged.s = "abc", k = 5252 % 26 = 0 → unchanged, proving the reduce-first step.s = "Zz", k = 1'Z'→'A' and 'z'→'a' — both wrap within their own case.s = "123 #!", k = 5No letters → output identical to input.
Pitfalls
- Omitting % 26 so 'y' + 5 becomes a punctuation code instead of 'd'.
- Using a single base for both cases, flipping uppercase to lowercase.
- Forgetting to reduce k, letting a 10⁹ shift bloat arithmetic or overflow fixed-width ints.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic String caesarCipher(String s, int k) { k %= 26; StringBuilder out = new StringBuilder(s.length()); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i);Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Missing the modulo (no wrap)
Adding k to the code without % 26 pushes letters near the end of the alphabet into non-letter codes — 'y' + 5 becomes '~' instead of 'd'.
Explore the code comparisonHide the code comparison
Missing the modulo (no wrap)
Adding k to the code without % 26 pushes letters near the end of the alphabet into non-letter codes — 'y' + 5 becomes '~' instead of 'd'.
Don't write this
out.append((char) (c + k));Write this instead
out.append((char) ('a' + (c - 'a' + k) % 26));2Losing case by using one base
Re-basing every letter to 'a' turns uppercase letters into lowercase, breaking the case-preservation rule.
Explore the code comparisonHide the code comparison
Losing case by using one base
Re-basing every letter to 'a' turns uppercase letters into lowercase, breaking the case-preservation rule.
Don't write this
out.append(chr((ord(c) - ord('a') + k) % 26 + ord('a'))) # for ALL lettersWrite this instead
base = ord('a') if c.islower() else ord('A')
out.append(chr((ord(c) - base + k) % 26 + base))3Shifting non-letters
If you don't guard the branch, spaces and punctuation get shifted into other symbols, corrupting the output.
Explore the code comparisonHide the code comparison
Shifting non-letters
If you don't guard the branch, spaces and punctuation get shifted into other symbols, corrupting the output.
Don't write this
out.append(chr((ord(c) - ord('a') + k) % 26 + ord('a'))) # even for ' ' and '-'Write this instead
if c.isalpha():
base = ord('a') if c.islower() else ord('A')
out.append(chr((ord(c) - base + k) % 26 + base))
else:
out.append(c)Revise
Carry the pattern into the interview
Pattern to remember
Per-letter modular shift within its case
Code pattern to memorize
k %= 26
for each c in s:
if 'a' <= c <= 'z': out += chr((c - 'a' + k) % 26 + 'a')
elif 'A' <= c <= 'Z': out += chr((c - 'A' + k) % 26 + 'A')
else: out += c # leave non-letters as-isRules to remember
- 1Map a letter to its 0–25 offset (
c − base), addk, take% 26, then addbaseback — this is the wrap. - 2Use a different base per case:
'a'for lowercase,'A'for uppercase. Mixing them garbles the case. - 3Leave non-letters unchanged — digits, spaces, and punctuation pass straight through.
- 4Reduce
k %= 26up front; a largek(e.g. 10⁹) is otherwise just wasted work and risks overflow in fixed-width arithmetic. - 5Preserve case: never uppercase or lowercase the whole string — shift each letter in its own alphabet.
Use this pattern when
- A fixed rotation/shift over a cyclic alphabet (encrypt, ROT13, rotate digits)
- Each element transforms independently based only on itself and a constant offset
- Wrap-around behaviour ('past z goes to a') is required
- Some elements must be skipped (non-letters) while others are mapped
Choose another pattern when
- The shift varies per position (e.g. Vigenère cipher) → use a key sequence, not one k
- You must decrypt without knowing k → frequency analysis, not a fixed shift
- Characters interact (e.g. transposition ciphers) → not an independent per-char map
- Alphabet isn't 26 letters / isn't cyclic → the % 26 model breaks
Interview takeaway
Rotating over a fixed cyclic alphabet is textbook modular arithmetic: normalise to a 0-based offset, add the shift mod the alphabet size, re-base. Reducing the shift mod 26 first keeps it correct and cheap no matter how large k is.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternModular rotation over a fixed alphabet. Normalise each symbol to a 0-based index, add the shift, take it modulo the alphabet size to wrap, then re-base. Reduce the shift mod the cycle length first. The same recipe powers ROT13, digit rotation, and clock arithmetic.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- ROT13Coming soonThe same shift with k = 13; encryption and decryption are identical.
- Decrypt a Caesar cipherComing soonShift back by 26 − (k % 26), or just call encrypt with that key.
- Vigenère cipherComing soonThe shift varies per position from a repeating key — a sequence of k's, not one.
- Excel Sheet Column TitleComing soonAnother base-conversion-over-letters problem using mod/divide on A–Z.