Solana Rust Interview Questions
Note: These questions test interview performance—concise answers, clear explanations, quick code snippets. Real work involves deeper problem-solving, debugging, reading existing codebases, and iteration. Interview skills ≠ work skills. Use this for interviews, not as a measure of engineering ability.
1. Why can't a program modify an account it doesn't own?
Answer: Every account in Solana has an owner field pointing to a program ID. The runtime enforces that only the owner program can modify account data. This is checked at runtime—if a non-owner tries to write, the transaction fails with IncorrectProgramId.
Why this matters: This is Solana's core security model. Programs own their data. To modify state, you must either be the owner or CPI to the owner program.
Rust snippet:
#![allow(unused)] fn main() { if account.owner != program_id { return Err(ProgramError::IncorrectProgramId); } }
2. How does a DeFi protocol deterministically find a user's vault account?
Answer: Using PDAs (Program Derived Addresses). You derive a deterministic address from seeds + program ID using find_program_address. Same seeds = same address every time.
Why PDAs: They look like public keys but have no private key. Only the program can sign for them, enabling program-controlled state.
Rust snippet:
#![allow(unused)] fn main() { let (vault_pda, bump) = Pubkey::find_program_address( &[b"vault", user.key().as_ref()], program_id ); }
Common pattern: User vaults, escrow accounts, configuration PDAs.
3. What happens if you don't include a required signer in an instruction?
Answer: The transaction fails with MissingRequiredSignature. The runtime checks that every account marked as signer actually signed the transaction.
Why signers matter: Authorization. Payers sign for fees. Account owners sign for debits. Programs require signers for privileged operations.
Missing signer check = vulnerability:
#![allow(unused)] fn main() { // BAD: Anyone can call this pub fn withdraw(ctx: Context<Withdraw>) -> Result<()> { // No signer check! let authority = &ctx.accounts.authority; // Transfer from authority to user } // GOOD: Require signer pub fn withdraw(ctx: Context<Withdraw>) -> Result<()> { require!(ctx.accounts.authority.is_signer); // Now only authority can withdraw } }
4. Why do some programs fail with "compute budget exceeded"?
Answer: Solana transactions have a compute budget (200k default, 1.4M max). Every operation costs compute units. Expensive operations (deserialization, CPI, loops) can exceed the limit.
Common culprits:
- Deserializing large accounts (Borsh is expensive)
- CPI calls (each adds overhead)
- Loops over many accounts
- Crypto operations
Optimization strategies:
#![allow(unused)] fn main() { // BAD: Deserialize entire account let account_data: MyAccount = deserialize(&account.data.borrow())?; // BETTER: Only read what you need let count = u64::from_le_bytes( account.data.borrow()[0..8].try_into().unwrap() ); // Request more CU if needed let compute_budget_ix = ComputeBudgetInstruction::request_units(400_000); }
5. How does Jupiter swap tokens without holding them?
Answer: Jupiter doesn't hold tokens. It uses CPI to call DEX programs (Raydium, Orca, etc.) which hold tokens in their pools. Jupiter finds the best route, quotes the price, and executes the swap via CPI.
The flow:
- Jupiter receives swap request
- Quotes across all DEXs
- Finds best route
- CPI to DEX program (e.g., Raydium)
- DEX executes swap from its pools
- Returns result to caller
Rust snippet (simplified):
#![allow(unused)] fn main() { // Jupiter calls DEX via CPI let swap_ix = raydium::swap( from_token_account, to_token_account, amount_in, minimum_amount_out, ); invoke( &swap_ix, &[ from_token_account, to_token_account, dex_program, ], )?; }
Key insight: Jupiter is an aggregator, not a vault. It routes to protocols that hold liquidity.
Quick Reference
Core Solana Concepts:
- Accounts: Everything is an account, programs own accounts
- PDAs: Program-controlled addresses, no private keys
- CPI: Cross-program invocation, programs call other programs
- Signers: Authorization, must sign transactions
- Compute Units: Resource limits, optimize expensive operations
Security Patterns:
- Always check
account.owner == program_id - Always check
account.is_signerfor authorization - Always validate account data before use
- Never trust caller-provided accounts without validation
Performance Patterns:
- Minimize deserialization (use bytemuck or direct byte access)
- Batch operations where possible
- Use parallel account access when safe
- Profile compute unit usage