Junior Software Engineer interview prep: practice questions & salary data
Reviewed by Mark Dickie · Last updated
Junior Software Engineer interviews are assessments of programming fundamentals and basic system thinking. You should expect questions that test your grasp of SQL joins and grouping, Python data structures and common built-in functions, HTTP methods and status codes, simple system design trade-offs, basic prompt engineering and AI API usage, and core AWS services like S3 and EC2. At this level, interviewers care most about whether you can write correct, readable code and reason through a problem out loud.
| Area | What gets tested |
|---|---|
| Databases & SQL | Joins, aggregations, GROUP BY, basic schema design |
| Python | Data structures, list/dict comprehensions, error handling |
| HTTP & APIs | REST verbs, status codes, request/response lifecycle |
| System Design | Trade-offs in simple architectures, caching basics, read/write patterns |
| AI Engineering | Calling LLM APIs, prompt construction, token and cost basics |
| AWS | S3, EC2, IAM basics, common deployment patterns |
How do you prepare for a Junior Software Engineer interview?
- Start with Python and SQL. These two areas carry the most weight at junior level. Practice writing queries by hand and solving coding problems with lists, dicts, and sets until they feel automatic.
- Move to HTTP & APIs next. Know what each REST verb does, when to use 200 vs 400 vs 500, and how to structure a JSON request and response.
- Brush up on system design basics. You won't be asked to design a distributed system from scratch, but you should be able to talk through a simple web app's components and where caching or a queue might help.
- Cover AI engineering fundamentals. Learn how to call an LLM API, what a token is, and how prompt phrasing changes output quality.
- Finish with AWS essentials. Learn what S3, EC2, and IAM do at a basic level and how they fit together in a typical deployment.
What salary and demand should a Junior Software Engineer expect?
The data below reflects current market compensation and hiring demand for Junior Software Engineers, pulled from live job listings and salary databases. Use it to set your expectations before you start negotiating.
What to study, in order
For a junior Software Engineer interview, prioritise the role's most in-demand technologies first:
- Databases & SQL
- Python
- HTTP & APIs
- System Design
- AI Engineering
- AWS
What do Software Engineer roles pay in 2026?
From live job postings, the median Software Engineer salary is £70,000, across 6,452 postings in August 2026. These figures are role-wide across all seniority levels, as of August 2026.
| 25th percentile | £55,000 |
|---|---|
| Median (50th percentile) | £70,000 |
| 75th percentile | £95,000 |
Advertised base salary, from 894 job postings with pay data, as of September 2026.
We need 3 complete months of tracking before we publish a month-by-month series. Months we only partly covered are left out rather than shown as low demand.
Practice questions
AI Engineering/agents/agent-loops
Arrange the steps of a standard LLM agent loop in the correct execution order, starting from the beginning of one iteration.#
Put these in order
- Receive observation / tool result
- Check stopping condition (is task complete?)
- LLM reasons over current context and selects an action
- Execute the chosen action / call the tool
- Append observation to context and start next iteration
Show answer
In a standard LLM agent loop, the correct execution order is: (1) the LLM reasons over the current context and selects an action, (2) that action or tool call is executed, (3) the agent receives the observation or tool result, (4) a stopping condition is checked to see if the task is complete, and (5) if not done, the observation is appended to context and the next iteration begins. This repeating think-act-observe cycle is the core pattern behind frameworks like ReAct.
An agent loop (also called a ReAct loop or think-act loop) follows a repeated cycle: the agent observes the current state/context, reasons or thinks about what to do, takes an action (e.g., calls a tool), receives an observation back, and repeats until a stopping condition is met. This is the fundamental pattern behind virtually all LLM-based agent frameworks.
AWS/aws-networking/vpc
What is an Amazon VPC, and what are its main building blocks?#
Show answer
A Virtual Private Cloud is a logically isolated virtual network you define inside a Region, with an IP address range you choose (a CIDR block). You carve it into subnets, each living in a single Availability Zone; a public subnet routes to the internet via an Internet Gateway, a private subnet reaches the internet only outbound through a NAT Gateway. Route tables control where traffic goes, and security groups (stateful, instance-level) plus network ACLs (stateless, subnet-level) control what traffic is allowed. It's the network boundary your EC2 instances, RDS databases, and other resources run inside.
A VPC is the foundational networking layer almost every other AWS resource sits in. The pieces that matter in interviews: it's Region-scoped with subnets pinned to one AZ each, public vs private subnets are defined by their route to an Internet Gateway vs a NAT Gateway, and security groups (stateful) vs NACLs (stateless) are the two filtering layers. Being fuzzy on subnets-are-AZ-scoped or on the IGW-vs-NAT distinction is a common tell of someone who hasn't actually built a network.
Databases & SQL/db-performance/query-planning
A query planner is choosing how to execute the following SQL query on a large orders table that has an index on the customer_id column:#
Options
- Sequential scan (full table scan)
- Index scan using the
customer_idindex - Hash join
- Merge sort scan
Show answer
The planner will most likely choose an index scan using the customer_id index. Because only ~0.1% of rows match, the index lets the database jump directly to those rows via the B-tree, avoiding a full table scan. Sequential scans are preferred only when a large percentage of rows would be returned, making random I/O from the index costlier than reading pages sequentially.
When a query filters on an indexed column and the selectivity is very high (only ~0.1% of rows match), the query planner will almost always prefer an index scan. An index scan navigates the B-tree to quickly locate the matching rows without reading the entire table. A sequential scan reads every page in the table and is preferred only when a large fraction of rows are expected to match, making random I/O from an index scan more expensive than sequential I/O. Hash join and merge sort scan are join strategies, not applicable to a single-table lookup.
HTTP & APIs/api-design
An HTTP PUT request is idempotent, meaning that making the same PUT request multiple times should produce the same result as making it once.#
Options
- True
- False
Show answer
True — HTTP PUT is idempotent. Sending the same PUT request multiple times leaves the server in the same state as sending it once, because PUT replaces (or creates) the resource at the specified URL with the given data each time. This contrasts with POST, which typically creates a new resource on every call.
Idempotency means that repeating the same operation multiple times yields the same server state as performing it once. PUT is defined as idempotent by the HTTP specification: sending PUT /users/1 with the same body replaces the resource each time, leaving the server in the identical state after the first call. This is unlike POST, which typically creates a new resource on each request and is therefore NOT idempotent.
Python/typing/type-hints
Which of the following is the correct way to annotate a function parameter items that accepts a list of integers in Python 3.9+, using built-in generics (no typing import needed)?#
Options
items: List[int]items: list[int]items: list(int)items: Array[int]
Show answer
The correct annotation is items: list[int]. Since Python 3.9, built-in types like list, dict, and tuple support subscripting directly in type hints, so no from typing import List is needed. list(int) is a runtime call, not an annotation, and Array[int] is not a standard Python type.
Since Python 3.9, built-in collection types like list, dict, tuple, and set can be used directly as generic types (e.g., list[int]), eliminating the need to import List from the typing module. List[int] is the older style (still valid but requires from typing import List). list(int) is a runtime call, not a type hint, and Array[int] does not exist in standard Python.
System Design/sd-architecture/microservices
In a microservices architecture, Service A needs to call Service B to complete a user request. Service B is temporarily unavailable. Which pattern is most appropriate to prevent Service A from exhausting its own resources (e.g., thread pools) while Service B is down?#
Options
- Retry pattern — keep retrying Service B with exponential back-off until it responds.
- Circuit Breaker pattern — detect repeated failures and stop forwarding calls to Service B until it recovers.
- Saga pattern — coordinate a series of local transactions to undo side effects across services.
- Sidecar pattern — deploy a proxy container alongside Service A to handle all network traffic.
Show answer
The Circuit Breaker pattern is the most appropriate choice. It tracks failure rates to Service B and, once a threshold is exceeded, 'opens' the circuit so subsequent calls fail fast instead of blocking. This prevents Service A's thread pools from being exhausted waiting for a slow or unreachable dependency, allowing the system to degrade gracefully rather than cascade-fail.
The Circuit Breaker pattern monitors call failures to a downstream service and, after a threshold is crossed, 'opens' the circuit so that further calls fail immediately rather than waiting for a timeout. This prevents thread pools and other resources in the calling service from being exhausted while the dependency is down. Retries alone worsen the situation by consuming more resources. The Saga and Sidecar patterns address different concerns (distributed transactions and cross-cutting concerns, respectively).
AI Engineering/agents/agent-memory
An AI agent is having a conversation with a user. It needs to remember what the user said five messages ago in order to answer the current question correctly. Which type of agent memory is primarily responsible for this capability?#
Options
- Semantic memory — storing factual knowledge learned during training
- In-context (working) memory — the messages held in the active prompt/context window
- Episodic memory — long-term storage of past interaction summaries retrieved via search
- Procedural memory — stored instructions for how the agent should behave
Show answer
In-context (working) memory is responsible. It holds the entire active conversation thread — all prior user and assistant messages — inside the LLM's context window. As long as the five-message-ago exchange hasn't been truncated or evicted, the model can reference it directly without any external retrieval step.
In-context (working) memory is the portion of the LLM's context window that holds the current conversation history, including prior user and assistant messages. Recalling a message from five turns ago is a straightforward in-context retrieval — no external database lookup is required. Episodic memory refers to persisted, retrievable logs of past sessions, semantic memory to general world knowledge, and procedural memory to behavioural rules or skills.
AWS/aws-databases/dynamodb
DynamoDB's Time to Live (TTL) feature automatically deletes expired items, typically within 48 hours of their TTL timestamp passing, and this background deletion doesn't consume any of the table's provisioned write capacity.#
Options
- True
- False
Show answer
True. DynamoDB's TTL feature deletes expired items through its own background process, and AWS documents that items are typically removed within 48 hours of their TTL timestamp passing, though the exact timing isn't guaranteed. Because the deletion is system-initiated rather than an application write, it consumes no provisioned write capacity, which is why TTL is a standard way to prune session data, cache entries, or other time-bounded records cheaply at scale. Items that have expired but haven't yet been deleted still appear in normal reads, queries, and scans, and still count toward storage size until they're actually removed.
True. TTL deletion is a background process DynamoDB runs itself; AWS's documented expectation is that items are typically removed within 48 hours of expiring, though the exact timing isn't guaranteed and depends on the table's size and activity. Because it's a system-initiated deletion rather than an application write, it doesn't consume the table's provisioned WCUs — this is why TTL is commonly used to cost-effectively prune session data, cache entries, or time-bounded records at scale. Until an item is actually deleted, it's still visible to normal reads, queries, and scans (unless the app filters it out), and it still counts toward storage size and billing during that window.
Databases & SQL/schema-design/normalization
A orders table has a single-column primary key order_id and stores the following columns:#
Options
- First Normal Form (1NF) — the table contains repeating groups of columns.
- Second Normal Form (2NF) — non-key columns depend only on part of a composite primary key.
- Third Normal Form (3NF) — non-key columns (
customer_name,customer_email,product_name) are transitively dependent onorder_idthroughcustomer_idandproduct_id. - Boyce-Codd Normal Form (BCNF) — there are multiple overlapping candidate keys.
Show answer
Third Normal Form (3NF) is violated. Because order_id is the sole primary key, 2NF is automatically satisfied. However, non-key columns like customer_name and customer_email are determined by customer_id (not directly by order_id), and product_name is determined by product_id — these are transitive dependencies, which violate 3NF.
Because order_id is explicitly the sole primary key, there is no composite key, so 2NF is automatically satisfied — every non-key column depends on the full (single-column) key. The table is not in 3NF, however, because of transitive dependencies: order_id → customer_id → {customer_name, customer_email} and order_id → product_id → product_name. In both chains, an intermediate non-key attribute (customer_id or product_id) determines other non-key attributes, which is the classic 3NF violation. The fix is to decompose the table into separate customers and products tables, keeping only the foreign keys customer_id and product_id in orders.
HTTP & APIs/api-auth/oauth-basics
In OAuth 2.0, a refresh token can be used directly as a Bearer token to access a protected resource on the resource server.#
Options
- True
- False
Show answer
No, a refresh token cannot be used directly to access a protected resource. Refresh tokens are presented only to the authorization server's token endpoint to obtain a new access token. The resource server only accepts access tokens (typically as a Bearer token in the Authorization header), not refresh tokens.
Refresh tokens are credentials used exclusively with the authorization server's token endpoint to obtain new access tokens (and optionally new refresh tokens) when the current access token expires. They are never sent to the resource server. Only access tokens are presented to the resource server as Bearer tokens in the Authorization header.
Python/testing-idioms/pytest
What is printed to the console when the following pytest test file is collected and run with pytest -v (assume pytest is installed and the file is named test_example.py)?#
# Conceptual — run mentally, not as a Python snippet
# pytest test_example.py -v
# test_pass -> passes
# test_fail -> fails (AssertionError: assert 2 == 3)
# test_skip -> skipped via pytest.skip()
print("1 passed, 1 failed, 1 skipped")Show answer
1 passed, 1 failed, 1 skipped
pytest runs all three test functions. test_pass succeeds because 1+1 == 2 is True. test_fail raises an AssertionError because 1+1 == 3 is False, so it counts as a failure. test_skip calls pytest.skip(), which raises pytest.skip.Exception internally and marks the test as skipped rather than failed or passed. The final summary line therefore reads 1 passed, 1 failed, 1 skipped.
System Design/sd-fundamentals/scalability
A startup's web application runs on a single server. As traffic grows, the team decides to add more servers and distribute incoming requests across them using a load balancer. Which scalability strategy does this best describe?#
Options
- Vertical scaling (scaling up)
- Horizontal scaling (scaling out)
- Database sharding
- Cache invalidation
Show answer
Horizontal scaling (scaling out) is the correct answer. This strategy involves adding more servers/instances and using a load balancer to distribute traffic among them, rather than upgrading a single machine's resources (which would be vertical scaling). It allows a system to handle growing traffic by simply adding commodity machines.
Horizontal scaling (scaling out) means adding more machines/instances to share the load, as opposed to vertical scaling (scaling up), which means upgrading the existing machine's CPU, RAM, or storage. Using a load balancer to distribute traffic across multiple servers is the canonical example of horizontal scaling. Database sharding and cache invalidation are separate, more specific techniques.
AI Engineering/evaluation-safety/guardrails
An LLM-based chatbot occasionally generates responses containing personally identifiable information (PII) that was present in its training data. Which guardrail strategy most directly mitigates this specific risk at inference time?#
Options
- Increase the model's temperature to 0 so outputs are deterministic.
- Apply a post-generation output filter that detects and redacts PII patterns (e.g., regex + NER) before returning the response to the user.
- Use a larger model with more parameters, since larger models hallucinate less.
- Rate-limit API requests to reduce the number of potentially harmful responses per minute.
Show answer
Applying a post-generation output filter that detects and redacts PII patterns (e.g., regex + NER) before returning the response is the most direct mitigation. This approach inspects the actual generated text at inference time and removes sensitive information before it reaches the user, regardless of what the model memorized during training. The other options do not address the content of the output itself.
A post-generation output filter that combines regex patterns and Named Entity Recognition (NER) is the most direct mitigation at inference time because it inspects and sanitizes the actual text the model has produced before it reaches the user. Lowering temperature only changes randomness, larger models still memorize and reproduce PII, and rate-limiting reduces volume but does not block PII content.
AWS/aws-compute/auto-scaling
Which of the following can be used to determine when an Amazon EC2 Auto Scaling Group should scale out or scale in? (Select all that apply.)#
Options
- A CloudWatch alarm based on aggregate CPU utilization of the instances in the group
- Application Load Balancer metrics such as RequestCountPerTarget
- Amazon S3 bucket object-creation events sent directly to the Auto Scaling Group
- Changes to the IAM account password policy
Show answer
A CloudWatch alarm based on CPU utilization and Application Load Balancer metrics such as RequestCountPerTarget can both trigger an EC2 Auto Scaling Group to scale. Auto Scaling relies on CloudWatch alarms to drive scaling policies, and those alarms may use EC2 instance metrics or metrics from attached load balancers. S3 events and IAM password-policy changes are not scaling triggers.
EC2 Auto Scaling policies are driven by Amazon CloudWatch alarms, and those alarms can be based on EC2 instance metrics (like CPU utilization) or on metrics from integrated services such as an Application Load Balancer (e.g., RequestCountPerTarget). S3 events and IAM password-policy changes are not signals that an Auto Scaling Group listens to, and neither can directly trigger a scaling activity.
Databases & SQL/transactions/acid
Which ACID property guarantees that once a transaction has been committed, its changes will survive even a subsequent system crash?#
Options
- Atomicity
- Consistency
- Isolation
- Durability
Show answer
Durability is the ACID property that guarantees committed changes survive system crashes. Once a transaction is committed, the database engine persists the changes to non-volatile storage (commonly via a write-ahead log), so even if the system crashes immediately after the commit, the data will be recovered correctly on restart.
Durability is the 'D' in ACID. It ensures that committed transactions are permanently recorded, typically by writing to non-volatile storage (e.g., a write-ahead log or data files), so data survives crashes or power failures. Atomicity ensures all-or-nothing execution, Consistency ensures valid state transitions, and Isolation controls how concurrent transactions see each other's data.