Большинство компаний используют рубрику -- структурированную систему оценки с конкретными критериями и шкалой. Это обеспечивает объективность и последовательность оценки между разными интервьюерами.
Типичная шкала
Оценка
Описание
Решение
Strong Hire
Превосходит ожидания для уровня
Однозначный офер
Hire
Соответствует ожиданиям
Офер с высокой вероятностью
Lean Hire
Скорее да, с оговорками
Зависит от других интервью
Lean No Hire
Скорее нет, но есть плюсы
Скорее отказ
No Hire
Не соответствует уровню
Однозначный отказ
Основные критерии оценки
1. Problem Exploration (Исследование проблемы)
Вес: 15-20%
Оценивается способность понять задачу до начала решения.
<?php
declare(strict_types=1);
/**
* Problem exploration scoring examples
*/
// Score 1-2: Jumps straight to solution
// "OK, let me draw the architecture for the notification system..."
// Score 3: Asks basic questions
// "How many users do we have?"
// "What types of notifications?"
// Score 4: Structured requirement gathering
// "Let me understand the functional requirements first..."
// "What are our SLAs for delivery?"
// "What's the read/write ratio?"
// Score 5: Deep exploration with business context
// "What's the business impact of delayed notifications?"
// "Are there regulatory requirements (GDPR, CAN-SPAM)?"
// "What's our budget for third-party services (Twilio, SendGrid)?"
/**
* What a score-5 candidate does during problem exploration.
* They create a structured requirements document.
*/
final readonly class RequirementsDocument
{
public function __construct(
// Functional Requirements
/** @var array<string> */
public array $mustHave,
/** @var array<string> */
public array $niceToHave,
/** @var array<string> */
public array $outOfScope,
// Non-Functional Requirements
public int $targetLatencyMs,
public float $availabilityTarget,
public int $dailyActiveUsers,
public int $peakQps,
// Constraints
public float $budgetPerMonth,
public array $existingInfrastructure,
public array $complianceRequirements,
) {}
}
package interview
// Problem exploration scoring examples:
//
// Score 1-2: Jumps straight to solution
// "OK, let me draw the architecture for the notification system..."
//
// Score 3: Asks basic questions
// "How many users do we have?"
//
// Score 4: Structured requirement gathering
// "Let me understand the functional requirements first..."
//
// Score 5: Deep exploration with business context
// "What's the business impact of delayed notifications?"
// "Are there regulatory requirements (GDPR, CAN-SPAM)?"
// RequirementsDocument is what a score-5 candidate creates.
type RequirementsDocument struct {
// Functional Requirements
MustHave []string
NiceToHave []string
OutOfScope []string
// Non-Functional Requirements
TargetLatencyMs int
AvailabilityTarget float64
DailyActiveUsers int
PeakQPS int
// Constraints
BudgetPerMonth float64
ExistingInfrastructure []string
ComplianceRequirements []string
}
namespace Interview;
// Problem exploration scoring examples:
//
// Score 1-2: Jumps straight to solution
// "OK, let me draw the architecture for the notification system..."
//
// Score 3: Asks basic questions
// "How many users do we have?"
//
// Score 4: Structured requirement gathering
// "Let me understand the functional requirements first..."
//
// Score 5: Deep exploration with business context
// "What's the business impact of delayed notifications?"
// "Are there regulatory requirements (GDPR, CAN-SPAM)?"
// RequirementsDocument is what a score-5 candidate creates.
// A positional record gives immutability for free.
public sealed record RequirementsDocument(
// Functional Requirements
IReadOnlyList<string> MustHave,
IReadOnlyList<string> NiceToHave,
IReadOnlyList<string> OutOfScope,
// Non-Functional Requirements
int TargetLatencyMs,
double AvailabilityTarget,
int DailyActiveUsers,
int PeakQps,
// Constraints
decimal BudgetPerMonth,
IReadOnlyList<string> ExistingInfrastructure,
IReadOnlyList<string> ComplianceRequirements);
from dataclasses import dataclass, field
# Problem exploration scoring examples:
#
# Score 1-2: Jumps straight to solution
# "OK, let me draw the architecture for the notification system..."
#
# Score 3: Asks basic questions
# "How many users do we have?"
#
# Score 4: Structured requirement gathering
# "Let me understand the functional requirements first..."
#
# Score 5: Deep exploration with business context
# "What's the business impact of delayed notifications?"
# "Are there regulatory requirements (GDPR, CAN-SPAM)?"
@dataclass(frozen=True, slots=True)
class RequirementsDocument:
"""What a score-5 candidate creates during problem exploration."""
# Functional Requirements
must_have: tuple[str, ...]
nice_to_have: tuple[str, ...]
out_of_scope: tuple[str, ...]
# Non-Functional Requirements
target_latency_ms: int
availability_target: float
daily_active_users: int
peak_qps: int
# Constraints
budget_per_month: float
existing_infrastructure: tuple[str, ...] = field(default=())
compliance_requirements: tuple[str, ...] = field(default=())
### 2. High-Level Design (Высокоуровневый дизайн)
Вес: 20-25%
Способность спроектировать архитектуру с правильным набором компонентов.
Что оценивают:
Правильный выбор компонентов
Логичные связи между ними
Соответствие требованиям
Разделение ответственности
<?php
declare(strict_types=1);
/**
* High-level design scoring
*
* Score 1-2: Missing critical components
* "We have a web server and a database"
* (No caching, no queue, no CDN for a high-traffic system)
*
* Score 3: Correct components, weak reasoning
* "We need Redis for caching, RabbitMQ for messaging"
* (Right choices, but can't explain why)
*
* Score 4: Well-reasoned architecture
* Explains data flow, failure modes, scaling points
*
* Score 5: Anticipates future needs
* Designs for evolution, considers operational aspects
*/
// Example: How a strong candidate describes a component
final class NotificationArchitectureNarration
{
public function describe(): string
{
return <<<'TEXT'
The notification system has 5 major components:
1. API Gateway - receives notification requests, validates,
rate-limits, and routes to the ingestion service.
Why: Decouples clients from internal topology.
2. Priority Router - classifies notifications by priority
and routes to appropriate queues.
Why: Urgent notifications (password reset) should not
wait behind marketing emails.
3. Channel Workers - separate worker pools per channel
(email, SMS, push). Each pool scales independently.
Why: SMS costs $0.01/msg, email is nearly free.
Different scaling economics.
4. User Preference Service - stores delivery preferences,
opt-outs, quiet hours. Cached in Redis.
Why: Must check preferences before every send
to comply with regulations.
5. Delivery Tracker - records delivery status, handles
retries, and provides analytics.
Why: Need to know if notifications actually arrive.
TEXT;
}
}
package interview
// High-level design scoring:
//
// Score 1-2: Missing critical components
// "We have a web server and a database"
//
// Score 3: Correct components, weak reasoning
// "We need Redis for caching, RabbitMQ for messaging"
//
// Score 4: Well-reasoned architecture
// Explains data flow, failure modes, scaling points
//
// Score 5: Anticipates future needs
// Designs for evolution, considers operational aspects
// NotificationArchitectureNarration describes how a strong
// candidate presents the system components.
type NotificationArchitectureNarration struct{}
// Describe returns a narration of the notification system architecture.
func (n *NotificationArchitectureNarration) Describe() string {
return `The notification system has 5 major components:
1. API Gateway - receives notification requests, validates,
rate-limits, and routes to the ingestion service.
Why: Decouples clients from internal topology.
2. Priority Router - classifies notifications by priority
and routes to appropriate queues.
Why: Urgent notifications (password reset) should not
wait behind marketing emails.
3. Channel Workers - separate worker pools per channel
(email, SMS, push). Each pool scales independently.
Why: SMS costs $0.01/msg, email is nearly free.
Different scaling economics.
4. User Preference Service - stores delivery preferences,
opt-outs, quiet hours. Cached in Redis.
Why: Must check preferences before every send
to comply with regulations.
5. Delivery Tracker - records delivery status, handles
retries, and provides analytics.
Why: Need to know if notifications actually arrive.`
}
namespace Interview;
// High-level design scoring:
//
// Score 1-2: Missing critical components
// "We have a web server and a database"
//
// Score 3: Correct components, weak reasoning
// "We need Redis for caching, RabbitMQ for messaging"
//
// Score 4: Well-reasoned architecture
// Explains data flow, failure modes, scaling points
//
// Score 5: Anticipates future needs
// Designs for evolution, considers operational aspects
// NotificationArchitectureNarration describes how a strong
// candidate presents the system components.
public sealed class NotificationArchitectureNarration
{
// Raw string literal keeps the narration free of escaping.
public string Describe() =>
"""
The notification system has 5 major components:
1. API Gateway - receives notification requests, validates,
rate-limits, and routes to the ingestion service.
Why: Decouples clients from internal topology.
2. Priority Router - classifies notifications by priority
and routes to appropriate queues.
Why: Urgent notifications (password reset) should not
wait behind marketing emails.
3. Channel Workers - separate worker pools per channel
(email, SMS, push). Each pool scales independently.
Why: SMS costs $0.01/msg, email is nearly free.
Different scaling economics.
4. User Preference Service - stores delivery preferences,
opt-outs, quiet hours. Cached in Redis.
Why: Must check preferences before every send
to comply with regulations.
5. Delivery Tracker - records delivery status, handles
retries, and provides analytics.
Why: Need to know if notifications actually arrive.
""";
}
import textwrap
# High-level design scoring:
#
# Score 1-2: Missing critical components
# "We have a web server and a database"
#
# Score 3: Correct components, weak reasoning
# "We need Redis for caching, RabbitMQ for messaging"
#
# Score 4: Well-reasoned architecture
# Explains data flow, failure modes, scaling points
#
# Score 5: Anticipates future needs
# Designs for evolution, considers operational aspects
class NotificationArchitectureNarration:
"""How a strong candidate presents the system components."""
def describe(self) -> str:
# dedent keeps the source indented while the output stays flush left
return textwrap.dedent(
"""\
The notification system has 5 major components:
1. API Gateway - receives notification requests, validates,
rate-limits, and routes to the ingestion service.
Why: Decouples clients from internal topology.
2. Priority Router - classifies notifications by priority
and routes to appropriate queues.
Why: Urgent notifications (password reset) should not
wait behind marketing emails.
3. Channel Workers - separate worker pools per channel
(email, SMS, push). Each pool scales independently.
Why: SMS costs $0.01/msg, email is nearly free.
Different scaling economics.
4. User Preference Service - stores delivery preferences,
opt-outs, quiet hours. Cached in Redis.
Why: Must check preferences before every send
to comply with regulations.
5. Delivery Tracker - records delivery status, handles
retries, and provides analytics.
Why: Need to know if notifications actually arrive."""
)
### 3. Detailed Design (Детальный дизайн)
Вес: 25-30%
Глубина понимания критических компонентов.
<?php
declare(strict_types=1);
/**
* Detailed design: strong candidate dives deep into one component
*
* Example: Priority queue routing with dead letter handling
*/
final class PriorityRouter
{
/** @var array<string, QueueConfig> */
private array $queues;
public function __construct(
private readonly MessageBroker $broker,
private readonly MetricsCollector $metrics,
) {
$this->queues = [
'urgent' => new QueueConfig(
name: 'notifications.urgent',
workers: 20,
maxRetries: 5,
retryDelayMs: 1000,
deadLetterQueue: 'notifications.urgent.dlq',
),
'high' => new QueueConfig(
name: 'notifications.high',
workers: 10,
maxRetries: 3,
retryDelayMs: 5000,
deadLetterQueue: 'notifications.high.dlq',
),
'normal' => new QueueConfig(
name: 'notifications.normal',
workers: 5,
maxRetries: 3,
retryDelayMs: 30000,
deadLetterQueue: 'notifications.normal.dlq',
),
'low' => new QueueConfig(
name: 'notifications.low',
workers: 2,
maxRetries: 2,
retryDelayMs: 60000,
deadLetterQueue: 'notifications.low.dlq',
),
];
}
public function route(NotificationMessage $message): void
{
$queue = $this->queues[$message->priority->value]
?? $this->queues['normal'];
$this->broker->publish($queue->name, $message->serialize(), [
'max_retries' => $queue->maxRetries,
'retry_delay' => $queue->retryDelayMs,
'dead_letter' => $queue->deadLetterQueue,
'message_id' => $message->id, // For deduplication
]);
$this->metrics->record('notification.routed', 1, [
'priority' => $message->priority->value,
'channel' => $message->channel->value,
]);
}
}
final readonly class QueueConfig
{
public function __construct(
public string $name,
public int $workers,
public int $maxRetries,
public int $retryDelayMs,
public string $deadLetterQueue,
) {}
}
package interview
import "context"
// QueueConfig holds configuration for a priority-specific queue.
type QueueConfig struct {
Name string
Workers int
MaxRetries int
RetryDelayMs int
DeadLetterQueue string
}
// PriorityRouter routes notifications to priority-specific queues
// with dead letter handling.
type PriorityRouter struct {
broker MessageBroker
metrics MetricsCollector
queues map[string]QueueConfig
}
func NewPriorityRouter(broker MessageBroker, metrics MetricsCollector) *PriorityRouter {
return &PriorityRouter{
broker: broker,
metrics: metrics,
queues: map[string]QueueConfig{
"urgent": {Name: "notifications.urgent", Workers: 20, MaxRetries: 5, RetryDelayMs: 1000, DeadLetterQueue: "notifications.urgent.dlq"},
"high": {Name: "notifications.high", Workers: 10, MaxRetries: 3, RetryDelayMs: 5000, DeadLetterQueue: "notifications.high.dlq"},
"normal": {Name: "notifications.normal", Workers: 5, MaxRetries: 3, RetryDelayMs: 30000, DeadLetterQueue: "notifications.normal.dlq"},
"low": {Name: "notifications.low", Workers: 2, MaxRetries: 2, RetryDelayMs: 60000, DeadLetterQueue: "notifications.low.dlq"},
},
}
}
func (r *PriorityRouter) Route(ctx context.Context, msg NotificationMessage) error {
q, ok := r.queues[msg.Priority]
if !ok {
q = r.queues["normal"]
}
err := r.broker.Publish(ctx, q.Name, msg.Serialize(), map[string]any{
"max_retries": q.MaxRetries,
"retry_delay": q.RetryDelayMs,
"dead_letter": q.DeadLetterQueue,
"message_id": msg.ID, // For deduplication
})
if err != nil {
return err
}
r.metrics.Record("notification.routed", 1, map[string]string{
"priority": msg.Priority,
"channel": msg.Channel,
})
return nil
}
namespace Interview;
// Detailed design: strong candidate dives deep into one component.
// Example: priority queue routing with dead letter handling.
public sealed record QueueConfig(
string Name,
int Workers,
int MaxRetries,
int RetryDelayMs,
string DeadLetterQueue);
public sealed class PriorityRouter
{
private readonly IMessageBroker _broker;
private readonly IMetricsCollector _metrics;
private readonly Dictionary<string, QueueConfig> _queues;
public PriorityRouter(IMessageBroker broker, IMetricsCollector metrics)
{
_broker = broker;
_metrics = metrics;
_queues = new Dictionary<string, QueueConfig>
{
["urgent"] = new("notifications.urgent", 20, 5, 1000, "notifications.urgent.dlq"),
["high"] = new("notifications.high", 10, 3, 5000, "notifications.high.dlq"),
["normal"] = new("notifications.normal", 5, 3, 30000, "notifications.normal.dlq"),
["low"] = new("notifications.low", 2, 2, 60000, "notifications.low.dlq"),
};
}
public async Task RouteAsync(NotificationMessage message, CancellationToken ct = default)
{
var queue = _queues.TryGetValue(message.Priority, out var cfg)
? cfg
: _queues["normal"];
await _broker.PublishAsync(queue.Name, message.Serialize(), new Dictionary<string, object>
{
["max_retries"] = queue.MaxRetries,
["retry_delay"] = queue.RetryDelayMs,
["dead_letter"] = queue.DeadLetterQueue,
["message_id"] = message.Id, // For deduplication
}, ct);
_metrics.Record("notification.routed", 1, new Dictionary<string, string>
{
["priority"] = message.Priority,
["channel"] = message.Channel,
});
}
}
from dataclasses import dataclass
# Detailed design: strong candidate dives deep into one component.
# Example: priority queue routing with dead letter handling.
@dataclass(frozen=True, slots=True)
class QueueConfig:
name: str
workers: int
max_retries: int
retry_delay_ms: int
dead_letter_queue: str
class PriorityRouter:
def __init__(self, broker: MessageBroker, metrics: MetricsCollector) -> None:
self._broker = broker
self._metrics = metrics
self._queues: dict[str, QueueConfig] = {
"urgent": QueueConfig("notifications.urgent", 20, 5, 1_000, "notifications.urgent.dlq"),
"high": QueueConfig("notifications.high", 10, 3, 5_000, "notifications.high.dlq"),
"normal": QueueConfig("notifications.normal", 5, 3, 30_000, "notifications.normal.dlq"),
"low": QueueConfig("notifications.low", 2, 2, 60_000, "notifications.low.dlq"),
}
async def route(self, message: NotificationMessage) -> None:
queue = self._queues.get(message.priority, self._queues["normal"])
await self._broker.publish(
queue.name,
message.serialize(),
{
"max_retries": queue.max_retries,
"retry_delay": queue.retry_delay_ms,
"dead_letter": queue.dead_letter_queue,
"message_id": message.id, # For deduplication
},
)
self._metrics.record(
"notification.routed",
1,
{"priority": message.priority, "channel": message.channel},
)
### 4. Trade-off Analysis (Анализ компромиссов)
Вес: 15-20%
Способность видеть альтернативы и обосновывать выбор.
<?php
declare(strict_types=1);
/**
* Communication scoring rubric
*/
final class CommunicationEvaluation
{
// Score 1-2: Poor communication
// - Long pauses without explanation
// - Jumps between topics randomly
// - Doesn't respond to interviewer's hints
// - Uses jargon without explaining
// Score 3: Adequate communication
// - Explains decisions when asked
// - Some structure in presentation
// - Responds to questions
// Score 4: Good communication
// - Thinks aloud consistently
// - Clear structure and transitions
// - Proactively discusses alternatives
// - Engages with interviewer feedback
// Score 5: Excellent communication
// - Like a collaborative design session with a colleague
// - Draws interviewer into discussion
// - Summarizes before moving to next topic
// - Adjusts depth based on interviewer's interest
}
package interview
// Communication scoring rubric:
//
// Score 1-2: Poor communication
// - Long pauses without explanation
// - Jumps between topics randomly
// - Doesn't respond to interviewer's hints
// - Uses jargon without explaining
//
// Score 3: Adequate communication
// - Explains decisions when asked
// - Some structure in presentation
// - Responds to questions
//
// Score 4: Good communication
// - Thinks aloud consistently
// - Clear structure and transitions
// - Proactively discusses alternatives
// - Engages with interviewer feedback
//
// Score 5: Excellent communication
// - Like a collaborative design session with a colleague
// - Draws interviewer into discussion
// - Summarizes before moving to next topic
// - Adjusts depth based on interviewer's interest
// CommunicationEvaluation captures communication scoring.
type CommunicationEvaluation struct {
Score int
Strengths []string
Weaknesses []string
}
namespace Interview;
// Communication scoring rubric:
//
// Score 1-2: Poor communication
// - Long pauses without explanation
// - Jumps between topics randomly
// - Doesn't respond to interviewer's hints
// - Uses jargon without explaining
//
// Score 3: Adequate communication
// - Explains decisions when asked
// - Some structure in presentation
// - Responds to questions
//
// Score 4: Good communication
// - Thinks aloud consistently
// - Clear structure and transitions
// - Proactively discusses alternatives
// - Engages with interviewer feedback
//
// Score 5: Excellent communication
// - Like a collaborative design session with a colleague
// - Draws interviewer into discussion
// - Summarizes before moving to next topic
// - Adjusts depth based on interviewer's interest
// CommunicationEvaluation captures communication scoring.
public sealed record CommunicationEvaluation(
int Score,
IReadOnlyList<string> Strengths,
IReadOnlyList<string> Weaknesses);
from dataclasses import dataclass, field
# Communication scoring rubric:
#
# Score 1-2: Poor communication
# - Long pauses without explanation
# - Jumps between topics randomly
# - Doesn't respond to interviewer's hints
# - Uses jargon without explaining
#
# Score 3: Adequate communication
# - Explains decisions when asked
# - Some structure in presentation
# - Responds to questions
#
# Score 4: Good communication
# - Thinks aloud consistently
# - Clear structure and transitions
# - Proactively discusses alternatives
# - Engages with interviewer feedback
#
# Score 5: Excellent communication
# - Like a collaborative design session with a colleague
# - Draws interviewer into discussion
# - Summarizes before moving to next topic
# - Adjusts depth based on interviewer's interest
@dataclass(frozen=True, slots=True)
class CommunicationEvaluation:
"""Captures communication scoring."""
score: int
strengths: tuple[str, ...] = field(default=())
weaknesses: tuple[str, ...] = field(default=())
## Частые ошибки кандидатов
Ошибка 1: Resume-Driven Design
Кандидат проектирует систему на основе технологий из своего резюме, а не требований задачи.
"Давайте используем Kubernetes, потому что на моем текущем проекте мы его используем."
Ошибка 2: Over-Engineering
Кандидат добавляет сложные компоненты без обоснования.
<?php
// Over-engineered for a system with 1000 users
// "We need event sourcing, CQRS, Kafka, Elasticsearch,
// Kubernetes, service mesh, and a custom CDC pipeline"
// Appropriate for 1000 users
final class SimpleOrderService
{
public function __construct(
private readonly \PDO $db,
private readonly MailerInterface $mailer,
) {}
public function createOrder(array $items, string $userId): int
{
$this->db->beginTransaction();
try {
$stmt = $this->db->prepare(
'INSERT INTO orders (user_id, total, status, created_at)
VALUES (:userId, :total, :status, NOW())
RETURNING id'
);
$total = array_sum(array_column($items, 'price'));
$stmt->execute([
'userId' => $userId,
'total' => $total,
'status' => 'pending',
]);
$orderId = (int) $stmt->fetchColumn();
foreach ($items as $item) {
$this->db->prepare(
'INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES (:orderId, :productId, :quantity, :price)'
)->execute([
'orderId' => $orderId,
'productId' => $item['product_id'],
'quantity' => $item['quantity'],
'price' => $item['price'],
]);
}
$this->db->commit();
// Simple email notification
$this->mailer->send($userId, 'Order confirmed', "Order #$orderId");
return $orderId;
} catch (\Throwable $e) {
$this->db->rollBack();
throw $e;
}
}
}
package interview
import (
"context"
"database/sql"
"fmt"
)
// Over-engineered for a system with 1000 users:
// "We need event sourcing, CQRS, Kafka, Elasticsearch,
// Kubernetes, service mesh, and a custom CDC pipeline"
// SimpleOrderService is appropriate for 1000 users.
type SimpleOrderService struct {
db *sql.DB
mailer Mailer
}
func (s *SimpleOrderService) CreateOrder(ctx context.Context, items []OrderItem, userID string) (int64, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return 0, fmt.Errorf("begin tx: %w", err)
}
defer tx.Rollback()
var total float64
for _, item := range items {
total += item.Price
}
var orderID int64
err = tx.QueryRowContext(ctx,
`INSERT INTO orders (user_id, total, status, created_at)
VALUES ($1, $2, $3, NOW()) RETURNING id`,
userID, total, "pending",
).Scan(&orderID)
if err != nil {
return 0, fmt.Errorf("insert order: %w", err)
}
for _, item := range items {
_, err = tx.ExecContext(ctx,
`INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES ($1, $2, $3, $4)`,
orderID, item.ProductID, item.Quantity, item.Price,
)
if err != nil {
return 0, fmt.Errorf("insert order item: %w", err)
}
}
if err := tx.Commit(); err != nil {
return 0, fmt.Errorf("commit: %w", err)
}
// Simple email notification
_ = s.mailer.Send(ctx, userID, "Order confirmed", fmt.Sprintf("Order #%d", orderID))
return orderID, nil
}
using System.Data.Common;
namespace Interview;
// Over-engineered for a system with 1000 users:
// "We need event sourcing, CQRS, Kafka, Elasticsearch,
// Kubernetes, service mesh, and a custom CDC pipeline"
public sealed record OrderItem(string ProductId, int Quantity, decimal Price);
// SimpleOrderService is appropriate for 1000 users.
public sealed class SimpleOrderService(DbConnection db, IMailer mailer)
{
public async Task<long> CreateOrderAsync(
IReadOnlyList<OrderItem> items,
string userId,
CancellationToken ct = default)
{
await using var tx = await db.BeginTransactionAsync(ct);
var total = items.Sum(i => i.Price);
await using var insertOrder = db.CreateCommand();
insertOrder.Transaction = tx;
insertOrder.CommandText =
"""
INSERT INTO orders (user_id, total, status, created_at)
VALUES (@userId, @total, @status, NOW()) RETURNING id
""";
AddParam(insertOrder, "@userId", userId);
AddParam(insertOrder, "@total", total);
AddParam(insertOrder, "@status", "pending");
var orderId = (long)(await insertOrder.ExecuteScalarAsync(ct)
?? throw new InvalidOperationException("orders INSERT returned no id"));
foreach (var item in items)
{
await using var insertItem = db.CreateCommand();
insertItem.Transaction = tx;
insertItem.CommandText =
"""
INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES (@orderId, @productId, @quantity, @price)
""";
AddParam(insertItem, "@orderId", orderId);
AddParam(insertItem, "@productId", item.ProductId);
AddParam(insertItem, "@quantity", item.Quantity);
AddParam(insertItem, "@price", item.Price);
await insertItem.ExecuteNonQueryAsync(ct);
}
await tx.CommitAsync(ct);
// Simple email notification
await mailer.SendAsync(userId, "Order confirmed", $"Order #{orderId}", ct);
return orderId;
}
private static void AddParam(DbCommand cmd, string name, object value)
{
var p = cmd.CreateParameter();
p.ParameterName = name;
p.Value = value;
cmd.Parameters.Add(p);
}
}
from dataclasses import dataclass
import asyncpg
# Over-engineered for a system with 1000 users:
# "We need event sourcing, CQRS, Kafka, Elasticsearch,
# Kubernetes, service mesh, and a custom CDC pipeline"
@dataclass(frozen=True, slots=True)
class OrderItem:
product_id: str
quantity: int
price: float
class SimpleOrderService:
"""Appropriate for 1000 users."""
def __init__(self, pool: asyncpg.Pool, mailer: Mailer) -> None:
self._pool = pool
self._mailer = mailer
async def create_order(self, items: list[OrderItem], user_id: str) -> int:
total = sum(item.price for item in items)
async with self._pool.acquire() as conn:
# transaction() rolls back automatically if the block raises
async with conn.transaction():
order_id: int = await conn.fetchval(
"""
INSERT INTO orders (user_id, total, status, created_at)
VALUES ($1, $2, $3, NOW()) RETURNING id
""",
user_id,
total,
"pending",
)
await conn.executemany(
"""
INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES ($1, $2, $3, $4)
""",
[(order_id, i.product_id, i.quantity, i.price) for i in items],
)
# Simple email notification
await self._mailer.send(user_id, "Order confirmed", f"Order #{order_id}")
return order_id
Кандидат фокусируется только на функциональности и забывает про:
Мониторинг и алертинг
Обработку ошибок
Graceful degradation
Observability (логи, метрики, трейсы)
Ошибка 4: Неумение управлять временем
Проблема
Последствие
20 минут на requirements
Не успели обсудить детали
25 минут на один компонент
Другие компоненты не покрыты
Нет плана
Хаотичное переключение между темами
Ошибка 5: Оборонительная позиция
Когда интервьюер указывает на проблему в дизайне, слабый кандидат начинает защищать свое решение. Сильный кандидат говорит: "Хорошее замечание. Давайте рассмотрим альтернативу."
Что отличает Strong Hire
Характеристики
Ведет интервью как технический лид на design review
Предвидит проблемы до того, как интервьюер их поднимет
Обосновывает каждое решение числами и опытом
Гибко адаптирует дизайн при изменении требований
Знает пределы своих знаний и честно об этом говорит
Пример: как обсуждать ограничения
<?php
declare(strict_types=1);
/**
* Strong candidate discusses limitations honestly
*
* "The current design has these limitations:
*
* 1. Single region - if the region goes down, entire system is offline.
* To fix: multi-region with cross-region replication.
* Trade-off: 2-3x infrastructure cost.
*
* 2. No message ordering across partitions.
* Users in the same conversation might see messages
* in slightly different order under high load.
* To fix: per-conversation sequence numbers.
*
* 3. File uploads are synchronous.
* For large files, this blocks the API thread.
* To fix: presigned URLs for direct S3 upload.
*
* If I had more time, I would focus on #1 first,
* as availability is our primary non-functional requirement."
*/
package interview
// Strong candidate discusses limitations honestly:
//
// "The current design has these limitations:
//
// 1. Single region - if the region goes down, entire system is offline.
// To fix: multi-region with cross-region replication.
// Trade-off: 2-3x infrastructure cost.
//
// 2. No message ordering across partitions.
// Users in the same conversation might see messages
// in slightly different order under high load.
// To fix: per-conversation sequence numbers.
//
// 3. File uploads are synchronous.
// For large files, this blocks the API thread.
// To fix: presigned URLs for direct S3 upload.
//
// If I had more time, I would focus on #1 first,
// as availability is our primary non-functional requirement."
namespace Interview;
// Strong candidate discusses limitations honestly:
//
// "The current design has these limitations:
//
// 1. Single region - if the region goes down, entire system is offline.
// To fix: multi-region with cross-region replication.
// Trade-off: 2-3x infrastructure cost.
//
// 2. No message ordering across partitions.
// Users in the same conversation might see messages
// in slightly different order under high load.
// To fix: per-conversation sequence numbers.
//
// 3. File uploads are synchronous.
// For large files, this blocks the request thread.
// To fix: presigned URLs for direct S3 upload.
//
// If I had more time, I would focus on #1 first,
// as availability is our primary non-functional requirement."
# Strong candidate discusses limitations honestly:
#
# "The current design has these limitations:
#
# 1. Single region - if the region goes down, entire system is offline.
# To fix: multi-region with cross-region replication.
# Trade-off: 2-3x infrastructure cost.
#
# 2. No message ordering across partitions.
# Users in the same conversation might see messages
# in slightly different order under high load.
# To fix: per-conversation sequence numbers.
#
# 3. File uploads are synchronous.
# For large files, this blocks the worker process.
# To fix: presigned URLs for direct S3 upload.
#
# If I had more time, I would focus on #1 first,
# as availability is our primary non-functional requirement."
## Самооценка после практики
Используйте этот чек-лист для оценки своих тренировочных сессий:
Задал ли я 5+ уточняющих вопросов?
Сделал ли я оценку нагрузки?
Нарисовал ли я понятную высокоуровневую архитектуру?
Описал ли я API и модель данных?
Углубился ли я в 2-3 компонента?
Обсудил ли я trade-offs для ключевых решений?
Упомянул ли я мониторинг и обработку ошибок?
Уложился ли я в 45 минут?
Выводы
Понимание критериев оценки позволяет целенаправленно готовиться. Тренируйте не только технические знания, но и навыки коммуникации, тайм-менеджмент и способность обосновывать решения.