На JS-собеседованиях чаще всего просят реализовать: deepClone (рекурсивное копирование), flattenArray (разворачивание вложенных массивов), groupBy (группировка объектов), pipe (цепочка функций), once (однократный вызов), reverse/palindrome, поиск дубликатов. Важно знать сложность каждого решения и уметь обсуждать trade-offs.
// Наивные подходы и их проблемы:
const copy1 = { ...obj } // поверхностная копия (shallow)
const copy2 = JSON.parse(JSON.stringify(obj)) // не копирует Date, undefined, функции
// Правильная реализация:
function deepClone(value) {
// null и примитивы — возвращаем как есть
if (value === null || typeof value !== 'object') return value
// Date — создаём новый объект
if (value instanceof Date) return new Date(value.getTime())
// Array — рекурсивно копируем каждый элемент
if (Array.isArray(value)) {
return value.map(item => deepClone(item))
}
// Object — рекурсивно копируем каждое свойство
const cloned = {}
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
cloned[key] = deepClone(value[key])
}
}
return cloned
}
// O(n) время и пространство, где n — количество узлов в структуре// Встроенный: arr.flat(Infinity) — но может быть запрещён
function flattenArray(arr, depth = Infinity) {
if (depth === 0) return [...arr]
return arr.reduce((acc, item) => {
if (Array.isArray(item) && depth > 0) {
acc.push(...flattenArray(item, depth - 1))
} else {
acc.push(item)
}
return acc
}, [])
}
flattenArray([1, [2, [3, [4]]]]) // [1, 2, 3, 4]
flattenArray([1, [2, [3, [4]]]], 1) // [1, 2, [3, [4]]]
// O(n) где n — общее количество элементовfunction groupBy(array, key) {
return array.reduce((groups, item) => {
const groupKey = typeof key === 'function' ? key(item) : item[key]
;(groups[groupKey] ??= []).push(item)
return groups
}, {})
}
const orders = [
{ id: 1, status: 'done', amount: 100 },
{ id: 2, status: 'pending', amount: 200 },
{ id: 3, status: 'done', amount: 50 },
]
groupBy(orders, 'status')
// { done: [{id:1,...}, {id:3,...}], pending: [{id:2,...}] }
groupBy(orders, o => o.amount > 100 ? 'big' : 'small')
// { small: [{id:1,...}, {id:3,...}], big: [{id:2,...}] }
// O(n) — один проходfunction pipe(...fns) {
return (x) => fns.reduce((acc, fn) => fn(acc), x)
}
const process = pipe(
x => x * 2,
x => x + 1,
x => x.toString()
)
process(5) // '11' (5*2=10, 10+1=11, String(11)='11')
// O(n) где n — количество функцийfunction once(fn) {
let called = false
let result
return function(...args) {
if (!called) {
called = true
result = fn.apply(this, args)
}
return result // всегда возвращаем тот же результат
}
}
const initialize = once(() => {
console.log('Инициализация (один раз)')
return { ready: true }
})
initialize() // выполняется
initialize() // не выполняется, возвращает тот же результат
initialize() // не выполняется
// O(1) после первого вызова// Реверс строки
function reverseString(str) {
return str.split('').reverse().join('')
// или: return [...str].reverse().join('') (корректно для Unicode)
}
// Проверка палиндрома
function isPalindrome(str) {
// Нормализуем: убираем не-буквы, приводим к нижнему регистру
const clean = str.toLowerCase().replace(/[^a-zа-яё]/g, '')
return clean === clean.split('').reverse().join('')
}
isPalindrome('racecar') // true
isPalindrome('A man a plan a canal Panama') // true
isPalindrome('hello') // false
// O(n) время, O(n) пространство// Вариант 1: через Set — O(n) время и пространство
function findDuplicates(arr) {
const seen = new Set()
const duplicates = new Set()
for (const item of arr) {
if (seen.has(item)) {
duplicates.add(item)
} else {
seen.add(item)
}
}
return [...duplicates]
}
// Вариант 2: через reduce + Map (с подсчётом)
function findDuplicatesWithCount(arr) {
const counts = arr.reduce((map, item) => {
map.set(item, (map.get(item) || 0) + 1)
return map
}, new Map())
return [...counts.entries()]
.filter(([_, count]) => count > 1)
.map(([item, count]) => ({ item, count }))
}
findDuplicates([1, 2, 3, 2, 4, 3, 5]) // [2, 3]
findDuplicatesWithCount([1, 2, 2, 3, 3, 3])
// [{ item: 2, count: 2 }, { item: 3, count: 3 }]| Функция | Время | Память |
|---------|-------|--------|
| deepClone | O(n) | O(n) |
| flattenArray | O(n) | O(n) |
| groupBy | O(n) | O(n) |
| pipe | O(k) | O(1) |
| once | O(1) | O(1) |
| reverseString | O(n) | O(n) |
| findDuplicates (Set) | O(n) | O(n) |
Перед написанием кода проговори вслух: «Я понимаю задачу так...», уточни edge cases (null, вложенные массивы, циклические ссылки). Напиши решение, затем скажи сложность. Предложи оптимизацию если видишь её. Для deepClone обязательно обсуди: что делать с Date, Map, Set, функциями, циклическими ссылками — интервьюер оценит осознанность.
Все 7 функций с реализацией и тестами: deepClone, flattenArray, groupBy, pipe, once, reverse/palindrome, findDuplicates
// ===== 1. DEEP CLONE =====
console.log('=== 1. deepClone ===')
function deepClone(value) {
if (value === null || typeof value !== 'object') return value
if (value instanceof Date) return new Date(value.getTime())
if (Array.isArray(value)) return value.map(item => deepClone(item))
const cloned = {}
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
cloned[key] = deepClone(value[key])
}
}
return cloned
}
const original = {
name: 'Алиса',
scores: [10, 20, 30],
address: { city: 'Москва', zip: '101000' },
birthday: new Date('1990-01-15')
}
const cloned = deepClone(original)
cloned.scores.push(40)
cloned.address.city = 'Питер'
console.log('original.scores:', original.scores) // [10, 20, 30] — не изменился
console.log('cloned.scores:', cloned.scores) // [10, 20, 30, 40]
console.log('original.address:', original.address.city) // 'Москва'
console.log('cloned.address:', cloned.address.city) // 'Питер'
console.log('Date сохранён:', cloned.birthday instanceof Date, cloned.birthday.getFullYear())
// ===== 2. FLATTEN ARRAY =====
console.log('\n=== 2. flattenArray ===')
function flattenArray(arr, depth = Infinity) {
return arr.reduce((acc, item) => {
if (Array.isArray(item) && depth > 0) {
acc.push(...flattenArray(item, depth - 1))
} else {
acc.push(item)
}
return acc
}, [])
}
console.log(flattenArray([1, [2, 3], [4, [5, 6]]])) // [1, 2, 3, 4, 5, 6]
console.log(flattenArray([1, [2, [3, [4, [5]]]]])) // [1, 2, 3, 4, 5]
console.log(flattenArray([1, [2, [3, [4]]]], 1)) // [1, 2, [3, [4]]]
console.log(flattenArray([1, [2, [3, [4]]]], 2)) // [1, 2, 3, [4]]
console.log(flattenArray([[1, 2], [3, [4, 5]], [[6]]])) // [1, 2, 3, 4, 5, 6]
// ===== 3. GROUP BY =====
console.log('\n=== 3. groupBy ===')
function groupBy(array, key) {
return array.reduce((groups, item) => {
const groupKey = typeof key === 'function' ? key(item) : item[key]
;(groups[groupKey] ??= []).push(item)
return groups
}, {})
}
const products = [
{ name: 'iPhone', category: 'phone', price: 999 },
{ name: 'Galaxy', category: 'phone', price: 799 },
{ name: 'MacBook', category: 'laptop', price: 1299 },
{ name: 'iPad', category: 'tablet', price: 599 },
{ name: 'ThinkPad', category: 'laptop', price: 899 },
]
const byCategory = groupBy(products, 'category')
console.log('По категориям:', Object.keys(byCategory)) // ['phone', 'laptop', 'tablet']
console.log('Телефонов:', byCategory.phone.length) // 2
const byPrice = groupBy(products, p => p.price > 800 ? 'expensive' : 'affordable')
console.log('Дорогих:', byPrice.expensive?.length) // 3
console.log('Доступных:', byPrice.affordable?.length) // 2
// ===== 4. PIPE =====
console.log('\n=== 4. pipe ===')
function pipe(...fns) {
return (x) => fns.reduce((acc, fn) => fn(acc), x)
}
const processOrder = pipe(
order => ({ ...order, total: order.price * order.qty }),
order => ({ ...order, tax: order.total * 0.2 }),
order => ({ ...order, finalPrice: order.total + order.tax }),
order => `Заказ #${order.id}: ${order.finalPrice.toFixed(2)}₽`
)
console.log(processOrder({ id: 101, price: 100, qty: 3 }))
// 'Заказ #101: 360.00₽' (300 + 60 tax)
const transformText = pipe(
s => s.trim(),
s => s.toLowerCase(),
s => s.replace(/\s+/g, '-'),
s => s.replace(/[^a-z0-9-]/g, '')
)
console.log(transformText(' Hello World 2024! ')) // 'hello-world-2024'
// ===== 5. ONCE =====
console.log('\n=== 5. once ===')
function once(fn) {
let called = false
let result
return function(...args) {
if (!called) {
called = true
result = fn.apply(this, args)
}
return result
}
}
let initCount = 0
const initialize = once(() => {
initCount++
console.log('Инициализация выполнена!')
return { sessionId: 'sess_' + Date.now(), ready: true }
})
const r1 = initialize()
const r2 = initialize()
const r3 = initialize()
console.log('r1 === r2:', r1 === r2) // true — тот же объект
console.log('r1 === r3:', r1 === r3) // true
console.log('initCount:', initCount) // 1 — выполнилось только раз
console.log('ready:', r1.ready) // true
// ===== 6. REVERSE / PALINDROME =====
console.log('\n=== 6. reverseString / isPalindrome ===')
function reverseString(str) {
return [...str].reverse().join('') // [...str] корректен для Unicode
}
function isPalindrome(str) {
const clean = str.toLowerCase().replace(/[^a-zа-яёa-z0-9]/g, '')
return clean === [...clean].reverse().join('')
}
console.log(reverseString('hello')) // 'olleh'
console.log(reverseString('JavaScript')) // 'tpircSavaJ'
console.log(reverseString('12345')) // '54321'
console.log(isPalindrome('racecar')) // true
console.log(isPalindrome('level')) // true
console.log(isPalindrome('A man a plan a canal Panama')) // true
console.log(isPalindrome('hello')) // false
console.log(isPalindrome('Ротор')) // true (русский)
// ===== 7. FIND DUPLICATES =====
console.log('\n=== 7. findDuplicates ===')
function findDuplicates(arr) {
const seen = new Set()
const duplicates = new Set()
for (const item of arr) {
if (seen.has(item)) {
duplicates.add(item)
} else {
seen.add(item)
}
}
return [...duplicates]
}
function findDuplicatesWithCount(arr) {
const counts = arr.reduce((map, item) => {
map.set(item, (map.get(item) || 0) + 1)
return map
}, new Map())
return [...counts.entries()]
.filter(([_, count]) => count > 1)
.map(([item, count]) => ({ item, count }))
}
console.log(findDuplicates([1, 2, 3, 2, 4, 3, 5])) // [2, 3]
console.log(findDuplicates(['a', 'b', 'a', 'c', 'b'])) // ['a', 'b']
console.log(findDuplicates([1, 2, 3])) // [] — нет дубликатов
console.log(findDuplicatesWithCount([1, 2, 2, 3, 3, 3]))
// [{ item: 2, count: 2 }, { item: 3, count: 3 }]На JS-собеседованиях чаще всего просят реализовать: deepClone (рекурсивное копирование), flattenArray (разворачивание вложенных массивов), groupBy (группировка объектов), pipe (цепочка функций), once (однократный вызов), reverse/palindrome, поиск дубликатов. Важно знать сложность каждого решения и уметь обсуждать trade-offs.
// Наивные подходы и их проблемы:
const copy1 = { ...obj } // поверхностная копия (shallow)
const copy2 = JSON.parse(JSON.stringify(obj)) // не копирует Date, undefined, функции
// Правильная реализация:
function deepClone(value) {
// null и примитивы — возвращаем как есть
if (value === null || typeof value !== 'object') return value
// Date — создаём новый объект
if (value instanceof Date) return new Date(value.getTime())
// Array — рекурсивно копируем каждый элемент
if (Array.isArray(value)) {
return value.map(item => deepClone(item))
}
// Object — рекурсивно копируем каждое свойство
const cloned = {}
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
cloned[key] = deepClone(value[key])
}
}
return cloned
}
// O(n) время и пространство, где n — количество узлов в структуре// Встроенный: arr.flat(Infinity) — но может быть запрещён
function flattenArray(arr, depth = Infinity) {
if (depth === 0) return [...arr]
return arr.reduce((acc, item) => {
if (Array.isArray(item) && depth > 0) {
acc.push(...flattenArray(item, depth - 1))
} else {
acc.push(item)
}
return acc
}, [])
}
flattenArray([1, [2, [3, [4]]]]) // [1, 2, 3, 4]
flattenArray([1, [2, [3, [4]]]], 1) // [1, 2, [3, [4]]]
// O(n) где n — общее количество элементовfunction groupBy(array, key) {
return array.reduce((groups, item) => {
const groupKey = typeof key === 'function' ? key(item) : item[key]
;(groups[groupKey] ??= []).push(item)
return groups
}, {})
}
const orders = [
{ id: 1, status: 'done', amount: 100 },
{ id: 2, status: 'pending', amount: 200 },
{ id: 3, status: 'done', amount: 50 },
]
groupBy(orders, 'status')
// { done: [{id:1,...}, {id:3,...}], pending: [{id:2,...}] }
groupBy(orders, o => o.amount > 100 ? 'big' : 'small')
// { small: [{id:1,...}, {id:3,...}], big: [{id:2,...}] }
// O(n) — один проходfunction pipe(...fns) {
return (x) => fns.reduce((acc, fn) => fn(acc), x)
}
const process = pipe(
x => x * 2,
x => x + 1,
x => x.toString()
)
process(5) // '11' (5*2=10, 10+1=11, String(11)='11')
// O(n) где n — количество функцийfunction once(fn) {
let called = false
let result
return function(...args) {
if (!called) {
called = true
result = fn.apply(this, args)
}
return result // всегда возвращаем тот же результат
}
}
const initialize = once(() => {
console.log('Инициализация (один раз)')
return { ready: true }
})
initialize() // выполняется
initialize() // не выполняется, возвращает тот же результат
initialize() // не выполняется
// O(1) после первого вызова// Реверс строки
function reverseString(str) {
return str.split('').reverse().join('')
// или: return [...str].reverse().join('') (корректно для Unicode)
}
// Проверка палиндрома
function isPalindrome(str) {
// Нормализуем: убираем не-буквы, приводим к нижнему регистру
const clean = str.toLowerCase().replace(/[^a-zа-яё]/g, '')
return clean === clean.split('').reverse().join('')
}
isPalindrome('racecar') // true
isPalindrome('A man a plan a canal Panama') // true
isPalindrome('hello') // false
// O(n) время, O(n) пространство// Вариант 1: через Set — O(n) время и пространство
function findDuplicates(arr) {
const seen = new Set()
const duplicates = new Set()
for (const item of arr) {
if (seen.has(item)) {
duplicates.add(item)
} else {
seen.add(item)
}
}
return [...duplicates]
}
// Вариант 2: через reduce + Map (с подсчётом)
function findDuplicatesWithCount(arr) {
const counts = arr.reduce((map, item) => {
map.set(item, (map.get(item) || 0) + 1)
return map
}, new Map())
return [...counts.entries()]
.filter(([_, count]) => count > 1)
.map(([item, count]) => ({ item, count }))
}
findDuplicates([1, 2, 3, 2, 4, 3, 5]) // [2, 3]
findDuplicatesWithCount([1, 2, 2, 3, 3, 3])
// [{ item: 2, count: 2 }, { item: 3, count: 3 }]| Функция | Время | Память |
|---------|-------|--------|
| deepClone | O(n) | O(n) |
| flattenArray | O(n) | O(n) |
| groupBy | O(n) | O(n) |
| pipe | O(k) | O(1) |
| once | O(1) | O(1) |
| reverseString | O(n) | O(n) |
| findDuplicates (Set) | O(n) | O(n) |
Перед написанием кода проговори вслух: «Я понимаю задачу так...», уточни edge cases (null, вложенные массивы, циклические ссылки). Напиши решение, затем скажи сложность. Предложи оптимизацию если видишь её. Для deepClone обязательно обсуди: что делать с Date, Map, Set, функциями, циклическими ссылками — интервьюер оценит осознанность.
Все 7 функций с реализацией и тестами: deepClone, flattenArray, groupBy, pipe, once, reverse/palindrome, findDuplicates
// ===== 1. DEEP CLONE =====
console.log('=== 1. deepClone ===')
function deepClone(value) {
if (value === null || typeof value !== 'object') return value
if (value instanceof Date) return new Date(value.getTime())
if (Array.isArray(value)) return value.map(item => deepClone(item))
const cloned = {}
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
cloned[key] = deepClone(value[key])
}
}
return cloned
}
const original = {
name: 'Алиса',
scores: [10, 20, 30],
address: { city: 'Москва', zip: '101000' },
birthday: new Date('1990-01-15')
}
const cloned = deepClone(original)
cloned.scores.push(40)
cloned.address.city = 'Питер'
console.log('original.scores:', original.scores) // [10, 20, 30] — не изменился
console.log('cloned.scores:', cloned.scores) // [10, 20, 30, 40]
console.log('original.address:', original.address.city) // 'Москва'
console.log('cloned.address:', cloned.address.city) // 'Питер'
console.log('Date сохранён:', cloned.birthday instanceof Date, cloned.birthday.getFullYear())
// ===== 2. FLATTEN ARRAY =====
console.log('\n=== 2. flattenArray ===')
function flattenArray(arr, depth = Infinity) {
return arr.reduce((acc, item) => {
if (Array.isArray(item) && depth > 0) {
acc.push(...flattenArray(item, depth - 1))
} else {
acc.push(item)
}
return acc
}, [])
}
console.log(flattenArray([1, [2, 3], [4, [5, 6]]])) // [1, 2, 3, 4, 5, 6]
console.log(flattenArray([1, [2, [3, [4, [5]]]]])) // [1, 2, 3, 4, 5]
console.log(flattenArray([1, [2, [3, [4]]]], 1)) // [1, 2, [3, [4]]]
console.log(flattenArray([1, [2, [3, [4]]]], 2)) // [1, 2, 3, [4]]
console.log(flattenArray([[1, 2], [3, [4, 5]], [[6]]])) // [1, 2, 3, 4, 5, 6]
// ===== 3. GROUP BY =====
console.log('\n=== 3. groupBy ===')
function groupBy(array, key) {
return array.reduce((groups, item) => {
const groupKey = typeof key === 'function' ? key(item) : item[key]
;(groups[groupKey] ??= []).push(item)
return groups
}, {})
}
const products = [
{ name: 'iPhone', category: 'phone', price: 999 },
{ name: 'Galaxy', category: 'phone', price: 799 },
{ name: 'MacBook', category: 'laptop', price: 1299 },
{ name: 'iPad', category: 'tablet', price: 599 },
{ name: 'ThinkPad', category: 'laptop', price: 899 },
]
const byCategory = groupBy(products, 'category')
console.log('По категориям:', Object.keys(byCategory)) // ['phone', 'laptop', 'tablet']
console.log('Телефонов:', byCategory.phone.length) // 2
const byPrice = groupBy(products, p => p.price > 800 ? 'expensive' : 'affordable')
console.log('Дорогих:', byPrice.expensive?.length) // 3
console.log('Доступных:', byPrice.affordable?.length) // 2
// ===== 4. PIPE =====
console.log('\n=== 4. pipe ===')
function pipe(...fns) {
return (x) => fns.reduce((acc, fn) => fn(acc), x)
}
const processOrder = pipe(
order => ({ ...order, total: order.price * order.qty }),
order => ({ ...order, tax: order.total * 0.2 }),
order => ({ ...order, finalPrice: order.total + order.tax }),
order => `Заказ #${order.id}: ${order.finalPrice.toFixed(2)}₽`
)
console.log(processOrder({ id: 101, price: 100, qty: 3 }))
// 'Заказ #101: 360.00₽' (300 + 60 tax)
const transformText = pipe(
s => s.trim(),
s => s.toLowerCase(),
s => s.replace(/\s+/g, '-'),
s => s.replace(/[^a-z0-9-]/g, '')
)
console.log(transformText(' Hello World 2024! ')) // 'hello-world-2024'
// ===== 5. ONCE =====
console.log('\n=== 5. once ===')
function once(fn) {
let called = false
let result
return function(...args) {
if (!called) {
called = true
result = fn.apply(this, args)
}
return result
}
}
let initCount = 0
const initialize = once(() => {
initCount++
console.log('Инициализация выполнена!')
return { sessionId: 'sess_' + Date.now(), ready: true }
})
const r1 = initialize()
const r2 = initialize()
const r3 = initialize()
console.log('r1 === r2:', r1 === r2) // true — тот же объект
console.log('r1 === r3:', r1 === r3) // true
console.log('initCount:', initCount) // 1 — выполнилось только раз
console.log('ready:', r1.ready) // true
// ===== 6. REVERSE / PALINDROME =====
console.log('\n=== 6. reverseString / isPalindrome ===')
function reverseString(str) {
return [...str].reverse().join('') // [...str] корректен для Unicode
}
function isPalindrome(str) {
const clean = str.toLowerCase().replace(/[^a-zа-яёa-z0-9]/g, '')
return clean === [...clean].reverse().join('')
}
console.log(reverseString('hello')) // 'olleh'
console.log(reverseString('JavaScript')) // 'tpircSavaJ'
console.log(reverseString('12345')) // '54321'
console.log(isPalindrome('racecar')) // true
console.log(isPalindrome('level')) // true
console.log(isPalindrome('A man a plan a canal Panama')) // true
console.log(isPalindrome('hello')) // false
console.log(isPalindrome('Ротор')) // true (русский)
// ===== 7. FIND DUPLICATES =====
console.log('\n=== 7. findDuplicates ===')
function findDuplicates(arr) {
const seen = new Set()
const duplicates = new Set()
for (const item of arr) {
if (seen.has(item)) {
duplicates.add(item)
} else {
seen.add(item)
}
}
return [...duplicates]
}
function findDuplicatesWithCount(arr) {
const counts = arr.reduce((map, item) => {
map.set(item, (map.get(item) || 0) + 1)
return map
}, new Map())
return [...counts.entries()]
.filter(([_, count]) => count > 1)
.map(([item, count]) => ({ item, count }))
}
console.log(findDuplicates([1, 2, 3, 2, 4, 3, 5])) // [2, 3]
console.log(findDuplicates(['a', 'b', 'a', 'c', 'b'])) // ['a', 'b']
console.log(findDuplicates([1, 2, 3])) // [] — нет дубликатов
console.log(findDuplicatesWithCount([1, 2, 2, 3, 3, 3]))
// [{ item: 2, count: 2 }, { item: 3, count: 3 }]Реализуй функцию deepClone(obj) которая создаёт глубокую копию объекта. Функция должна корректно обрабатывать: примитивы (числа, строки, boolean), null и undefined, вложенные объекты, массивы (включая вложенные), объекты Date. Изменение клона не должно влиять на оригинал.
Порядок проверок важен: сначала null/примитивы, затем Date, затем Array.isArray(), затем объект. Для Date: new Date(value.getTime()). Для массива: value.map(item => deepClone(item)). Для объекта: перебери for...in с проверкой hasOwnProperty.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке